Redis上的多渠道批量邮件

介绍性



哈Ha!我的名字叫鲍里斯(Boris),在这项工作中,我将与您分享我在设计和实施群发邮件服务方面的经验,这是我也正在实施的,用于提醒学生注意老师的综合系统(以下也称为Ada)的一部分。







地狱



然后出于以下原因需要取消教育过程中的中断次数:



  1. 老师不想分享个人的联系方式;
  2. 学生们也是如此-他们只是没有太多选择。
  3. 由于母校的特殊要求,许多老师被迫或宁愿使用无法访问Internet的移动设备。
  4. 如果您通过小组负责人发送消息,则“电话损坏”的影响以及“哦,我忘了:(”。


它运行大约如此



  1. 教师通过以下一种可用的通信渠道:SMS,Telegram,SPA应用程序-向Ada发送消息文本和收件人列表;
  2. Ada通过各种通信渠道将收到的消息广播给所有感兴趣的*学生。


*对服务的访问是自愿申请的。



假设



  1. 用户总数不超过一万;
  2. 学生与教师/内政部成员(教务长,卫生中心,军事登记处等)的比例应保持在10:1的水平;
  3. : « », « ))0» ..




  1. ;
  2. , , ;
  3. ;
  4. : - - , , .


这项工作包括五个部分:绪论,准备,概念,主题和最后部分。



如果您熟悉Pub / Sub模式的Redis解释以及事件的机制,LUA脚本和过时的键的处理,则可以安全地跳过准备部分,此外,非常希望至少对软件的微服务架构有所了解。



在主题部分,回顾了Python中的代码,但是我相信有足够的信息可以让您在任何东西上写类似的东西。



预备



很粗糙很抽象〜5分钟
Redis — [BSD 3-clause] , «-» ().



, .



, -, .



( ).



, , , LUA 5.1.



详细信息和第一手〜15分钟
  1. Pub/Sub — Redis. , fire&forget , , PUBLISH, SUBSCRIBE -;
  2. Redis Keyspace Notifications. ;
  3. EXPIRE — Redis. «How Redis expires keys»;
  4. Redis 6.0 Default Configuration File. . 939:948 (The default effort of the expire cycle…);
  5. EVAL — Redis. EVAL EVALSHA, «Atomicity of scripts», «Global variables protection» «Available libraries», cjson;
  6. Redis Lua Scripts Debugger. , . — ;
  7. . , .


概念性



天真的方法



您可以想到的最明显的解决方案是:多种传递方法(send_vksend_telegram等等)和一个使用所需参数调用它们的处理程序。



可扩展性问题



如果要添加新的交付方式,则将不得不修改现有代码,这是软件平台的局限性。



稳定性问题



其中一种方法已损坏=整个服务已损坏。



应用问题



在交互方面,不同通信通道的API彼此之间存在显着差异。例如,VKontakte支持大量邮件,但每个呼叫最多不超过数百个用户。电报不存在,但每秒允许更多呼叫。



VK API仅通过HTTP起作用;Telegram有一个HTTP网关,但是它比MTProto不稳定,并且文档也很少。



这些差异有很多:最大消息长度random_id,解释和错误处理等。等等



怎么处理呢?



决定在组织级别将消息入队和发送过程(以下称为快递)分开,这样前者甚至不会怀疑后者的存在,反之亦然,Redis将充当它们之间的连接。



不清楚?点餐!



同时,您正在等待-让我向您介绍我对这一崇高举止的理解,从设计开始,到快递员身后关闭的门结束。







  1. 您单击黄色的大“订单”按钮;
  2. Yandex.Food找到快递员,将选择的商品告知餐厅,然后将订单号返回给您,以减少期望值的不确定性;
  3. 烹饪完成后,餐厅会更新订单状态,并将食物交给快递员;
  4. 快递员依次将食物交给您,然后将订单标记为已完成。


祝您好胃口!



回到设计



前面段落中给出的模型可能不完全符合实际,但是正是她构成了开发的解决方案的基础。



与订单号关联的数据称为历史记录,它使您可以随时回答以下问题:



  1. 谁发的;
  2. 他寄了什么;
  3. 来自哪里;
  4. 给谁;
  5. 谁得到的以及如何获得的。


历史记录与订单一起创建为两个独立的Redis密钥,并通过后缀链接:



suffix={ }:{UNIX-  }
=history:{suffix}
=delivery:{suffix}


该命令确定快递员何时一次查看历史记录,以便在分配完成后更改对“谁接收到它以及如何接收”的问题的答案。



快递人员的“视觉”通过订阅DEL表单中的事件键来工作delivery:*



当交货时刻到来时,Redis删除订单密钥,然后快递员开始处理它。



由于有多个快递公司,因此在历史更改阶段竞争的可能性很高。







您可以通过原子定义相应的操作来避免这种情况-在Redis中,这是通过LUA脚本完成的。



下一章将详细讨论实现细节。现在重要的是要对整个解决方案有一个清晰的认识,这可以通过下图来帮助。







追踪状态


客户端可以通过历史记录密钥来跟踪传递状态,该历史记录密钥是在消息排队之前正在开发的服务的单独API方法生成的(就像订单号是一开始由Yandex.Eda生成的一样)。



生成密钥后,将在其上挂有带超时的跟踪器(可选,也可以通过其他方法),该跟踪器将监视信使(SET事件)的历史记录更改次数仅现在,该消息正在排队。







如果快递员在他的域-通讯渠道中找不到收件人联系人,则他会SET通过命令触发人为事件PUBLISH,从而表明他“还可以”,不需要再等待了。



当您拥有RabbitMQ和Celery时,为什么会惹上Redis中的事件



至少有五个客观原因:



  1. Redis , RabbitMQ/Celery — ;
  2. Redis , , , IPC;
  3. Redis’a SQL- ;
  4. . , API-, ;
  5. Celery asyncio, asyncio .




通知系统(包含)以一组微服务的形式实现。为了方便起见,接口,用于初始化数据层的方法,错误文本以及一些重复性逻辑块已移至库中core,该又依赖于:gino(asyncio wrapper SQLAlchemyaioredisaiohttp



您可以在代码中看到不同的实体,例如UserContactAllegiance它们之间的连接如下图所示,在扰流板下面有一个简短的描述。





关于实体〜3分钟
— .



: , , . ., .



, : , Telegram, . .



[allegiance].



[supergroup].



[ownership] .



生成历史记录密钥



交付/处理程序/ history_key /获取-GitHub



队列



交付/处理程序/队列/放置-GitHub



注意:



  1. 评论171:174;
  2. 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效果,即 如果某个快递员生病时必须删除订单密钥,则在相应的域中没有人会收到任何东西,但是,这将反映在历史记录中。



All Articles