当前位置: 首页 > article >正文

如何去设计一个消息队列

以下相关业务纯属假设,如有雷同,实属巧合,下面的设计案例仅仅描述一些关键的设计考虑点和决策点

1 需求

假想一个创业公司,叫胖批微博,胖批微博的业务发展很快,系统越来越多,系统之间的协调效率很低,如下:

  • 用户发了一条微博后,微博子系统需要通知审核子系统进行审核,然后通知统计子系统进行统计,再通知广告子系统进行广告预测,接着通知消息子系统进行消息推送、一条微博就会有十几个通知,现在都是通用接口直接调用的,每通知一个新系统,就要设计接口,进行测试,效率低,且定位问题麻烦
  • 用户等级达到VIP后,等级子系统要通知福利子系统进行奖品发放,通知客服子系统安排专属服务人员

新来的架构师在梳理这些问题时,结合自己的经验敏锐发现了问题在于架构上各业务子系统强耦合,正好消息队列又能完成子系统之间解耦,所以引入消息队列系统来解决这个问题很有必要

其他背景信息

  • 中间件团队规模不大,约6人
  • 中间件团队熟悉java语言,但有一个新同事C/C++很牛
  • 开发平台是linux,数据库是MySQL
  • 目前整个业务系统是单机房部署,没有双机房

2 设计流程

2.1 识别复杂度

  • 是否需要高性能:假设胖批微博的每天有1000万条微博
    • 那么微博子系统一天会产生1000万条数据,其他子系统总共会有1亿次的消息
    • 但是这是消息总量,高性能更多的是看秒级,如TPS,QPS。计算下来,一天内的平均每秒写入115,每秒读取消息是1150,再考虑请求数并不会是完全平均,所以设计目标是峰值计算,一般取平均3倍,TPS:345,QPS:3450
    • 这个量级不算很高,kafka都是万级,但是也会会增长,流量也会变得更多,为了考虑未来一两年,可以按照4倍峰值计算,最终的TPS:1380,QPS:13800,可以看到每秒1万多次的读取,其实也达到了高性能的要求的,但是使用kafka能应对
  • 是否需要高可用:可以假想如果消息丢失了怎么办
    • 首先当前流量很大,每天1000万条微博,所以如果对于审核子系统,没有审核到某个微博,可能导致触犯法律,或者社会性危害
    • 如果用户达到VIP后,却发现别人有的奖品,自己没有,可能会不满意,且导致用户流失,但是相对来说没有审核那么严重
    • 所以综合来看,消息队列需要高可用,包括数据写入、存储、消息读取都要高可用
  • 是否需要高扩展性:消息队列使用的场景和明确,就是给各个子系统解耦,所以没有扩展需求,因此并不是复杂度关键

综合下来,消息队列的复杂性主要体现在:高性能消息读取高可用消息写入高可用消息存储高可用消息读取

2.2 设计备选方案

  • 备选方案1:采用开源Kafka
    • 是一个成熟的开源消息队列方案、功能强大、性能高、且成熟
  • 备选方案2:集群+MySQL存储
    • 设计要求虽然要求高性能,C++等语言的性能优势是比java大的,但是C++更适合开发高性能中间件,但是为了语言的性能优势而去让团队切换语言是没必要的
    • 所以使用java开发消息队列,例如采用Netty这种java中的高性能网络库来开发消息队列系统,但是QPS达到了13800,单个Netty来支撑还是可能存在问题,所以我们决定采用Netty加上集群的方式来分散QPS的压力,负载均衡就采用简单的轮询即可
    • 这样高性能读取、高可用写入等都能满足,但是高可用存储和高可用读取确实比较复杂
      • 高可用存储要保证:写入的消息在单台服务器宕机时不丢失
      • 高可用读取要保证:写入的消息在单台服务器宕机后能继续读取
    • 可以使用MySQL的主备方案,主备之间数据的复制能保证读取,主备结构可以保证存储
  • 备选方案3:集群+自研存储方案
    • 在2的基础上,将MySQL存储换为了自研的存储方案,因为MySQL的关系型数据特点本身就不是很契合消息队列的数据特点
    • Kafka就是自己实现一套文件存储和复制方案

可以看出高性能消息读取单机系统设计并没有多少方案可选,很大原因是团队的背景约束,java为熟悉语言,适用于高性能中间件的C/C++的人员又少,虽然上面给出了3个方案,比较少,但是现实情况会更复杂,比如开源的MQ还可以选择ActiveMQ,RocketMQ,RabbitMQ,存储方案可以选HBase,Redis+Mysql等

2.3 评估和选择备选方案

参加备选方案的人组织了架构师、研发、测试、运维、几个核心业务主管,人之常情——踢皮球

  • 备选方案1:采用开源的Kafka
    • 支持
      • Kafka比较成熟,且业务团队已经或多或少了解过Kafka,能节省大量的开发投入
      • Kafka比较成熟,无须太多的测试投入
    • 反对
      • 但Kafka设计目的是支撑大容量的日志消息传输,但是当前要解决的问题是业务数据的可靠传输,并且能够保证高性能
      • Kafka由Scala语言编写,运维团队没有维护Scala的经验,很难快速应对问题,且运维有一套运维体系,Kafka很难融入进来
  • 备选方案2:集群+MySQL存储
    • 支持
      • 能有效融入当前运维体系,且MySQL存储数据,可靠性有保障
    • 反对
      • MySQL存储消息数据性能肯定不如文件系统,且会影响团队声誉,毕竟用MySQL做消息队列看起来另类且土
      • 运维又认为成本过高,一个数据分组就需要4台机器(2台Server+2台DB)
      • 测试人力投入大,各方面都需要进行测试
  • 备选方案3:集群+自研存储系统
    • 支持
      • 部分研发人员任务很好,能够展现中间件团队的技术实力,性能相比MySQL要高
    • 反对
      • 一部分研发认为方案复杂度较高,想要达到稳定可靠需要长时间的迭代
      • 运维认为存储系统在没成熟之前,存在各种问题,丢数据等,并且不认为研发能够一上来就解决这种问题
      • 测试和运维一样的意见,测试不一定能够测出全部问题
        环评表如下
质量属性引入KafkaMySQL存储自研存储
性能
复杂度低,开箱即用中,MySQL存储和复制已有,只需要开发服务器集群即可高,自研存储系统复杂度更高
硬件成本高,一个分区就需要2S+2DB低,和Kafka一样
可运维性低,无法融入现有的运维体系高,MySQL运维很成熟,能融入现有体系高,只需维护服务器即可,能融入现有体系
可靠性高,成熟的开源方案高,MySQL存储很成熟低,最初时自研的存储系统的可靠性很难保证
人力投入低,开箱即用中,只需要开发服务器集群高,需要开发服务器集群和存储系统

架构师最终选择了备选方案2

原因

  • 排除备选1:运维性低,会导致新项目上线后,无法快速解决问题,也就意味着整个系统出现停滞的时间会很久,以及出了问题之后的数据丢失问题,且需求是一个可靠传输的MQ
  • 排除备选3:复杂度过高,人又少,可能导致开发周期过长,且开发过程中有其他的开发任务以及中间件维护工作
  • 备选2复杂度不高,可靠性有保障
    • 缺点是性能,但是当前的性能要求不高,即使后续业务量提升了,备选2能支持平行扩展支撑来提高性能下限
    • 成本过高也是一个缺点,备机本身就是为了应对突发故障和备份,可以和其他业务系统部署在同一个机器上
    • 虽然技术上看起来不优越,但是设计的目的首先就是要满足业务需求,并不是证明自己(合适原则)

2.4 细化方案

细化设计1 数据库表如何设计
  • 数据库设计两类表,一类是日志表,用于消息写入时快速存储到M小ySQL中;另一类是消息表,每个消息队列一张表。
  • 业务系统发布消息时,首先写入日志表,日志表写入成功就代表消息写入成功;后台线程再从日志表中读取消息写入记录,将消息内容写入消息表中。
  • 业务系统读取消息时,从消息表中读取。
  • 日志表表名为MQ_LOG,包含的字段:日志IID、发布者信息、发布时间、队列名称、消息内容。
  • 消息表表名就是队列名称,包含的字段:消息ID(递增生成人消息内容、消息发布时间、消息发布者。
  • 日志表需要及时清除已经写入消息表的日志数据,消息表最多保存30天的消息数据。
细化设计2 数据如何复制
  • 直接采用MySQL的主从复制,只复制消息存储表
细化设计3 主备如何倒换
  • 使用ZK做主备决策:主备都在ZK建立节点,主服务器路径规则(/MQ/server/分区编号/master),备机(/MQ/server/分区编号/slave),备机去挂在监听主机节点,主机断开连接后,备机修改自己的状态,对外提供服务
细化设计4 业务服务器如何写入消息
  • 消息队列系统设计两个角色:生产者和消费者,且都有唯一名称
  • 消息队列系统通过SDK提供给其他业务系统调用
    • SDK从配置中读取消息队列系统的服务器信息,使用轮询算法发起消息写入请求,如果发送后无响应(ack)或返回错误,则SDK再次轮询发送
细化设计5 业务服务器如何读取消息
  • 消息队列系统通过SDK提供给其他业务系统调用
    • SDK从配置中读取消息队列系统的服务器信息,轮流向所有服务发起读取请求
  • 消息队列服务器,记录每个消费者状态,即读到哪条消息——index,当接收到读取请求时,就可以根据index返回下一条消息
细化设计6 业务系统和消息队列之间的通信协议
  • 考虑消息队列系统后续可能会对接多种编程语言编写的系统,提高兼容性,就选择了TCP,数据格式为ProtocolBuffer

3 总结

  • 识别复杂度对架构师来说是一项挑战,因为原始的需求中并没有哪个地方会明确地说复杂度在哪里,需要架构师在理解需求的基础上进行分析。
  • 有经验的架构师可能一看需求就知道复杂度大概在哪里,如果经验不足,则只能采取“排查法”,从不同的角度逐一进行分析。
  • 架构师关注的不是一天的数据,而是1秒的数据,即TPS和QPS。
  • 备选方案的选择和很多因素相关,并不单单考虑性能高低、技术是否优越这些纯技术因素,业务的需求特点、运维团队的经验、已有的技术体系、团队人员的技术水平都会影响备选方案的选择。
  • 架构设计目的不是证明自己(参考架构设计原则1:合适原则),而是更快更好地满足业务需求。

http://www.kler.cn/a/447025.html

相关文章:

  • Spring Cloud Gateway 源码
  • 谷歌浏览器的扩展市场使用指南
  • 【GCC】2015: draft-alvestrand-rmcat-congestion-03 机器翻译
  • python:用 sklearn 构建线性回归模型,并评价
  • 笔记本电脑需要一直插着电源吗?电脑一直充电的利弊介绍
  • 5G 模组 初始化状态检测
  • Vue Web开发(十)
  • Git版本控制工具--基础命令和分支管理
  • 【蓝桥杯】43688-《Excel地址问题》
  • 人工智能ACA(四)--机器学习基础
  • 通信技术以及5G和AI保障电网安全与网络安全
  • Godot RPG 游戏开发指南
  • 2024年华为OD机试真题-寻找链表的中间节点-Python-OD统一考试(E卷)
  • SqlSugar查询达梦数据库遇到的异常情况(续)
  • Python使用GitLab API来获取文件内容
  • VMware安装Ubuntu 16.04以及安装好后初步使用配置!
  • 【容器】k8s学习笔记原理详解(十万字超详细)
  • 【论文笔记】结合:“integrate“ 和 “combine“等
  • 在 DDD 中优雅的发送 Kafka 消息
  • 基于SIFT的目标识别算法
  • 中化信息与枫清科技深化合作:共探“AI+”产业新生态
  • 如何制造生产电控超表面
  • 云消息队列 MQTT 版:物联网通信的基础设施
  • 深入QML语法
  • 详解快排+归并排序+堆排序 附源码
  • thinking claude从入门到精通