面试被问到过这个问题,以下为自己收集资料之后做的一个不成熟的整理。
更多可能是自用面试向、思路向,感觉其实就是一个常见角度的参考,实际面试的时候,很难想得很完整,但是能想起来尽量多的点也是好的。也欢迎路过大神指正问题
确定需求
一句话概括需求:分布式、可扩展、高可用的一个用于推送通知的系统,输入侧支持灵活调用,输出侧支持接入扩展多种通知方式。
可能需要确定的问题:
(问题的来源即对于产品需求的概括,实现过程中你不太清楚的地方。不一定要一次全部想清楚想很多,很多点可能确实出于经验暂时想不到,但是一定有这个过程;)
- 预期用怎样的方式创建和推送通知?
- 通知有实时性要求吗?(精准度)
- 通知只是纯文字的吗?有类型要求吗?
- 使用方是谁?内部使用还是toc使用?通知发送量大概怎样?(并发量)
整体分析
整体沟通下来,我们得出一种大致可能的设计:
MVP:
其实主要就是说明大概服务的一个交互流程
难点分析
然后我们再提炼出来其中的难点来做讨论
非功能难点
可用性和扩展性:
- 现有架构容易单点故障
- 不方便进行去做扩展(这里并不是说,mvp下不方便做加机器和负载。主要是耦合高,职责不单一的原因。像是一次扩展量大,然后一次修改变更量大等;所以我们引入mq来做解耦,这样就更方便做扩展)
- 基本解决,我们用mq解耦,分为消息接受代理和实际推送worker两层
性能瓶颈:
- 通知这个场景无论是收集调用还是推送通知,也是典型的「资源密集场景」。就算是考虑整点要给用户发促销通知这种场景,也不能把这个推送间隔整体设置得太长了。一次推送的量肯定很大。
- 水平扩展和缓存能帮我们解决一部分问题,除此之外基本就是异步和限流了。
功能难点
实时和定时(这是没找到参考,目前不太清楚的点)
- 这个目前预期的设计,是实时做基本的直接流程处理。感觉其实很难实现了,目前有mq流程,整个处理间隔很难做到实时,应该会有至少10s的延迟。
- 定时的话预期实现方案在这种架构下可以用dlq去做,目前看上去是不太适合用时间轮之类的别的调度算法。
防止消息丢失
- 消息预期肯定是要做持久化的,不仅是防止丢失,也是有归档价值的。当然也要考虑预期对老数据做冷备迁移。
- 同时我们可以通过写日志的方式来进一步做存档
- 主要通过重试机制来确保
防止消息重复
- 基本方案可以用消息id(一个类型,对一个用户的唯一id)做乐观锁(这种场景下重复概率不会很大),消息到worker的时候我们再做id校验。如果出现过,则丢弃消息。
架构设计
解决以上难点后,得出来的 high-level 架构:
- 服务层面,整体上分为通知模块和实际推送通知的worker两大模块,这样方便按照不同压力进行扩展。通知服务整体上是一个服务代理层,在交互有压力的时候自己这边可以去做扩展。分worker还有的好处是,worker可以专门为了具体的通知类型做实现,职责单一。
- 缓存和持久化也
- lb和mq来串起来从左到右的整个流程
细节:
- 通知服务主要职责是负责跟db交互,处理封装要调用的消息,最后对消息推送进行调度。
- worker执行过程中可以跟mq实现一个重试的交互逻辑
- worker会记录日志
- 缓存主要用在代理发送服务这边,获取旧通知、推送需要的token等信息
总结
整体上来说,要设计一个主要支持分布式、扩展性好的消息通知服务大概就是这些内容,这里是以一个完成基础功能的视角去考虑。实际如果需求没那么完整,可以做得更简单。如果需求更大,则需要考虑更多细节。
主要难点在于架构的扩展性的实现,这里主要是通过mq解耦,功能分得更细来解决的。 其他也要考虑发送失败、重复发送等基本功能要点。
确保基本框架简洁,其他暂时没考虑的点:
- 通知服务鉴权:认证后优化有权限的客户端才能调用我们的通知服务
- 调用速率限制:进一步也得防止滥用和dos攻击
以上是我不成熟的一个系统设计间接,有不对的地方还请指出。
References & Acknowledgements