synchronizacja w php - meet magento

22
Wiktor Jarka Creatuity Synchronizacja - sekcje krytyczne na przykładzie integracji z systemem lojalnościowym

Upload: wjarka

Post on 29-Nov-2014

157 views

Category:

Technology


4 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Synchronizacja w PHP - Meet Magento

Wiktor JarkaCreatuity

Synchronizacja - sekcje krytyczne na przykładzie integracji z systemem lojalnościowym

Page 2: Synchronizacja w PHP - Meet Magento

Definicje

Sekcja krytyczna

“W programowaniu współbieżnym - fragment kodu, w którym korzysta się z zasobu współdzielonego, a co za tym idzie - w danej chwili może być wykorzystywany przez co najwyżej jeden wątek.”

Semafor

“Chroniona zmienna lub abstrakcyjny typ danych, który stanowi klasyczną metodę kontroli dostępu przez wiele procesów do wspólnego zasobu w środowisku programowania równoległego.”

Page 3: Synchronizacja w PHP - Meet Magento

Opis problemu

● Projekt: system zakupów grupowych wyspecjalizowany w polach golfowych,

● Opticard: system punktów lojalnościowych posiadający API,

● typy transakcji: inicjacja karty, dodawanie punktów, zapisanie danych użytkownika,

● sequence number: część specyfikacji Opticard API,● pula numerów kart: część specyfikacji modułu

integrującego Opticard z Magento.

Page 4: Synchronizacja w PHP - Meet Magento

Zachowanie spójności danych

Jak?

Page 5: Synchronizacja w PHP - Meet Magento

Sequence Number

● służy do zachowania spójności Opticard i systemu,

● zwiększany po poprawnej transakcji,● pozostaje taki sam po błędzie,● nie mogą istnieć dwie poprawne transakcje

z tym samym numerem sekwencji.

Page 6: Synchronizacja w PHP - Meet Magento

Pula kart

● administrator Magento definiuje jakie karty mogą być przypisane do klientów,

● dowolna, wolna karta jest przypisywana do użytkownika w momencie, kiedy pierwszy raz coś kupi,

● jedna karta może być przypisana tylko do jednego użytkownika (przez cały “cykl życia” karty).

Page 7: Synchronizacja w PHP - Meet Magento

Podstawowe informacje o systemie

Page 8: Synchronizacja w PHP - Meet Magento

Wersja wyjściowaclass Creatuitycorp_Opticard_Model_Card_Pool extends Varien_Object {

(...)

public function assignAnyFreeCardToCustomer($customerOrId) { $customerId = $this->_helper()->asCustomerId($customerOrId); $freeCards = $this->getFreeCardNumbersCollection() ->addFieldToSelect('card_id') ->addFieldToSelect('card_number'); if ($freeCards->count() == 0) { Mage::throwException('There is no Opticard card numbers available to assign'); } /** @var Creatuitycorp_Opticard_Model_Card $card */ $card = $freeCards->getFirstItem(); $card->assignToCustomer($customerId); $card->save(); return $card; }}

Page 9: Synchronizacja w PHP - Meet Magento

Synchronizacja w PHP

Jest kilka możliwości:● poprzez plik: flock(),● poprzez wbudowany semafor: sem_acquire(),● poprzez mysql: GET_LOCK().

Page 10: Synchronizacja w PHP - Meet Magento

Rozwiązanie: krok 1class Creatuitycorp_Opticard_Model_Card_Pool extends Varien_Object {

public function assignAnyFreeCardToCustomer($customerOrId) { try { $row = $this->_db()->query("SELECT GET_LOCK('" . self::$_lockName . "', 15) AS locked")->fetch(); $isTimeout = $row['locked'] == 0; if ($isTimeout) { $this->_debugLog("Couldn't lock. Timeout exceeded."); Mage::throwException("I was unable to create lock within 15 seconds."); } $this->_db()->beginTransaction();

// LOGIC GOES HERE

$this->_db()->commit(); $this->_db()->query("DO RELEASE_LOCK('" . self::$_lockName . "')"); return $card;

} catch (Exception $e) { $this->_db()->rollback(); $this->_db()->query("DO RELEASE_LOCK('" . self::$_lockName . "')"); throw $e; } }

(...)

$customerId = $this->_helper()->asCustomerId($customerOrId);

$freeCards = $this->getFreeCardNumbersCollection() ->addFieldToSelect('card_id') ->addFieldToSelect('card_number');

if ($freeCards->count() == 0) { Mage::throwException('There is no Opticard card numbers available to assign');}/** @var Creatuitycorp_Opticard_Model_Card $card */$card = $freeCards->getFirstItem();

$card->assignToCustomer($customerId);

$card->save();

Page 11: Synchronizacja w PHP - Meet Magento

Lepiej, ale…?

• Ciężko to pokazać na slajdzie (serio!),• duplikacja kodu dla każdej funkcji, która wymaga

działania w sekcji krytycznej,• kiedy sekcje nachodzą do siebie - RELEASE_LOCK

może być wykonane zbyt wcześnie.

Page 12: Synchronizacja w PHP - Meet Magento

Nachodzące na siebie sekcje

Wymagana transakcyjność!

Page 13: Synchronizacja w PHP - Meet Magento

Rozwiązanie: krok 2.1class Creatuitycorp_Opticard_Model_Mysql_Transactional_Critical_Section extends Creatuitycorp_Opticard_Model_Mysql_Abstract {

public function begin() { $this->_semaphore()->lock(); $this->_transaction()->begin(); }

public function end() { $this->_transaction()->commit(); $this->_semaphore()->release(); }

public function revert() { $this->_transaction()->rollback(); $this->_semaphore()->release(); }

/** @return Creatuitycorp_Opticard_Model_Mysql_Transactions */ protected function _transaction() { return Mage::getSingleton('opticard/mysql_transactions'); }

/** @return Creatuitycorp_Opticard_Model_Mysql_Semaphore */ protected function _semaphore() { return Mage::getSingleton('opticard/mysql_semaphore'); }(...)

Page 14: Synchronizacja w PHP - Meet Magento

Rozwiązanie: krok 2.2class Creatuitycorp_Opticard_Model_Mysql_Semaphore extends Creatuitycorp_Opticard_Model_Mysql_Abstract {

private static $_count = 0; private static $_lockName = 'opticard_semaphore';

public function lock($timeout = 300) { if (self::$_count++ === 0) { $row = $this->_db()->query("SELECT GET_LOCK('" . self::$_lockName . "', " . $timeout . ") AS locked")->fetch(); $isTimeout = $row['locked'] == 0; if ($isTimeout) { Mage::throwException("Unable to lock. Timeout exceeded."); } } }

public function release() { if (self::$_count === 0) { Mage::throwException("Cannot unlock what's not locked. Neither can Chuck Norris."); }

if (--self::$_count === 0) { $this->_db()->query("DO RELEASE_LOCK('" . self::$_lockName . "')"); } }(...)

Page 15: Synchronizacja w PHP - Meet Magento

Rozwiązanie: krok 2.3class Creatuitycorp_Opticard_Model_Card_Pool extends Varien_Object { public function assignAnyFreeCardToCustomer($customerOrId) { try { $this->_criticalSection()->begin();

$customerId = $this->_helper()->asCustomerId($customerOrId); $freeCards = $this->getFreeCardNumbersCollection() ->addFieldToSelect('card_id') ->addFieldToSelect('card_number');

if ($freeCards->count() == 0) { Mage::throwException('There is no Opticard card numbers available to assign'); }

/** @var Creatuitycorp_Opticard_Model_Card $card */ $card = $freeCards->getFirstItem(); $card->assignToCustomer($customerId);

$card->save();

$this->_criticalSection()->end(); return $card; } catch (Exception $e) { $this->_criticalSection()->rollback(); throw $e; } }(...)

Page 16: Synchronizacja w PHP - Meet Magento

Krok 2 - podsumowanie

Minusy

• konieczność dodawania dodatkowego kodu do każdej funkcji używającej sekcji krytycznej

Plusy

• obsługa nachodzących na siebie sekcji krytycznych

• lepsza organizacja i czytelność kodu

Page 17: Synchronizacja w PHP - Meet Magento

Rozwiązanie: krok 3.1class Creatuitycorp_Opticard_Model_Card_Pool extends Varien_Object { public function assignAnyFreeCardToCustomer__CRITICAL_SECTION($customerOrId) { $customerId = $this->_helper()->asCustomerId($customerOrId);

$freeCards = $this->getFreeCardNumbersCollection() ->addFieldToSelect('card_id') ->addFieldToSelect('card_number');

if ($freeCards->count() == 0) { Mage::throwException('There is no Opticard card numbers available to assign'); }

/** @var Creatuitycorp_Opticard_Model_Card $card */ $card = $freeCards->getFirstItem();

$card->assignToCustomer($customerId); $card->save();

return $card; }

public function __call($method, $args) { if ($this->_criticalSection()->shouldRunInCriticalSection($this, $method)) { return $this->_criticalSection()->runMethodInCriticalSection($this, $method, $args); } return parent::__call($method, $args); }(...)

Page 18: Synchronizacja w PHP - Meet Magento

Rozwiązanie: krok 3.2class Creatuitycorp_Opticard_Model_Mysql_Transactional_Critical_Section extends Creatuitycorp_Opticard_Model_Mysql_Abstract {

const METHOD_SUFFIX = '__CRITICAL_SECTION';

public function shouldRunInCriticalSection($object, $methodName) { return method_exists($object, $this->_csMethodName($methodName)); }

public function runMethodInCriticalSection($object, $methodName, $args) { return $this->run($object, $this->_csMethodName($methodName), $args); } protected function _csMethodName($methodName) { return $methodName . self::METHOD_SUFFIX; } public function run($object, $methodName, array $args = array()) { try { $this->begin(); $result = call_user_func_array(array($object, $methodName), $args); $this->end(); return $result; } catch (Exception $e) { $this->revert(); throw $e; } }(...)

Page 19: Synchronizacja w PHP - Meet Magento

Krok 3 - podsumowanie

Minusy

• konieczność nadpisania funkcji __call dla każdej klasy używającej sekcji krytycznej

Plusy

• funkcja robi tylko i wyłącznie to za co jest odpowiedzialna

Page 20: Synchronizacja w PHP - Meet Magento

Pytania?

Page 21: Synchronizacja w PHP - Meet Magento

A jak Ty byś to zrobił?

Page 22: Synchronizacja w PHP - Meet Magento

Dziękuję za uwagę

@[email protected]