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 e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *