Контакти

Вітаю Вас, дорогий друже! Партнерська програма Бідний success php

Стандартний механізм зберігання даних сесії користувачів в php - зберігання в файлах. Однак при роботі додатка на декількох серверах для балансування навантаження, виникає необхідність зберігати дані сесій в сховище, доступному кожному сервера додатки. В цьому випадку для зберігання сесій добре підходить Redis.

Найбільш популярне рішення - розширення phpredis. Досить встановити розширення і налаштувати php.ini і сесії будуть автоматично зберігатися в Redis без зміни коду додатків.

Однак таке рішення має недолік - відсутність блокування сесії.

При використанні стандартного механізму зберігання сесій в файлах відкрита сесія блокує файл поки не буде закрита. При декількох одночасних зверненнях доступ до сесії нові запити чекатимуть, поки попередній не завершить роботу з сесією. Однак при використанні phpredis подібного механізму блокувань немає. При декількох асинхронних запитів одночасно відбувається гонка, і деякі дані, що записуються в сесію, коли ви можете втратити.

Це легко перевірити. Відправляємо на сервер асинхронно 100 запитів, кожен з яких пише в сесію свій параметр, потім вважаємо кількість параметрів в сесії.

тестовий скрипт

"; Break;)


В результаті отримуємо, що в сесії не 100 параметрів, а 60-80. Інші дані ми втратили.
У реальних додатках звичайно 100 одночасних запитів не буде, проте практика показує, що навіть при двох асинхронних одночасних запитах дані, що записуються одним із запитів, досить часто затираються іншим. Таким чином, використання розширення phpredis для зберігання сесій небезпечно і може привести до втрати даних.

Як один з варіантів вирішення проблеми - свій SessionHandler, Що підтримує блокування.

Реалізація

Щоб встановити блокування сесії, встановимо значення ключа блокування в випадково сгенерированное (на основі uniqid) значення. Значення має бути унікальним, щоб будь-який паралельний запит не міг отримати доступ.

Protected function lockSession ($ sessionId) ($ attempts \u003d (1000000 * $ this-\u003e lockMaxWait) / $ this-\u003e spinLockWait; $ this-\u003e token \u003d uniqid (); $ this-\u003e< $attempts; ++$i) { $success = $this->redis-\u003e set ($ this-\u003e getRedisKey ($ this-\u003e lockKey), $ this-\u003e token, [ "NX",]); if ($ success) ($ this-\u003e locked \u003d true; return true;) usleep ($ this-\u003e spinLockWait); ) Return false; )
Значення встановлюється з прапором NX, Тобто установка відбувається тільки в разі, якщо такого ключа немає. Якщо ж такий ключ існує, робимо повторну спробу через деякий час.

Можна також використовувати обмежений час життя ключа в редисці, проте час роботи скрипта може бути змінено після установки ключа, і паралельний процес зможе отримати доступ до сесії до завершення роботи з нею в поточному скрипті. При завершенні роботи скрипта ключ в будь-якому випадку видаляється.

При розблокуванні сесії при завершенні роботи скрипта для видалення ключа використовуємо Lua-сценарій:

Private function unlockSession () ($ script \u003d<<redis-\u003e eval ($ script, array ($ this-\u003e getRedisKey ($ this-\u003e lockKey), $ this-\u003e token), 1); $ This-\u003e locked \u003d false; $ This-\u003e token \u003d null; )
використовувати команду DEL не можна, так як за допомогою неї можна видалити ключ, встановлена \u200b\u200bіншою скриптом. Такий сценарій же гарантує видалення тільки в разі, якщо ключу блокування відповідає унікальне значення, встановлене поточним скриптом.

Повний код класу

class RedisSessionHandler implements \\ SessionHandlerInterface (protected $ redis; protected $ ttl; protected $ prefix; protected $ locked; private $ lockKey; private $ token; private $ spinLockWait; private $ lockMaxWait; public function __construct (\\ Redis $ redis, $ prefix \u003d "PHPREDIS_SESSION:", $ spinLockWait \u003d 200000) ($ this-\u003e redis \u003d $ redis; $ this-\u003e ttl \u003d ini_get ( "gc_maxlifetime"); $ iniMaxExecutionTime \u003d ini_get ( "max_execution_time"); $ this-\u003e lockMaxWait \u003d $ iniMaxExecutionTime? $ iniMaxExecutionTime * 0.7: 20; $ this-\u003e prefix \u003d $ prefix; $ this-\u003e locked \u003d false; $ this-\u003e lockKey \u003d null; $ this-\u003e spinLockWait \u003d $ spinLockWait;) public function open ($ savePath , $ sessionName) (return true;) protected function lockSession ($ sessionId) ($ attempts \u003d (1000000 * $ this-\u003e lockMaxWait) / $ this-\u003e spinLockWait; $ this-\u003e token \u003d uniqid (); $ this-\u003e lockKey \u003d $ sessionId. ".lock"; for ($ i \u003d 0; $ i< $attempts; ++$i) { $success = $this->redis-\u003e set ($ this-\u003e getRedisKey ($ this-\u003e lockKey), $ this-\u003e token, [ "NX",]); if ($ success) ($ this-\u003e locked \u003d true; return true;) usleep ($ this-\u003e spinLockWait); ) Return false; ) Private function unlockSession () ($ script \u003d<<redis-\u003e eval ($ script, array ($ this-\u003e getRedisKey ($ this-\u003e lockKey), $ this-\u003e token), 1); $ This-\u003e locked \u003d false; $ This-\u003e token \u003d null; ) Public function close () (if ($ this-\u003e locked) ($ this-\u003e unlockSession ();) return true;) public function read ($ sessionId) (if (! $ This-\u003e locked) (if (! $ this-\u003e lockSession ($ sessionId)) (return false;)) return $ this-\u003e redis-\u003e get ($ this-\u003e getRedisKey ($ sessionId))?: "";) public function write ($ sessionId, $ data) (if ($ this-\u003e ttl\u003e 0) ($ this-\u003e redis-\u003e setex ($ this-\u003e getRedisKey ($ sessionId), $ this-\u003e ttl, $ data);) else ($ this-\u003e redis-\u003e set ($ this-\u003e getRedisKey ($ sessionId), $ data);) return true;) public function destroy ($ sessionId) ($ this-\u003e redis-\u003e del ($ this-\u003e getRedisKey ($ sessionId) ); $ this-\u003e close (); return true;) public function gc ($ lifetime) (return true;) public function setTtl ($ ttl) ($ this-\u003e ttl \u003d $ ttl;) public function getLockMaxWait () ( return $ this-\u003e lockMaxWait;) public function setLockMaxWait ($ lockMaxWait) ($ this-\u003e lockMaxWait \u003d $ lockMaxWait;) protected function getRedisKey ($ key) (if (empty ($ this-\u003e prefix)) (return $ key; ) return $ This-\u003e prefix. $ Key; ) Public function __destruct () ($ this-\u003e close ();))

підключення

$ Redis \u003d new Redis (); if ($ redis-\u003e connect ( "11.111.111.11", 6379) && $ redis-\u003e select (0)) ($ handler \u003d new \\ suffi \\ RedisSessionHandler \\ RedisSessionHandler ($ redis); session_set_save_handler ($ handler);) session_start ();

результат

Після підключення нашого SessionHandler наш тестовий скрипт впевнено показує 100 параметрів в сесії. При цьому незважаючи на блокування загальний час обробки 100 запитів зросла незначно. У реальній практиці такої кількості одночасних запитів не буде. Однак час роботи скрипта зазвичай більш істотно, і при одночасних запитах може бути помітне очікування. Тому потрібно думати про скорочення часу роботи з сесією скрипта (виклик session_start () тільки при необхідності роботи з сесією і session_write_close () при завершенні роботи з нею)

Здійснює запит до сервера без перезавантаження сторінки. Це низькорівневий метод, який володіє великою кількістю налаштувань. Він лежить в основі роботи всіх інших методів ajax. Має два варіанти використання:

url - адреса запиту.
settings - в цьому параметрі можна задати налаштування для даного запиту. Здається за допомогою об'єкта в форматі (ім'я: значення, ім'я: значення ...). Жодна з налаштувань не є обов'язковою. Встановити налаштування за замовчуванням можна за допомогою методу $ .ajaxSetup ().

список налаштувань

↓ назва: тип (значення за замовчуванням)

При виконанні запиту, в заголовках (header) вказуються допустимі типи вмісту, очікуваного від сервера. Значення цих типів будуть взяті з параметра accepts.

За замовчуванням, всі запити без перезавантаження сторінки відбуваються асинхронно (тобто після відправки запиту на сервер, сторінка не зупиняє свою роботу в очікуванні відповіді). Якщо вам знадобитися синхронне виконання запиту, то встановіть параметр в false. Кроссдоменние запити і запити типу "jsonp" не можуть виконуватися в синхронному режимі.

Майте на увазі, що виконання запитів в синхронному режимі може привести до блокування сторінки, поки запит не буде повністю виконаний.

Це поле містить функцію, яка буде викликана безпосередньо перед відправкою ajax-запиту на сервер. Така функція може бути корисна для модифікації jqXHR-об'єкта (в ранніх версіях бібліотеки (до 1.5), замість jqXHR використовується XMLHttpRequest). Наприклад, можна змінити / вказати потрібні заголовки (headers) і.т.д. Об'єкт-jqXHR буде переданий в функцію першим аргументом. Другим аргументом передаються настройки запиту.

У цьому полі можна вказати додаткові заголовки запиту (header). Ці зміни будуть введені до виклику beforeSend, в якій можуть бути зроблені остаточні правки заголовків.

При перекладі цієї настройки в true, запит буде виконаний зі статусом "успішно", лише в разі, якщо відповідь від сервера відрізняється від попереднього відповіддю. jQuery перевіряє цей факт звертаючись до заголовку Last-Modified. Починаючи з jQuery-1.4, крім Last-Modified перевіряється і "etag" (обидва вони надаються сервером і необхідні для оповіщення браузера про те, що запитувані дані з сервера не змінені з попереднього запиту).

Дозволяє встановити статус джерела сторінки локальним (як якщо б це відбувалося по протоколу file), навіть якщо jQuery розпізнав його інакше. Бібліотека вирішує, що сторінка запущена локально в разі наступних протоколів: file, * -extension, і widget.

Рекомендується встановлювати значення параметра isLocal глобально - за допомогою функциии $ .ajaxSetup (), а не в налаштуваннях окремих ajax-запитів.

Визначає ім'я параметра, який додається в url при jsonp-запиті (за замовчуванням, використовується "callback" - "httр: //siteName.ru? Callback \u003d ...").

Починаючи з jQuery-1.5, вказавши в цьому параметрі false, ви уникнете додавання в url додаткового параметра. В цьому випадку необхідно явно встановити значення властивості jsonpCallback. Наприклад так: (jsonp: false, jsonpCallback: "callbackName").

Визначає ім'я функції, яка буде викликана при відповіді сервера на jsonp-запит. За замовчуванням, jQuery генерує довільну назву цієї функції, що є кращим варіантом, що спрощує роботу бібліотеки. Один з причин, при якому варто вказувати власну функцію обробки jsonp-запиту, є поліпшення кешування GET-запитів.

Починаючи з jQuery-1.5, ви можете вказати функцію в цьому параметрі, для того, щоб обробити відповідь сервера самостійно. В цьому випадку, зазначена функція повинна повертати отримані від сервера дані (у зазначеній функції вони будуть доступні в першому параметрі).

За замовчуванням, всі передані на сервер дані, попередньо перетворюються в рядок (url-формату: fName1 \u003d value1 & fName2 \u003d value2 & ...) відповідну "application / x-www-form-urlencoded". Якщо вам необхідно відправити дані, які не можна піддавати подібній обробці (наприклад документ-DOM), то слід відключити опцію processData.

Цей параметр використовується для кроссдоменних ajax-запитів типу GET, dataType при цьому може бути або "jsonp", або "script". Визначає кодування, в якій буде виконаний кроссдоменний запит. Це необхідно, в разі, якщо сервер на чужому домені використовує систему кодування, відмінну від кодуванні на сервері рідного домену.

(Цей параметр з'явилася в jQuery-1.5) набір пар, в якому кодами виконання запиту зіставляються функції, які при цьому буде викликані. Наприклад, для коду 404 (сторінки не існують) можна зробити висновок повідомлення на екран:

$ .Ajax ((statusCode: (404: function () (alert ( "Сторінка не знайдена") ; } } } ) ;

Функції, що реагують на коди успішного виконання запиту будуть отримувати ті ж аргументи, що і функції-обробники вдалого виконання запиту (зазначені в параметрі success), а функції, що спрацьовують на коди помилок, будуть такими ж, як і у error-функцій.

Функція, яка буде викликана в разі вдалого завершення запиту до сервера. Їй будуть передані три параметра: дані, надіслані сервером і вже пройшли попередню обробку (яка відмінна для різних dataType). Другий параметр - рядок зі статусом виконання. Третій параметр містить об'єкт jqXHR (в більш ранніх версіях бібліотеки (до 1.5), замість jqXHR використовується XMLHttpRequest). Починаючи з jQuery-1.5, замість однієї функції, цей параметр може приймати масив функцій.

Час очікування відповіді від сервера. Здається в в мілісекундах. Якщо цей час буде перевищено, запит буде завершено з помилкою і станеться подія error (див. Опис вище), яке буде мати статус "timeout".

Час відраховується з моменту виклику функції $ .ajax. Може трапитися так, що в цей момент буде запущено кілька інших запитів і браузер відкладе виконання поточного запиту. В цьому випадку timeout може завершитися, хоча фактично, запит навіть ще не був запущений.

В jQuery-1.4 і молодше, при завершенні часу очікування, об'єкт XMLHttpRequest перейде в стан помилки і доступ до його полях може викликати виключення. В Firefox 3.0 + запити типу script і JSONP НЕ будуть перервані при перевищенні часу очікування. Вони будуть завершені навіть після того як цей час закінчиться.

Функція, яка надасть об'єкт XMLHttpRequest. За замовчуванням, для браузерів IE цим об'єктом є ActiveXObject, а в інших випадках це XMLHttpRequest. За допомогою цього параметра ви можете впровадити власну версію цього об'єкта.

(Цей параметр з'явилася в jQuery-1.5.1) Набір пар (ім'я: значення) для зміни / додавання значень відповідних полів об'єкта XMLHttpRequest. Наприклад, можна встановити його властивість withCredentials в true, при виконанні кроссдоменного запиту:

$ .Ajax ((url: a_cross_domain_url, xhrFields: (withCredentials: true)));

В jQuery-1.5 властивість withCredentials не підтримує нативним XMLHttpRequest і при кроссдоменном запиті це поле буде проігноровано. У всіх наступних версіях бібліотеки, це виправлено.

обробники подій

Налаштування beforeSend, error, dataFilter, success і complete (їх опис є в попередньому розділі) дозволяють встановити обробники подій, які відбуваються в певні моменти виконання кожного ajax-запиту.

beforeSend відбувається безпосередньо перед відправкою запиту на сервер. error відбувається в разі невдалого виконання запиту. dataFilter відбувається в момент прибуття даних з сервера. Дозволяє обробити "сирі" дані, надіслані сервером. success відбувається в разі вдалого завершення запиту. complete відбувається в разі будь-якого завершення запиту.

приклад простого використання. Виведемо повідомлення при вдалому виконанні запиту:

$ .Ajax ((url: "ajax / test.html", success: function () (alert ( "Load was performed.");)));

Починаючи з jQuery-1.5, метод $ .ajax () повертає об'єкт jqXHR, який крім іншого реалізує інтерфейс deferred, що дозволяє задавати додаткові обробники виконання. Крім стандартних для об'єкта deferred методів .done (), .fail () і.then (), за допомогою яких можна встановлювати обробники, в jqXHR реалізовани.success (), .error () і.complete (). Це зроблено для відповідності звичним назв методів, за допомогою яких встановлюються обробники виконання ajax-запитів. Однак починаючи з jQuery-1.8 ці три методи стануть небажаними для використання.

Для деяких типів запитів, таких як jsonp або кроссдоменних GET-запитів, що не передбачається використання об'єктів XMLHttpRequest. В цьому випадку, що передаються в обробники XMLHttpRequest і textStatus міститимуть значення undefined.

Усередині оброблювачів, змінна this буде містити значення параметра context. У разі, якщо він не був заданий, this буде містити об'єкт налаштувань.

параметр dataType

Функція $ .ajax () дізнається про тип надісланих сервером даних від самого сервера (засобами MIME). Крім цього, існує можливість особисто вказати (уточнити), як слід інтерпретувати ці дані. Це робиться за допомогою параметра dataType. Можливі значення цього параметра:

"Xml" - отриманий xml-документ буде доступний в текстовому вигляді. З ним можна працювати стандартними засобами jQuery (також як і з документом html). "Html" - отриманий html буде доступний в текстовому вигляді. Якщо він містить скрипти в тегах



У цьому прикладі з використанням jQuery методу .load () ми при натисканні на елемент

Сподобалася стаття? поділіться їй