Persystencja sesji PHP niskim kosztem

Zapewnienie persystencji sesji logowania w obrębie rozproszonych infrastruktur jest dzisiaj koniecznością. Wykorzystanie do tego baz danych, choć popularne, zmniejsza uniwersalność architektury ograniczając ją wyłącznie do aplikacji wspierający to rozwiązanie, z kolei produkty zapewniające dobre wsparcie klastrowania np. couchbase niosą ze sobą spore wymagania sprzętowe i uniemożliwiają wykorzystanie ich na słabszych maszynach. Na szczęście zadowalające rozwiązanie można złożyć ze standardowych i lekkich komponentów.

PHP pozwala na zdefiniowanie handlera odpowiedzialnego za obsługę sesji. Domyślnie są one przechowywane w plikach:

[Session]
; Handler used to store/retrieve data.
; http://php.net/session.save-handler
session.save_handler = files

niestety, korzystając z tej metody bardzo trudno efektywnie zapewnić persystencję na wielu hostach (istnieją sposoby replikacji sesji przez pliki, ale z mojej wiedzy wynika, że są one skomplikowane i zawodne).

Dla zachowania maksymalnej uniwersalności zastosowanego rozwiązania należy pozostać przy sesjach PHP (w przeciwieństwie do sesji składowanych w bazie danych, z których aplikacja korzysta na własnych zasadach), zapewniając jednocześnie takie ich składowanie, które umożliwi łatwą replikację.

Produktem spełniającym powyższe kryteria, a jednocześnie lekkim i łatwym w konfiguracji jest  memcache, często i powszechnie wykorzystywany do przechowywania sesji. Jako serwer cache jest szybszy i wydajniejszy niż dostęp do plików, chociażby ze względu na ograniczenie (a właściwie praktyczną likwidację) operacji I/O. Niestety, również to rozwiązanie domyślnie ogranicza się do jednego hosta. Należy więc nieco podrasować jego konfigurację 😉 Głównie po stronie PHP.

Zmiany zaczynamy od ustawienia handlera sesji konfigurowanego w php.ini. Po pierwsze musimy podać, że będziemy używać memcache:

[Session]
; Handler used to store/retrieve data.
; http://php.net/session.save-handler
; session.save_handler = files
session.save_handler = memcache

następnie definiujemy session.save_path. To właśnie tutaj znajdą się adresy wszystkich serwerów memache na które będziemy zapisywać informacje o sesji. Każdy z serwerów podajemy poprzez connection string w postaci:

[protokol]://[ip/host name]:[port]

a poszczególne definicje serwerów oddzielamy przecinkiem.
Przykładowa konfiguracja będzie wyglądała więc następująco:

[Session]
; Handler used to store/retrieve data.
; http://php.net/session.save-handler
session.save_handler = memcache
session.save_path = "tcp://10.20.10.1:11211,tcp://10.20.10.2:11211,tcp://10.20.10.3:11211"

Po zdefiniowaniu handlera i listy serwerów należy przejść do konfiguracji i włączenia samego rozszerzenia memcache.so dla PHP w następujący sposób:

extension=memcache.so

memcache.allow_failover=1
memcache.session_redundancy=4
memcache.max_failover_attempts=4
memcache.hash_strategy=consistent
memcache.chunk_size=32768

Na początek musimy włączyć sam failover sesji poprzez ustawienie na 1 wartości memcache.allow_failover. Następnie w memcache.session_redundancy podajemy liczbę serwerów na które ma być replikowana sesja. Przez błąd PHP opisany tutaj liczba ta musi wynosić [memcache_servers]+1 (z dyskusji wynika, że do roku 2013 błąd nie został usunięty, a z moich obserwacji wynika, że występuje nadal). Należy również zmienić strategię tworzenia kluczy poprzez zdefiniowanie memcache.hash_strategy jako consistent, gdyż  przy takim ustawieniu dodawanie i odejmowanie kolejnych serwerów nie będzie się wiązało z remapowaniem kluczy na maszynach.

Opcjonalnie możemy także zmienić ilość prób zapisu / odczytu przed failover manipulując wartością memcache.max_failover_attempts oraz zmienić memcache.chunk_size. To ostatnie określa wielkość paczki (w kilobajtach) w jakiej dane będą wymieniane pomiędzy serwerami. Jej ustawienie wpływa na ilość zapisów wykonywanych między serwerami i powinno być adekwatne do ilości danych w naszych memcache sesyjnych oraz wydajności sieci.

Uzupełnieniem całej konfiguracji może być rozszerzenie connection strings serwerów memcache definiowanych w obrębie session.save_path. Do każdego z nich możemy dopisać (w standardowym formacie parametrów GET?parametr1=1&parametr2=2 ) zmienne, które wpłyną na sposób komunikacji z serwerem. Parametry są takie same jak dla funkcji Memcache::addServer i można za ich pomocą zdefiniować m.in.:

  • wagę danego serwera za pomocą parametru weight,
  • timeout – za pomocą tego parametru,
  • czas co jaki sprawdzane będzie czy niedziałający serwer jest online – retry_interval (ustawienie wartości -1 wyłącza automatyczne ponawianie),
  • wymusić status serwera poprzez zmianę wartości bool parametru status.

 Jak widać konfiguracja całego rozwiązania nie należy do łatwych ani intuicyjnych. Ponadto jest dość słabo udokumentowana i większość cennych porad rozsianych jest po różnych forach i stronach dla ekspertów. To, co opisałem wyżej to jedynie ustawienia związane z PHP, pewnych usprawnień można dokonać także w obrębie konfiguracji samych serwerów memache.

Zgodnie z tym co napisałem na początku przedstawione tu rozwiązanie zapewnia persystencję sesji niskim kosztem w rozumieniu nakładów na sprzęt niezbędny do utrzymania takiego rozwiązania. Stosując je pamiętać musimy jednak, że nie mamy tutaj do czynienia z pełnoprawnym klastrem. W związku z tym nie otrzymamy ani łatwości konfiguracji ani pewności spójności danych jaką dają bardziej rozbudowane rozwiązania np. couchbase.

Pomimo swoich wad (min. braku interfejsu do zarządzania czy diagnostyki – jego namiastką jeżeli chodzi o memcache może być np. phpmemcacheadmin) i skomplikowanej konfiguracji rozwiązanie to sprawdza się dobrze przy mniejszym ruchu i słabszych maszynach pozwalając na postawienie infrastruktury  rozproszonej dla aplikacji PHP np. na tanich VPS.

Mikołaj Niedbała

I'm a Poland based IT administrator, linux administrator and IT engineer creating professional IT infrastructure solutions based on Linux and virtual environments.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *