Redis pub/sub на php

02.08.2021
Redis это не только key-value (noSQL) хранилище, но и хороший инструмент реализующий паттерн pub/sub (еще ссылка):
  • publisher - издатель посылает сообщения в опредленный канал
  • subscriber - подписчик подписывается на определенный канал/каналы и обрабатывает поступившие сообщения
Издатель и подписчик необязательно должны находиться на одном сервере, если только это не запрещено конфигурацией, когда Redis не доступен наружу

Для php есть библиотека реализующая работу с Redis.

Устройство

Для посылки/приема сообщения не надо осуществлять процедуру создания канала, главное согласовать название канала между издателем и подписчиком

Для отправки сообщения в канал достаточно:

•••
php
$oRedis->publish('channel-name', 'text of message');

Для обработки сообщений из канала/каналов, необходимо подписаться и установить обработчик:

•••
php
$oRedis = new Redis(); $oRedis->connect(REDIS_IP, REDIS_PORT); $oRedis->auth(REDIS_PASSWORD); $oRedis->subscribe(['channel-name'], 'Handler');

В приведенном выше коде происходит подписка на один канал channel-name с установкой обработчика Handler:

•••
php
function Handler($oRedis, $sChannel, $sMessage) { ... }

Обработчик принимат 3 параметра (применение венгеркой нотации ненавязчиво определяет сигнатуру прямо в имени, что делает дополнительное описание аргументов бессмысленным, но для тех кто не знаком с такой нотацией ...):

  • oRedis - объект класса Redis
  • sChannel - название канала
  • sMessage - сообщение канала
Название канала передается потому что на самом деле можно подписаться на несколько каналов и установить один обработчик:
•••
php
$oRedis->subscribe(['channel-name1', 'channel-name2'], 'Handler'); function Handler($oRedis, $sChannel, $sMessage) { switch($sChannel) { case 'channel-name1': ... break; case 'channel-name2': ... break; default: break; } }

После вызова метода subscribe скрипт зависнет в ожидании сообщений на канале. Но не на долго ...

Прерывание соединения

Ожидание сообщений на канале в Redis может прерваться в связи с обрывом связи ... точнее php сам оборвет соединение:

•••
plaintext
Fatal error: Uncaught RedisException: Connection timed out in /home/byurrer/redis-pubsub/pubsub.php:44 Stack trace: #0 /home/byurrer/redis-pubsub/pubsub.php(44): Redis->connect('127.0.0.1', 6379) #1 /home/byurrer/rredis-pubsub/pubsub.php(70): Subscribe() #2 {main} thrown in /home/byurrer/redis-pubsub/pubsub.php on line 44

Директива тайм-аута соединений сокетов default_socket_timeout по умолчанию имеет значение 60 секунд. Именно поэтому соединение с Redis оборвется через 60 секунд.

Чтобы этого не произошло необходимо изменить ее значение на -1. Самый простой способ это добавить в самое начало php скрипта:

•••
php
ini_set("default_socket_timeout", -1);

Обработку разрыва соединения можно обработать обернув метод subscribe в блок try/catch:

•••
php
try { $oRedis->subscribe(['channel-name1', 'channel-name2'], 'Handler'); } catch(Exception $e) { ... }

Но можно обернуть весь блок инициализации Redis:

•••
php
try { $oRedis = new Redis(); $oRedis->connect(REDIS_IP, REDIS_PORT); $oRedis->auth(REDIS_PASSWORD); } catch(Exception $e) { ... }

В catch блок можно написать уведомление в любимый мессенджер либо попытаться восстановить соединение.

Но если скрипт все-таки упал (возможно внутренние ошибки в скрипте), или просто хочется сделать автозапуск скрипта с подпиской на канал, то можно сделать из php скрипта демона.