前言
当多个系统之间通过Kafka来解耦时,在系统设计初期,基本的要求都是相似的,只不过是消费消息时的业务逻辑可能不同。
本文以业务系统和邮件系统解耦作为示例。业务系统需要发送邮件时,不在自身服务器上发送邮件, 不通过RPC的方式调用邮件系统,而是通过将发送邮件需求以消息的形式发送到Kafka, 邮件系统通过从Kafka中消费消息来发送邮件。
当前系统现状
在我们当前系统中,存在N台服务器,每台服务器滚动部署相同的代码。也就是说,每台服务器都会执行发送邮件的逻辑,且发送邮件都是异步发送,使用统一的线程池以做到资源复用。
为什么要解耦?
经典回答
又不是不能用,动它干嘛?把人力资源投入到这个上面来,最后究竟能带来多大收益,真的值得吗?为了用消息队列而用?诸如此类的问题
要回答此问题,就要知道,当前系统中存在哪些问题,会对发送邮件业务产生影响
- 我们发送邮件虽然使用单独的线程池,但是系统中还有N个其他业务线程池也跑在同一个服务器上。当服务器遇到性能问题时,不稳定时,我们会将该服务器下线且关闭Tomcat进程,现在下线方式非常暴力,直接kill掉Tomcat进程。这时,如果负责发送邮件的线程池或其等待队列中有大量的未发送邮件,这些邮件任务将直接丢失
- 我们不仅有主系统,还有N个其他系统,也有发送邮件的需求。我们不想维护多份同样的代码
- 我们需要集成邮件发送服务商的webhook功能,当发出去的邮件很多时,此功能会对系统发起大量的webhook请求,处理这些webhook请求也会占用一些服务器的资源。我们认为这些webhook请求固然重要,但不应该影响到系统的稳定性以对核心业务产生影响
通过这样的解耦有以下几点好处
- 由于业务系统不直接调用邮件系统,所以不会将压力给到邮件系统,避免两个系统因大量的请求响应而出现的系统不稳定问题
- Kafka的性能是非常出色的,所以对于业务系统的大量写入应该可以hold住。当然我司每天写入的数据量和头部互联网公司的每天的海量数据没法比
- 邮件系统可以根据实际情况来从容的消费消息,也有利于我们可以针对邮件系统服务器进行硬件、软件、JVM等精细化的调优
邮件系统和业务系统的分界线在哪?
最大的一个问题,哪些是邮件系统应该做的?哪些是业务系统应该做的?并均衡考虑整体服务的性能、压力等因素
以订单完成之后需要发邮件场景通知顾客为例,有两种实现方式
- 业务系统只将orderId送到Kafka中,邮件系统根据orderId,查询数据库,组装数据,渲染模版,调用邮件服务商API发送邮件
- 业务系统根据orderId组装数据,将组装好的数据+需要渲染的模板,序列化为字符串,发送到Kafka中,邮件系统从Kafka中拿到字符串,反序列化,将数据和模板一起渲染,调用邮件服务商API发送邮件
我们决定使用第2种方案。理由是:如果采用第1种方案,会有以下几个弊端
- 如果只有一个orderId,我们并没有使用Redis集中式缓存Order相关Bean,邮件系统还要去数据库查一次甚至几次才能组装成完整数据,这么做反而会增加了数据库的压力
- 当接入的场景越来越多时,每一次接入一个场景就要调试一次,邮件系统的代码不光要考虑不同场景的代码的维护性,还要考虑滚动部署时的代码兼容性问题
使用第2中方案的好处是:
- 邮件服务器定义了统一的邮件格式,不和任何业务耦合,只要发过来的消息符合统一的邮件格式即可,邮件系统无需对各个场景进行适配
- 邮件服务器,拿到消息,反序列之后即可发送,无需查询数据库去组装数据,数据库没压力
- 我们大量使用JVM级别的缓存Ehcache,对于order这种场景,其发邮件的所需要的数据Bean,大多数来自于其上一步的购买流程或者相关缓存,所以该Bean本身不需要去数据库再次查询、组装,直接使用即可。将该Bean发送到Kafka时会转成Map,彻底和业务解耦,邮件系统最终拿到的消息只是一个Map,不依赖于业务系统的具体Class
整体设计
- 用户触发发送邮件逻辑并请求业务系统
- 业务系统异步向Kafka发送消息
- 消息系统从Kafka获取消息,执行消费逻辑
- 消息系统调用邮件发送服务商API发送邮件
- 用户打开邮件,点击内容链接等会上报给邮件发送服务商
- 邮件发送服务商通过Webhook将用户行为回传给消息系统
- 消息系统将用户行为写入数据库以便产品团队分析
- 业务系统的消息被邮件系统消费之后还需要通知业务系统
- 确保消息不丢失
- 确保消息不重复消费
- Kafka的高可用、稳定性不在本文讨论范围之内
时序图
下列时序图使用mermaid绘制
如果看不清楚,使用点此查看上述时序图高清图片中文版或点此查看上述时序图高清图片英文版
时序图解释
- 1 ~ 19步骤为核心流程
- 20~ 29步骤为可选流程, 如果存在回调消息,则还要回调
上线初期时新老系统切换问题
当新的消息服务器上线后,发送邮件都要逐渐切换到邮件系统。如果Kafka或邮件系统发生问题,如何回退到原来的逻辑呢,保证邮件正常发送。
大概思路是: 在发消息之前进行判断,当检测到无法将消息发送至Kafka时,自动将发送邮件的逻辑切换至旧的逻辑即可。等Kafak或邮件系统恢复后,再修改下状态(该状态在Redis维护),发消息即可切换到新的逻辑。
最后
整体大的细节就如时序图所示,后续将在具体编码中进行更详细的说明。对于和邮件发送服务商的交互以及Webhook的细节不在后续文章中展开,有需要的同学可自行了解。 下一篇博文专注于业务系统生产者端逻辑的实现