php redis php fpm
Обработка сессий PHP при помощи сервера Redis в Ubuntu 14.04
Redis – это открытая нереляционное хранилище данных и кэша типа «ключ-значение». Также Redis является сервером преобразования структур данных, предназначенным для расширенной поддержки нескольких типов данных (хэша, списков, множеств и битовых массивов и т.п.). Кроме того, Redis поддерживает кластеризацию, потому его часто используют в высокопроизводительных и масштабируемых средах.
Данное руководство поможет установить и настроить сервер Redis для обработки PHP-сессий приложения в системе Ubuntu 14.04.
Обработчик сессий отвечает за хранение и извлечение данных, сохраненных в сессиях; по умолчанию PHP использует для этого файлы. Вместе с балансировщиком нагрузки обработчик сессий может быть использован для создания масштабируемой среды PHP, в которой все ноды приложения смогут подключаться к ведущему серверу для обмена данными.
Требования
Для выполнения данного руководства использовалось два сервера.
Примечание: Из соображений безопасности и производительности важно, чтобы оба сервера находились в одном центре обработки данных с включенной частной сетью.
Итак, для работы понадобится:
1: Установка Redis
Сначала нужно установить сервер Redis.
Для этого подойдёт пакет из репозитория PPA.
Важно! Используйте наиболее актуальную версию Redis.
Из соображений безопасности рекомендуется работать только с надёжными и проверенными источниками репозиториев PPA.
Чтобы добавить репозиторий PPA, введите:
sudo add-apt-repository ppa:chris-lea/redis-server
Для подтверждения нажмите Enter.
Обновите кэш пакетного менеджера:
sudo apt-get update
sudo apt-get install redis-server
Чтобы убедиться, что установка прошла успешно, введите команду:
Команда создаст подключение к Redis с локального хоста на порт 6379. Если установка прошла успешно, команда вернёт:
2: Настройка Redis для поддержки внешних соединений
По умолчанию Redis поддерживает подключения только с локального хоста. Это значит, что Redis доступен только с того сервера, на котором он установлен. Эту настройку нужно изменить.
Для начала нужно узнать IP-адрес частной сети сервера Redis.
Примечание: Следующие действия нужно выполнить на сервере redis.
Запустите команду ifconfig, чтобы получить сведения о сетевых интерфейсах.
Команда вернёт примерно следующий вывод:
eth0 Link encap:Ethernet HWaddr 04:01:63:7e:a4:01
inet addr:188.166.77.33 Bcast:188.166.127.255 Mask:255.255.192.0
inet6 addr: fe80::601:63ff:fe7e:a401/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:3497 errors:0 dropped:0 overruns:0 frame:0
TX packets:3554 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:4895060 (4.8 MB) TX bytes:619070 (619.0 KB)
eth1 Link encap:Ethernet HWaddr 04:01:63:7e:a4:02
inet addr:10.133.14.9 Bcast:10.133.255.255 Mask:255.255.0.0
inet6 addr: fe80::601:63ff:fe7e:a402/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:648 (648.0 B) TX bytes:578 (578.0 B)
Найдите inet_addr для интерфейса eth1; в данном случае это 10.133.14.9. Это и есть IP-адрес сети, который необходим для настройки подключения сервера web к серверу redis.
При помощи редактора откройте файл /etc/redis/redis.conf и найдите строку bind. После localhost вставьте в неё полученный IP-адрес частной сети.
sudo vim /etc/redis/redis.conf
bind localhost 10.133.14.9
Примечание: Вместо localhost строка может содержать 127.0.0.1.
Перезапустите сервис Redis, чтобы обновить настройки.
sudo service redis-server restart
После этого любой сервер из этой частной сети сможет подключиться к этому серверу Redis.
3: Пароль для сервера Redis
Чтобы повысить уровень безопасности сервера Redis, нужно защитить его данные паролем. Отредактируйте конфигурационный файл /etc/redis/redis.conf.
sudo vim /etc/redis/redis.conf
Раскомментируйте строку requirepass и установите надёжный пароль.
Перезапустите сервис Redis:
sudo service redis-server restart
4: Тестирование подключения и авторизации Redis
Чтобы убедиться в том, что все настройки работают должным образом, подключитесь к Redis с сервера redis:
Параметр host использовать необязательно, но в этой команде он присутствует для того, чтобы подтвердить, что сервис Redis может принимать соединения, направленные на частный сетевой интерфейс.
Если вы установили пароль, сервер не откроет вам доступа к данным; он вернёт ошибку AUTH:
keys *
(error) NOAUTH Authentication required.
Чтобы пройти аутентификацию, нужно запустить команду AUTH и добавить в неё установленный ранее пароль:
Если пароль правильный, команда вернёт:
Теперь снова запустите:
На экране появится примерно такой вывод:
Это значит, что на сервере Redis пока что нет никаких данных; это нормальное поведение сервера, поскольку сервер web пока что не может использовать его в качестве обработчика сессий.
Оставьте эту сессию SSH открытой и подключитесь к redis-cli. После настройки сервера web нужно будет вернуться в командную строку redis-cli, чтобы убедиться в том, что Redis может хранить данные.
5: Установка расширений Redis на сервер web
Теперь нужно перейти на сервер web и установить расширения Redis, чтобы PHP мог подключаться к серверу Redis.
Обновите кэш пакетного менеджера:
sudo apt-get update
Затем установите пакет php5-redis
sudo apt-get install php5-redis
Теперь веб-сервер может подключаться к серверу redis.
6: Настройка обработки сессий
Оставайтесь на сервере web. Откройте файл php.ini, в котором можно определить стандартный обработчик сессий PHP. Место хранения файла зависит от используемого программного стека. В стеке LAMP в Ubuntu 14.04 этот файл обычно находится в /etc/php5/apache2/php.ini. В стеке LEMP в системе Ubuntu 14.04 файл, как правило, хранится в /etc/php5/fpm/php.ini.
Если вы не знаете точного пути файла php.ini, его можно быстро узнать при помощи функции phpinfo(). Просто поместите в файл info.php следующий код:
Затем откройте скрипт в браузере и найдите строку Loaded Configuration File. В ней указано местонахождение нужного файла.
Примечание: После этого рекомендуется удалить файл info.php, поскольку он открывает доступ к конфиденциальным данным о сервере.
Откройте файл php.ini и найдите строку session.save_handler; по умолчанию она содержит значение files. Измените стандартное значение строки, указав redis.
sudo vim /etc/php5/apache2/php.ini
sudo vim /etc/php5/fpm/php.ini
Строка должна выглядеть так: session.save_handler = redis
Теперь вы должны найти строку session.save_path. Раскомментируйте ее и измените значение, указав строку подключения Redis в таком формате:
tcp://IPADDRESS:PORT?auth=REDISPASSWORD. Строка должна выглядеть так:
session.save_path = «tcp://10.133.14.9:6379?auth=yourverycomplexpasswordhere»
В параметре auth нужно указать пароль сервера Redis.
Сохраните и закройте файл, а затем перезапустите сервер php.
sudo service apache2 restart
sudo service php5-fpm restart
7: Тестирование обработки сессий
Чтобы убедиться в том, что Redis успешно обрабатывает сессии PHP, создайте PHP-скрипт или приложение, хранящее информацию в сессиях. В данном руководстве используется простой скрипт-счётчик: он увеличивает номер при каждой перезагрузке страницы.
На сервере web создайте файл test.php и поместите его в каталог document root.
sudo vim /usr/share/nginx/html/test.php
Примечание: Если ваш каталог document root находится в другом месте, откорректируйте эту команду.
Добавьте в файл такой код:
Чтобы получить доступ к скрипту в браузере, откройте ссылку:
Номер на странице должен увеличиваться после каждого обновления страницы.
Это значит, что теперь данные сессий хранятся в Redis. Чтобы убедиться в этом, вернитесь в сессию SSH на сервере redis, в которой осталось подключение к Redis при помощи redis-cli. Извлеките данные при помощи команды keys *:
На экране появится такой вывод:
Это значит, что информация сессии хранится на сервере Redis. Аналогичным образом к серверу Redis можно подключить дополнительные веб-серверы.
Обработка сессий PHP при помощи сервера Redis в Ubuntu 16.04
Redis – это открытое нереляционное хранилище данных и кэша типа «ключ-значение». Также Redis является сервером преобразования структур данных, предназначенным для расширенной поддержки нескольких типов данных (хэша, списков, множеств, битовых массивов и т.п.). Кроме того, Redis поддерживает кластеризацию, потому его часто используют в высокопроизводительных и масштабируемых средах.
Данное руководство поможет установить и настроить сервер Redis для обработки PHP-сессий приложения в системе Ubuntu 16.04.
Обработчик сессий отвечает за хранение и извлечение данных, сохраненных в сессиях; по умолчанию PHP использует для этого файлы. Вместе с балансировщиком нагрузки обработчик сессий может быть использован для создания масштабируемой среды PHP, в которой все ноды приложения смогут подключаться к ведущему серверу для обмена данными.
Требования
Для выполнения данного руководства использовались два сервера.
Итак, для работы понадобится:
1: Установка сервера и клиента Redis
Сначала нужно установить Redis на оба сервера: на сервер redis установите пакет Redis server, а на сервер web – PHP-расширение Redis для обработки сессий и клиент командной строки.
Установка Redis Server
Подключитесь к машине redis и установите необходимый пакет. Для этого подойдёт репозиторий PPA. Используйте наиболее актуальную версию Redis.
Примечание: Используйте PPA только из надёжных и безопасных источников.
sudo apt-add-repository ppa:chris-lea/redis-server
Чтобы подтвердить, нажмите Enter.
Обновите индекс пакетов и установите сервер Redis:
sudo apt-get update
sudo apt-get install redis-server
Чтобы убедиться, что установка прошла успешно, введите команду:
Команда создаст подключение к Redis с локального хоста на порт 6379. Если установка прошла успешно, команда вернёт:
Установка клиента и PHP-расширения Redis
Перейдите на сервер web. Здесь нужно установить клиент командной строки (для проверки подключения) и PHP-расширения Redis (для хранения данных).
Обновите индекс пакетов и установите программы:
sudo apt-get update
sudo apt-get install redis-tools php-redis
Теперь у вас есть доступ к инструменту redis-cli, но пока что сервер не принимает внешних соединений.
2: Настройка Redis для поддержки внешних соединений
По умолчанию Redis поддерживает подключения только с локального хоста. Это значит, что Redis доступен только с того сервера, на котором он установлен. Эту настройку нужно изменить.
По умолчанию Redis не предоставляет шифрования, поскольку предполагает развёртывание в изолированной сети заведомо безопасных серверов. Следовательно, чтобы все внешние соединения были безопасными, оба сервера должны находиться в изолированной сети, либо же вы должны защитить трафик между серверами при помощи специальных инструментов шифрования.
Изолированная сеть
Если серверы находятся в изолированной сети, достаточно просто отредактировать конфигурационный файл Redis и добавить IP-адрес сети.
Перейдите на сервер redis, создайте резервную копию конфигурационного файла Redis и откройте его:
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.bak
sudo nano /etc/redis/redis.conf
Найдите строку bind и вставьте IP-адрес сети, в которой находится сервер Redis.
bind 127.0.0.1 isolated_IP_address
Сохраните и закройте файл. Перезапустите сервис:
sudo systemctl restart redis-server.service
Откройте доступ к порту в брандмауэре.
sudo ufw allow 6379
Теперь сервер Redis может принимать внешние соединения в изолированной сети.
Инструменты шифрования
Если сервер развёрнут вне изолированной среды, обязательно нужно защитить трафик между серверами. Существует несколько способов сделать это:
Выберите один из предложенных методов и зашифруйте трафик между серверами.
3: Пароль для сервера Redis
Чтобы повысить уровень безопасности сервера Redis, нужно защитить его данные паролем. Отредактируйте конфигурационный файл /etc/redis/redis.conf.
sudo nano /etc/redis/redis.conf
Раскомментируйте строку requirepass и установите надёжный пароль. Этот пароль необходим для аутентификации Redis. Выберите сложный фразовый пароль, чтобы предотвратить brute force атаки.
Сохраните и закройте файл. Перезапустите сервис Redis:
sudo systemctl restart redis-server.service
4: Тестирование подключения и авторизации Redis
Чтобы убедиться в том, что все настройки работают должным образом, подключитесь к Redis с сервера web.
По умолчанию сервис Redis прослушивает порт 6379 на локальном интерфейсе. Однако данные могут отличаться в зависимости от настроек безопасности трафика Redis (раздел 2). С помощью клиента redis-cli и флага –h можно указать IP-адрес, с помощью флага –p – задать порт удалённого сервиса, к которому нужно подключиться.
Примечание: Если вы используете нестандартные параметры, замените 127.0.0.1 и порт 6379 соответствующими данными.
Итак, если серверы работают в изолированной среде, вам нужно использовать IP-адрес этой сети и стандартный порт 6379 (его можно не указывать).
Если вы используете stunnel или spiped, укажите номер локального порта, который подключен к удаленному сервису Redis:
Если вы используете PeerVPN, укажите IP-адрес VPN:
Общий синтаксис команды выглядит так:
Теперь вы можете подключиться к удалённому сервису Redis с сервера web.
Если вы определили пароль, вы не получите доступа к данным, а на экране появится ошибка AUTH:
keys *
(error) NOAUTH Authentication required.
Чтобы пройти аутентификацию, нужно запустить команду AUTH и указать пароль, указанный в файле /etc/redis/redis.conf.
Если всё сработало, на экране появится вывод:
Теперь снова запустите:
На экране появится примерно такой вывод:
Это значит, что на сервере Redis пока что нет никаких данных; это нормальное поведение сервера, поскольку сервер web пока что не может использовать его в качестве обработчика сессий.
Вернитесь в командную строку:
5: Настройка обработки сессий
Оставайтесь на сервере web. Откройте файл php.ini, в котором можно определить стандартный обработчик сессий PHP. Место хранения файла зависит от используемого программного стека.
В стеке LAMP в Ubuntu 16.04 этот файл обычно находится в /etc/php/7.0/apache2/php.ini. В стеке LEMP в системе Ubuntu 16.04 файл, как правило, хранится в /etc/php/7.0/fpm/php.ini.
Поиск файла php.ini (опционально)
Примечание: Если вы уже нашли файл php.ini, пропустите этот раздел.
Если вы не знаете точного пути файла php.ini, его можно быстро узнать при помощи функции phpinfo(). Просто откройте в каталоге /var/www/html файл info.php:
sudo nano /var/www/html/info.php
и поместите в него следующий код:
Затем откройте скрипт в браузере и найдите строку Loaded Configuration File. В ней указано местонахождение нужного файла.
Примечание: После этого рекомендуется удалить файл info.php, поскольку он открывает доступ к конфиденциальным данным о сервере.
Изменение настроек
Откройте файл php.ini.
Стек LAMP:
sudo nano /etc/php/7.0/apache2/php.ini
Стек LEMP:
sudo nano /etc/php/7.0/fpm/php.ini
Примечание: Если путь к файлу php.ini отличается, укажите правильный путь.
В файле php.ini найдите строку session.save_handler. По умолчанию она имеет значение files. Замените значение на redis, чтобы использовать PHP-расширение Redis вместо файлов.
Теперь вы должны найти строку session.save_path. Раскомментируйте ее и измените значение, указав строку подключения Redis в таком формате:
Опять же, правильные значения зависят от выбранного вами метода защиты данных. используйте те же данные, что и в разделе 4. Например, если вы используете stunnel или spiped, строка session.save_path будет иметь такой вид:
Сохраните и закройте файл.
Затем перезапустите сервер PHP.
В LAMP:
sudo systemctl restart apache2
В LEMP:
sudo systemctl restart php7.0-fpm
6: Тестирование обработки сессий
Чтобы убедиться в том, что Redis успешно обрабатывает сессии PHP, создайте PHP-скрипт или приложение, хранящее информацию в сессиях. В данном руководстве используется простой скрипт-счётчик: он увеличивает номер при каждой перезагрузке страницы.
На сервере web создайте файл test.php и поместите его в каталог document root.
sudo nano /var/www/html/test.php
Примечание: Если ваш каталог document root находится в другом месте, откорректируйте эту команду.
Добавьте в файл такой код:
Сохраните и закройте файл.
Чтобы получить доступ к скрипту в браузере, откройте ссылку:
Номер на странице должен увеличиваться после каждого обновления страницы.
Это значит, что теперь данные сессий хранятся в Redis. Чтобы убедиться в этом, откройте новую сессию с помощью redis-cli на сервере redis. При подключении к локальному интерфейсу IP указывать не нужно:
Укажите пароль Redis:
AUTH yourverycomplexpasswordhere
OK
Извлеките данные при помощи команды keys *:
Вы увидите новую запись PHP-сессии:
Если вы запросите значение ключа, вы увидите текущее значение счётчика:
get PHPREDIS_SESSION:2ofnvhhr6gdvp88u0c4e7kb800
«count|i:6;»
Это значит, что информация сессии хранится на сервере Redis. Аналогичным образом к серверу Redis можно подключить дополнительные веб-серверы.
Установка Redis + Redis PHP + phpRedisAdmin на боевом сервере за 15 минут
Всем уже давно известно, что самые лучшие бинарные файлы — это те, которые были скомпилированы именно на этом компьютере. И, когда вопрос заходит о производительности, то компилирование компонентов на этом компьютере даст свое преимущество в скорости и стабильности.
В данной статье речь пойдет о том, как подготовить Redis, phpredis (С модуль для php) и phpRedisAdmin для работы на боевом сервере.
Собираем Redis
Для того, чтоб собрать Редис, нам потребуются сами исходники. Получить их можно с github. Поэтому
https://github.com/antirez/redis/zipball/2.4.4 загружаем сразу нужный тег.
Теперь у нас есть все исходники Redis 2.4.4, осталось их скомпилировать.
Все очень просто
Первой командой мы собрали Redis, второй его проверили на работоспособность.
Make test будет проходить долго (около 2х минут). После теста должна вернуться такая строка «\o/ All tests passed without errors!»
Redis собран, осталось зачистить мусор и унести редис в нужный каталог.
Унесем весь скомпилированный Redis в папку /usr/bin/redis. А чтоб запускать Redis-server, в /usr/bin создадим shell файл redis-server.
Уносим файлы Redis в /usr/bin/redis
Мы все еще остаемся в папке antirez-redis-04bba69, где лежат исходники
Мы скопировали все файлы от Redis и удалили папку с исходниками, она нам уже не нужна.
Будьте осторожны с последней командой! sudo для нее не нужен, папка и так в наших владениях.
Теперь нам нужно создать файл для запуска redis-server.
Для этого файла нужно дать право на исполнение.
Осталось взять redis.conf
Redis.conf загружен, осталось его настроить.
Заменяем в файле конфигурации
Запуск!
Если все было сделано правильно, то запускаем сервер
По идее вам должно показать приглашение от Redis
Trying 127.0.1.1…
Connected to localhost.
Escape character is ‘^]’.
Если вы его получили, значит все отлично, Redis работает.
Тестируем сервер
Теперь мы общаемся с Redis по сокету.
Авторизируемся:
Устанавливаем тестовое значение
Если все прошло успешно, значит сервер работает, и с ним мы заканчиваем. Устанавливаем команду на автозагрузку при старте системы и все.
Redis + PHP5. Компилируем модуль для PHP
Тут тоже все очень просто, так что можно просто следовать командам.
Тут думаю комментарии не нужны.
Теперь собираем redis.so расширение.
Теперь в папке modules появился файл redis.so, он-то нам и нужен.
меняем на имя папки, которое выглядит примерно так: 20090626.
Далее дать информацию php о Redis.so
поэтому
и вписываем в него extension=redis.so
По желанию заменяем apache2 на cli, cgi и так далее, в зависимости от того, как у Вас установлен php, и как вы его хотите использовать вместе с Redis.
Теперь рестартим apache2 и в тестовом php файле пишем:
Если ошибка, что класс Redis не существует, не вылетела, то все отлично.
Проведем benchmark тест непосредственно в php.
У меня появилась информация, что 80000 запросов были обработаны за 2.6 секунд.
На этом с php мы закончили. Далее phpRedisAdmin.
Установка phpRedisAdmin
Установка phpRedisAdmin абсолютно не нужна, но для визуализации данных не помешает.
Качаем из git саму админку.
Мы создаем папку redisadmin в /var/www. Добавляем права на файлы, потому что изначально у меня permission denied на redisadmin.
Не забудьте настроить веб-сервер, чтоб сайт можно было открыть уже из сети.
Но использовать redis admin не безопасно на продакшене, поэтому его нужно как ни как закрыть для всех посторонних глаз паролем.
Нужно расскоментировать строку 11 и установить пароль к серверу.
Далее нужно расскоментировать блок с 36 по 46 строку, назначив админский пароль, который будет запрашиваться при входе в phpRedisAdmin
PROFIT!
За 15 минут мы смогли поднять Redis сервер, настроить его для работы с PHP и поднять phpRedisAdmin.
Redis на практических примерах
Redis — достаточно популярный инструмент, который из коробки поддерживает большое количество различных типов данных и методов работы с ними. Во многих проектах он используется в качестве кэшируещего слоя, но его возможности намного шире. Мы в ManyChat очень любим Redis и активно используем его в нашем продукте для решения огромного количества задач. Про некоторые интересные кейсы использования этой in-memory key-value базы данных я расскажу на примерах. Надеюсь, вам они будут полезны, и вы сможете применить что-то в своих проектах.
Рассмотрим следующие кейсы:
Кэширование данных
Давайте начнем с самого простого, один из самых популярных кейсов использования Redis — кэширование данных. Будет полезно для тех, кто не работал с Redis. Для тех, кто уже давно пользуется этим инструментом — можно смело переходить к следующему кейсу. Для того, чтобы снизить нагрузку на БД, иметь возможность запрашивать часто используемые данные максимально быстро, используется кэш. Redis — это in-memory хранилище, то есть данные хранятся в оперативной памяти. Ещё это key-value хранилище, где доступ к данным по их ключу имеет сложность O(1) — поэтому данные мы получаем очень быстро.
Получение данных из хранилища выглядит следующим образом:
Но для того, чтобы данные из кэша получить, их нужно сначала туда положить. Простой пример записи:
Таким образом, мы запишем данные в Redis и сможем их считать по тому же самому ключу в любой нужный нам момент. Но если мы будем все время писать в Redis, данные в нем будут занимать все больше и больше места в оперативной памяти. Нам нужно удалять нерелевантные данные, контролировать это вручную достаточно проблематично, поэтому пускай redis занимается этим самостоятельно. Добавим к нашему ключу TTL (время жизни ключа):
По истечении времени ttl (в секундах) данные по этому ключу будут автоматически удалены.
Как говорят, в программировании существует две самых сложных вещи: придумывание названий переменных и инвалидация кэша. Для того, чтобы принудительно удалить значение из Redis по ключу, достаточно выполнить следующую команду:
Также редис позволяет получить массив значений по списку ключей:
И соответственно массовое удаление данных по массиву ключей:
Очереди
Используя имеющиеся в Redis структуры данных, мы можем запросто реализовать стандартные очереди FIFO или LIFO. Для этого используем структуру List и методы по работе с ней. Работа с очередями состоит из двух основных действий: отправить задачу в очередь, и взять задачу из очереди. Отправлять задачи в очередь мы можем из любой части системы. Получением задачи из очереди и ее обработкой обычно занимается выделенный процесс, который называется консьюмером (consumer).
Итак, для того, чтобы отправить нашу задачу в очередь, нам достаточно использовать следующий метод:
Со стороны консьюмера нам необходимо обеспечить получение задач из очереди, это реализуется простой командой чтения из листа. Для реализации FIFO очереди мы используем чтение с обратной записи стороны (в нашем случае мы писали через RPUSH), то есть читать будем через LPOP:
Для реализации LIFO очереди, нам нужно будет читать лист с той же стороны, с которой мы в него пишем, то есть через RPOP.
Тем самым мы вычитываем по одному сообщению из очереди. В случае если листа не существует (он пустой), то мы получим NULL. Каркас консьюмера мог бы выглядеть так:
Для того, чтобы получить информацию о глубине очереди (сколько значений хранится в нашем листе), можем воспользоваться следующей командой:
Мы рассмотрели базовую реализацию простых очередей, но Redis позволяет строить более сложные очереди. Например, мы хотим знать о времени последней активности наших пользователей на сайте. Нам не важно знать это с точностью вплоть до секунды, приемлемая погрешность — 3 минуты. Мы можем обновлять поле last_visit пользователя при каждом запросе на наш бэкенд от этого пользователя. Но если этих пользователей большое количество в онлайне — 10,000 или 100,000? А если у нас еще и SPA, которое отправляет много асинхронных запросов? Если на каждый такой запрос обновлять поле в бд, мы получим большое количество тупых запросов к нашей БД. Эту задачу можно решать разными способами, один из вариантов — это сделать некую отложенную очередь, в рамках которой мы будем схлопывать одинаковые задачи в одну в определенном промежутке времени. Здесь на помощь нам придет такая структура, как Sorted SET. Это взвешенное множество, каждый элемент которого имеет свой вес (score). А что если в качестве score мы будем использовать timestamp добавления элемента в этот sorted set? Тогда мы сможем организовать очередь, в которой можно будет откладывать некоторые события на определенное время. Для этого используем следующую функцию:
Теперь возникает вопрос о том, как читать эту очередь. Если методы LPOP и RPOP для листа читают значение и удаляют его из листа атомарно (это значит, что одно и тоже значение не может быть взято несколькими консьюмерами), то sorted set такого метода из коробки не имеет. Мы можем сделать чтение и удаление элемента только двумя последовательными командами. Но мы можем выполнить эти команды атомарно, используя простой LUA скрипт!
В этом LUA скрипте мы пытаемся получить первое значение с весом в диапазоне от 0 до текущего timestamp в переменную val с помощью команды ZRANGEBYSCORE, если нам удалось получить это значение, то удаляем его из sorted set командой ZREM и возвращаем само значение val. Все эти операции выполняются атомарно. Таким образом мы можем вычитывать нашу очередь в консьюмере, аналогично с примером очереди построенной на структуре LIST.
Я рассказал про несколько базовых паттернов очередей, реализованных в нашей системе. На текущий момент у нас в продакшене существуют более сложные механизмы построения очередей — линейных, составных, шардированных. При этом Redis позволяет все это делать при помощи смекалки и готовых круто работающих структур из коробки, без сложного программирования.
Блокировки (Mutex)
Mutex (блокировка) — это механизм синхронизации доступа к shared ресурсу нескольких процессов, тем самым гарантируя, что только один процесс будет взаимодействовать с этим ресурсом в единицу времени. Этот механизм часто применяется в биллинге и других системах, где важно соблюдать потоковую безопасность (thread safety).
Для реализации mutex на базе Redis прекрасно подойдет стандартный метод SET с дополнительными параметрами:
где параметрами для установки mutex являются:
Чаще всего, когда мы пишем код, который пытается работать с shared ресурсом, который заблокирован, мы хотим дождаться его разблокировки и продолжить работу с этим ресурсом. Для этого можем реализовать простой метод для ожидания освободившегося ресурса:
Мы разобрались как ставить блокировку, теперь нам нужно научиться ее снимать. Для того, чтобы гарантировать снятие блокировки тем процессом, который ее установил, нам понадобится перед удалением значения из хранилища Redis, сверить хранимый хэш по этому ключу. Для того, чтобы сделать это атомарно, воспользуемся LUA скриптом:
Rate limiter
Достаточно частая задача, когда мы хотим ограничить количество запросов к нашему апи. Например на один API endpoint от одного аккаунта мы хотим принимать не более 100 запросов в минуту. Эта задача легко решается с помощью нашего любимого Redis:
Таким простым методом мы можем лимитировать количество запросов к нашему API, базовый каркас нашего контроллера мог бы выглядеть следующим образом:
Pub/sub
Pub/sub — интересный механизм, который позволяет, с одной стороны, подписаться на канал и получать сообщения из него, с другой стороны — отправлять в этот канал сообщение, которое будет получено всеми подписчиками. Наверное у многих, кто работал с вебсокетами, возникла аналогия с этим механизмом, они действительно очень похожи. Механизм pub/sub не гарантирует доставки сообщений, он не гарантирует консистентности, поэтому не стоит его использовать в системах, для которых важны эти критерии. Однако рассмотрим этот механизм на практическом примере. Предположим, что у нас есть большое количество демонизированных команд, которыми мы хотим централизованно управлять. При инициализации нашей команды мы подписываемся на канал, через который будем получать сообщения с инструкциями. С другой стороны у нас есть управляющий скрипт, который отправляет сообщения с инструкциям в указанный канал. К сожалению, стандартный PHP работает в одном блокирующем потоке; для того, чтобы реализовать задуманное, используем ReactPHP и реализованный под него клиент Redis.
Отправка сообщения в канал — более простое действие, мы можем сделать это абсолютно из любого места системы одной командой:
В результате такой отправки сообщения в канал, все клиенты, которые подписаны на данный канал, получат это сообщение.