Package: asyncio-simple-mq
SimpleMQSimpleMQ- простойMQ, написанный на питоне с использованием стримов из библиотекиasyncioдля написания сервера
и сокетов из библиотекиsocketдля реализации постоянного соединения сервер-подписчикHello worldWorkerRoutingHello worldPublisher(Издатель) - отсылает сообщение на серверStream(Стрим) - Некоторое отдельное пространство для хранения сообщений. Сообщения могут храниться только внутри стримов, но при этом на сервере может быть создано множество стримов. Издатель отправляет сообщение в стрим. Подписчик в свою очередь пытается получить сообщение из стрима.Follower(Подписчик) - устанавливает постоянноеTCPсоединение с сервером и ждет пока в стриме появятся сообщенияВhello worldпримере мы рассмотрим только одного издателя, один стрим и одного подписчикаСоздаем конфигурационный файлsettings.yamlс данными для запуска сервера, удобнее его положить рядом сserver.py, который мы создадим далееhost:port:# примерhost:localhostport:9090Создаем server.py# server.pyfromsimplemq.server.serverimportrun_serverif__name__=='__main__':run_server(settings_yml_filepath='settings.yaml')Создаемfollower.py# follower.pyfromsimplemq.followerimportFollowerfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)bind=Bind(route_string='hello_queue')follower=Follower(connection=connection,bind=bind)cursor=connection.cursor()withcursor.session():cursor.create_stream('hello_queue')defhandle_message(message):fromtimeimportsleepsleep(5)print('message_body: ',message.message_body)print('message_handled')withfollower.session():formessageinfollower.get_messages():handle_message(message=message)follower.ack_message(message)Создаемpublisher.py# publisher.pyfromsimplemq.publisher.publisherimportSocketBasedPublisherfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)MESSAGE_BODY='hello world'bind=Bind(route_string='hello_queue')cursor=connection.cursor()withcursor.session():cursor.create_stream('hello_queue')publisher=SocketBasedPublisher(connection=connection,bind=bind)publisher.send_message(message_body=MESSAGE_BODY)Тут появляются новые определения:Bind- являются роутером, то есть - в случае с издателем говорит в очередь с каким наименованием (значение аргументаrouting_string) отправлять сообщение, в случае подписчика - из какой очереди ждать сообщения.Cursor- объект, который не участвует в обмене сообщениями, он нужен для утилити функций: создать стрим; посмотреть все стримы, которые есть на сервере и тд.Демонстрация работы:В этом примере мы сначала подключили подписчика, а только потом начали отправлять сообщения в стрим издателем. Мы также можем сначала заполнить стрим сообщениями и только потом включить подписчика, в таком случае он сразу получить сообщения, а не будет их ждать.Потверждение сообщенийКогда подписчик читает сообщение со стрима оно удаляется со стрима и переходит вPEL(Pending entry list). ПослеACKсообщения от подписчика об обработке этого сообщения оно исчезает и от туда.
Есть 2 способа потвердить обработку сообщения:самому, вызвав у инстанса Follower методack_messagewithfollower.session():formessageinfollower.get_messages():handle_message(message=message)follower.ack_message(message)передать True в аргумент auto_ack. В таком случае на сервер придет потверждение об обработке сообщения еще до вызоваhandle_messagewithfollower.session():formessageinfollower.get_messages(auto_ack=True):handle_message(message=message)WorkerПодписчики являются воркерами. При чтении сообщения подписчиком это сообщение пропадает из стрима, поэтому второй подписчик, который подписан на тот же стрим его не прочитает. Запустим двух подписчиков, только один из них будет обрабывать сообщение 100 секунд, а второй 2. Пока первый подписчик обработывает сообщение (в нашем случае просто спит) второй подписчик может читать входящие сообщения.# first_follower.pyfromsimplemq.followerimportFollowerfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)bind=Bind(route_string='hello_queue')follower=Follower(connection=connection,bind=bind)cursor=connection.cursor()withcursor.session():cursor.create_stream('hello_queue')defhandle_message(message):fromtimeimportsleepsleep(100)print('message_body: ',message.message_body)print('message_handled')withfollower.session():formessageinfollower.get_messages():handle_message(message=message)follower.ack_message(message)# second_follower.pyfromsimplemq.followerimportFollowerfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)bind=Bind(route_string='hello_queue')follower=Follower(connection=connection,bind=bind)cursor=connection.cursor()withcursor.session():cursor.create_stream('hello_queue')defhandle_message(message):fromtimeimportsleepsleep(2)print('message_body: ',message.message_body)print('message_handled')withfollower.session():formessageinfollower.get_messages():handle_message(message=message)follower.ack_message(message)Демонстрация работы:RoutingУBindесть два режима роутинга:Direct- режим по умолчанию, который мы использовали раньше. Издатель отправляет сообщения в стрим с наименованием равным значению аргументаrouting_key. Подписчик читает сообщения из стрима с наименованием равным значению аргументаrouting_key.REGEX_BASED- Издатель отправляет сообщения во все стримы, название которых удовлетворяет regex выражению изrouting_key. Подписчик читает сообщения из всех стримов, название которых содержит удовлетворяет regex выражению изrouting_key.RegexBased Routing cо стороны издателя# publisherfromsimplemq.publisher.publisherimportSocketBasedPublisherfromsimplemq.bindimportBind,BindTypesfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)message_body='hello world'bind=Bind(route_string='.+_world',bind_type=BindTypes.REGEX_BASED)cursor=connection.cursor()withcursor.session():cursor.create_stream('hello_queue')publisher=SocketBasedPublisher(connection=connection,bind=bind)publisher.send_message(message_body=message_body)# first_follower.pyfromsimplemq.followerimportFollowerfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)bind=Bind(route_string='new_world')follower=Follower(connection=connection,bind=bind)cursor=connection.cursor()withcursor.session():cursor.create_stream('new_world')defhandle_message(message):fromtimeimportsleepsleep(3)print('message_body: ',message.message_body)print('message_handled')withfollower.session():formessageinfollower.get_messages():handle_message(message=message)follower.ack_message(message)# second_follower.pyfromsimplemq.followerimportFollowerfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)bind=Bind(route_string='old_world')follower=Follower(connection=connection,bind=bind)cursor=connection.cursor()withcursor.session():cursor.create_stream('old_world')defhandle_message(message):fromtimeimportsleepsleep(3)print('message_body: ',message.message_body)print('message_handled')withfollower.session():formessageinfollower.get_messages():handle_message(message=message)follower.ack_message(message)Демонстрация работы:RegexBased Routing cо стороны подписчика# first_publisher.pyfromsimplemq.publisher.publisherimportSocketBasedPublisherfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)message_body='hello world'bind=Bind(route_string='old_world')cursor=connection.cursor()withcursor.session():cursor.create_stream('old_world')publisher=SocketBasedPublisher(connection=connection,bind=bind)publisher.send_message(message_body=message_body)# second_publisher.pyfromsimplemq.publisher.publisherimportSocketBasedPublisherfromsimplemq.bindimportBindfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)message_body='hello world'bind=Bind(route_string='new_world')cursor=connection.cursor()withcursor.session():cursor.create_stream('new_world')publisher=SocketBasedPublisher(connection=connection,bind=bind)publisher.send_message(message_body=message_body)# followerfromsimplemq.followerimportFollowerfromsimplemq.bindimportBind,BindTypesfromsimplemq.connectionimportConnection,ConnectionConfigconnection_config=ConnectionConfig(host='localhost',port=9090,)connection=Connection(connection_config=connection_config)bind=Bind(route_string='.+world',bind_type=BindTypes.REGEX_BASED)follower=Follower(connection=connection,bind=bind)defhandle_message(message):fromtimeimportsleepsleep(3)print('message_body: ',message.message_body)print('message_handled')withfollower.session():formessageinfollower.get_messages():handle_message(message=message)follower.ack_message(message)Демонстрация работы: