尝试做一个分布式通知系统设计

787 阅读5分钟

面试被问到过这个问题,以下为自己收集资料之后做的一个不成熟的整理。

更多可能是自用面试向、思路向,感觉其实就是一个常见角度的参考,实际面试的时候,很难想得很完整,但是能想起来尽量多的点也是好的。也欢迎路过大神指正问题

确定需求

一句话概括需求:分布式、可扩展、高可用的一个用于推送通知的系统,输入侧支持灵活调用,输出侧支持接入扩展多种通知方式。

可能需要确定的问题:

(问题的来源即对于产品需求的概括,实现过程中你不太清楚的地方。不一定要一次全部想清楚想很多,很多点可能确实出于经验暂时想不到,但是一定有这个过程;)

  1. 预期用怎样的方式创建和推送通知?
  2. 通知有实时性要求吗?(精准度)
  3. 通知只是纯文字的吗?有类型要求吗?
  4. 使用方是谁?内部使用还是toc使用?通知发送量大概怎样?(并发量)

整体分析

整体沟通下来,我们得出一种大致可能的设计:

MVP:

image.png

其实主要就是说明大概服务的一个交互流程

难点分析

然后我们再提炼出来其中的难点来做讨论

非功能难点

可用性和扩展性:

  • 现有架构容易单点故障
  • 不方便进行去做扩展(这里并不是说,mvp下不方便做加机器和负载。主要是耦合高,职责不单一的原因。像是一次扩展量大,然后一次修改变更量大等;所以我们引入mq来做解耦,这样就更方便做扩展)
  • 基本解决,我们用mq解耦,分为消息接受代理和实际推送worker两层

性能瓶颈:

  • 通知这个场景无论是收集调用还是推送通知,也是典型的「资源密集场景」。就算是考虑整点要给用户发促销通知这种场景,也不能把这个推送间隔整体设置得太长了。一次推送的量肯定很大。
  • 水平扩展和缓存能帮我们解决一部分问题,除此之外基本就是异步和限流了。

功能难点

实时和定时(这是没找到参考,目前不太清楚的点)

  • 这个目前预期的设计,是实时做基本的直接流程处理。感觉其实很难实现了,目前有mq流程,整个处理间隔很难做到实时,应该会有至少10s的延迟。
  • 定时的话预期实现方案在这种架构下可以用dlq去做,目前看上去是不太适合用时间轮之类的别的调度算法。

防止消息丢失

  • 消息预期肯定是要做持久化的,不仅是防止丢失,也是有归档价值的。当然也要考虑预期对老数据做冷备迁移。
  • 同时我们可以通过写日志的方式来进一步做存档
  • 主要通过重试机制来确保

防止消息重复

  • 基本方案可以用消息id(一个类型,对一个用户的唯一id)做乐观锁(这种场景下重复概率不会很大),消息到worker的时候我们再做id校验。如果出现过,则丢弃消息。

架构设计

解决以上难点后,得出来的 high-level 架构:

image.png

  • 服务层面,整体上分为通知模块和实际推送通知的worker两大模块,这样方便按照不同压力进行扩展。通知服务整体上是一个服务代理层,在交互有压力的时候自己这边可以去做扩展。分worker还有的好处是,worker可以专门为了具体的通知类型做实现,职责单一。
  • 缓存和持久化也
  • lb和mq来串起来从左到右的整个流程

细节:

  • 通知服务主要职责是负责跟db交互,处理封装要调用的消息,最后对消息推送进行调度。
  • worker执行过程中可以跟mq实现一个重试的交互逻辑
  • worker会记录日志
  • 缓存主要用在代理发送服务这边,获取旧通知、推送需要的token等信息

总结

整体上来说,要设计一个主要支持分布式、扩展性好的消息通知服务大概就是这些内容,这里是以一个完成基础功能的视角去考虑。实际如果需求没那么完整,可以做得更简单。如果需求更大,则需要考虑更多细节。

主要难点在于架构的扩展性的实现,这里主要是通过mq解耦,功能分得更细来解决的。 其他也要考虑发送失败、重复发送等基本功能要点。

确保基本框架简洁,其他暂时没考虑的点:

  • 通知服务鉴权:认证后优化有权限的客户端才能调用我们的通知服务
  • 调用速率限制:进一步也得防止滥用和dos攻击

以上是我不成熟的一个系统设计间接,有不对的地方还请指出。


References & Acknowledgements