介绍性
哈Ha!我的名字叫鲍里斯(Boris),在这项工作中,我将与您分享我在设计和实施群发邮件服务方面的经验,这是我也正在实施的,用于提醒学生注意老师的综合系统(以下也称为Ada)的一部分。
地狱
然后出于以下原因需要取消教育过程中的中断次数:
- 老师不想分享个人的联系方式;
- 学生们也是如此-他们只是没有太多选择。
- 由于母校的特殊要求,许多老师被迫或宁愿使用无法访问Internet的移动设备。
- 如果您通过小组负责人发送消息,则“电话损坏”的影响以及“哦,我忘了:(”。
它运行大约如此:
- 教师通过以下一种可用的通信渠道:SMS,Telegram,SPA应用程序-向Ada发送消息文本和收件人列表;
- Ada通过各种通信渠道将收到的消息广播给所有感兴趣的*学生。
*对服务的访问是自愿申请的。
假设
- 用户总数不超过一万;
- 学生与教师/内政部成员(教务长,卫生中心,军事登记处等)的比例应保持在10:1的水平;
- : « », « ))0» ..
- ;
- , , ;
- ;
- : - - , , .
这项工作包括五个部分:绪论,准备,概念,主题和最后部分。
如果您熟悉Pub / Sub模式的Redis解释以及事件的机制,LUA脚本和过时的键的处理,则可以安全地跳过准备部分,此外,非常希望至少对软件的微服务架构有所了解。
在主题部分,回顾了Python中的代码,但是我相信有足够的信息可以让您在任何东西上写类似的东西。
预备
很粗糙很抽象〜5分钟
Redis — [BSD 3-clause] , «-» ().
, .
, -, .
( ).
, , , LUA 5.1.
, .
, -, .
( ).
, , , LUA 5.1.
详细信息和第一手〜15分钟
- Pub/Sub — Redis. , fire&forget , ,
PUBLISH
,SUBSCRIBE
-; - Redis Keyspace Notifications. ;
- EXPIRE — Redis. «How Redis expires keys»;
- Redis 6.0 Default Configuration File. . 939:948 (The default effort of the expire cycle…);
- EVAL — Redis.
EVAL
EVALSHA
, «Atomicity of scripts», «Global variables protection» «Available libraries»,cjson
; - Redis Lua Scripts Debugger. , . — ;
- . , .
概念性
天真的方法
您可以想到的最明显的解决方案是:多种传递方法(
send_vk
,send_telegram
等等)和一个使用所需参数调用它们的处理程序。
可扩展性问题
如果要添加新的交付方式,则将不得不修改现有代码,这是软件平台的局限性。
稳定性问题
其中一种方法已损坏=整个服务已损坏。
应用问题
在交互方面,不同通信通道的API彼此之间存在显着差异。例如,VKontakte支持大量邮件,但每个呼叫最多不超过数百个用户。电报不存在,但每秒允许更多呼叫。
VK API仅通过HTTP起作用;Telegram有一个HTTP网关,但是它比MTProto不稳定,并且文档也很少。
这些差异有很多:最大消息长度
random_id
,解释和错误处理等。等等
怎么处理呢?
决定在组织级别将消息入队和发送过程(以下称为快递)分开,这样前者甚至不会怀疑后者的存在,反之亦然,Redis将充当它们之间的连接。
不清楚?点餐!
同时,您正在等待-让我向您介绍我对这一崇高举止的理解,从设计开始,到快递员身后关闭的门结束。
- 您单击黄色的大“订单”按钮;
- Yandex.Food找到快递员,将选择的商品告知餐厅,然后将订单号返回给您,以减少期望值的不确定性;
- 烹饪完成后,餐厅会更新订单状态,并将食物交给快递员;
- 快递员依次将食物交给您,然后将订单标记为已完成。
祝您好胃口!
回到设计
前面段落中给出的模型可能不完全符合实际,但是正是她构成了开发的解决方案的基础。
与订单号关联的数据称为历史记录,它使您可以随时回答以下问题:
- 谁发的;
- 他寄了什么;
- 来自哪里;
- 给谁;
- 谁得到的以及如何获得的。
历史记录与订单一起创建为两个独立的Redis密钥,并通过后缀链接:
suffix={ }:{UNIX- }
=history:{suffix}
=delivery:{suffix}
该命令确定了快递员何时一次查看历史记录,以便在分配完成后更改对“谁接收到它以及如何接收”的问题的答案。
快递人员的“视觉”通过订阅
DEL
表单中的事件键来工作delivery:*
。
当交货时刻到来时,Redis删除订单密钥,然后快递员开始处理它。
由于有多个快递公司,因此在历史更改阶段竞争的可能性很高。
您可以通过原子定义相应的操作来避免这种情况-在Redis中,这是通过LUA脚本完成的。
下一章将详细讨论实现细节。现在重要的是要对整个解决方案有一个清晰的认识,这可以通过下图来帮助。
追踪状态
客户端可以通过历史记录密钥来跟踪传递状态,该历史记录密钥是在消息排队之前由正在开发的服务的单独API方法生成的(就像订单号是一开始由Yandex.Eda生成的一样)。
生成密钥后,将在其上挂有带超时的跟踪器(可选,也可以通过其他方法),该跟踪器将监视信使(
SET
事件)的历史记录更改次数。仅现在,该消息正在排队。
如果快递员在他的域-通讯渠道中找不到收件人联系人,则他会
SET
通过命令触发人为事件PUBLISH
,从而表明他“还可以”,不需要再等待了。
当您拥有RabbitMQ和Celery时,为什么会惹上Redis中的事件
至少有五个客观原因:
通知系统(包含)以一组微服务的形式实现。为了方便起见,接口,用于初始化数据层的方法,错误文本以及一些重复性逻辑块已移至库中
core
,该库又依赖于:gino
(asyncio wrapper SQLAlchemy
)aioredis
和aiohttp
。
您可以在代码中看到不同的实体,例如
User
,Contact
或Allegiance
。它们之间的连接如下图所示,在扰流板下面有一个简短的描述。
关于实体〜3分钟
— .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
生成历史记录密钥
交付/处理程序/ history_key /获取-GitHub
队列
交付/处理程序/队列/放置-GitHub
注意:
- 评论171:174;
- Redis [164:179]的所有操作都包装在一个事务中。
快递人员的视线[94:117]
核心/交付-GitHub
快递人员更新历史
core / redis_lua-GitHub
指令[48:60]不会将空列表转换为字典(
[] -> {}
),因为包括CPython在内的大多数编程语言对它们的解释都不同于LUA。
ISS:允许区分数组和对象以进行正确的空对象序列化-GitHub
追踪器
交付/处理程序/跟踪/发布-GitHub-实现。
connect / telegram / handlers / select-GitHub [101:134]-用户界面中的示例用法。
快递员
来自
task_stream
(@Sight Couriers)的任何交货均在单独的异步协程中处理。
处理API的时间限制的一般策略如下:我们不计算RPS(每秒请求数),但是我们按类型正确/响应/响应响应
http.TooManyRequests
。
如果接口除了实现全局(针对应用程序)自定义时间限制外,还按队列顺序对其进行处理,即 首先,我们会尽可能地发送给所有人,然后我们开始等待,即使时间不长。
电报
courier / telegram-GitHub
如前所述,Telegram的MTProto接口在稳定性和文档大小方面胜过其HTTP同类产品。为了与之交互,我们将使用现成的解决方案,即LonamiWebs / Telethon。
与...联系
courier / vk-GitHub
VKontakte API通过将标识符列表传递给messages.send方法(不超过一百个)来支持大量邮件,并且还允许您
messages.send
在一次执行中最多“粘”二十五个,每次调用可给我们2500条消息。
好奇的事实
API,
execute
, .
决赛
在这项工作中,提出了一种组织多通道质量预警系统的方法。最终的解决方案可以满足大多数相关方的要求(邮件服务的关键要求),并且还可以扩展。
主要缺点是失火的Pub / Sub效果,即 如果某个快递员生病时必须删除订单密钥,则在相应的域中没有人会收到任何东西,但是,这将反映在历史记录中。