简介: 快手需要建设一个主要面向在线业务的消息系统作为 Kafka 的补充,低延迟、高并发、高可用、高可靠的分布式消息中间件 RocketMQ 正是我们所需的。

作者:黄理

黄理,10多年软件开发和架构经验,热衷于代码和性能优化,开发和参与过多个开源项目。曾在淘宝任业务架构师多年,当前在快手负责在线消息系统建设工作。

为什么建设在线消息系统

在引入RocketMQ之前,快手已经在大量的使用Kafka了,但并非所有情况下Kafka都是最合适的,比如以下场景:

  • 业务希望个别消费失败以后可以重试,并且不堵塞后续其它消息的消费。
  • 业务希望消息可以延迟一段时间再投递。
  • 业务需要发送的时候保证数据库操作和消息发送是一致的(也就是事务发送)。
  • 为了排查问题,有的时候业务需要一定的单个消息查询能力。

为了应对以上这类场景,我们需要建设一个主要面向在线业务的消息系统,作为Kafka的补充。在考察的一些消息中间件中,RocketMQ和业务需求匹配度比较高,同时部署结构简单,使用的公司也比较多,于是最后我们就采用了RocketMQ。

部署模式和落地策略

在一个已有的体系内落地一个开软软件,通常大概有两种方式:

方式一,在开源软件的基础上做深度修改,很容易实现公司内需要的定制功能。但和社区开源版本分道扬镳,以后如何升级?

方式二,尽量不修改社区版本(或减少不兼容的修改),而是在它的外围或者上层进一步包装来实现公司内部需要的定制功能。

注:上图方式一的图画的比较极端,实际上很多公司是方式一、方式二结合的。

我们选择了方式二。最早的时候,我们使用的是4.5.2版本,后来社区4.7版本大幅减小了同步复制的延迟,正好我们的部署模式就是同步复制,于是就很轻松的升级了4.7系列,享受了新版本的红利。

在部署集群的时候,还会面临很多部署策略的选择:

•      大集群 vs 小集群

•      选择副本数

•      同步刷盘 vs 异步刷盘

•      同步复制  vs 异步复制

•      SSD vs 机械硬盘

大集群会有更好的性能弹性,而小集群具有更好的隔离型,此外小集群可以不需要跨可用区/IDC部署,所以会有更好的健壮性,我们非常看重稳定性,因此选择了小集群。集群同步复制异步刷盘,首选SSD。

客户端封装策略

如上所述,我们没有在Rocketmq里面做深度修改,所以需要提供一个SDK来提供公司内的需要的定制功能,这个SDK大概是这样的:

对外只提供最基本的API,所有访问必须经过我们提供的接口。简洁的API就像冰山的一个角,除了对外的简单接口,下面所有的东西都可以升级更换,而不会破坏兼容性。

业务开发起来也很简单,只要需要提供Topic(全局唯一)和Group就可以生产和消费,不用提供环境、Name Server地址等。SDK内部会根据Topic解析出集群Name Server的地址,然后连接相应的集群。生产环境和测试环境环境会解析出不同的地址,从而实现了隔离。

上图分为3层,第二层是通用的,第三层才对应具体的MQ实现,因此,理论上可以更换为其它消息中间件,而客户端程序不需要修改。

SDK内部集成了热变更机制,可以在不重启client的情况下做动态配置,比如下发路由策略(更换集群name server的地址,或者连接到别的集群去),Client的线程数、超时时间等。通过maven强制更新机制,可以保证业务使用的SDK基本上是最新的。

集群负载均衡 & 机房灾备

所有的Topic默认都分配到两个可用区,生产者和消费者会同时连接至少两个独立集群(分布在不同的可用区),如下图:

生产者同时连接两个集群,如果可用区A出现故障,流量就会自动切换到可用区B的集群2去。我们开发了一个小组件来实现自适应的集群负载均衡,它包含以下能力:

•      千万级OPS

•      灵活的权重调整策略

•      健康检查支持/事件通知

•      并发度控制(自动降低响应慢的服务器的请求数)

•      资源优先级(类似Envoy,实现本地机房优先,或是被调服务器很多的时候选取一个子集来调用)

•      自动优先级管理

•      增量热变更

实际上它并不仅仅用于消息生产者,而是一个通用的主调方负载均衡类库,可以在github上找到:

https://github.com/PhantomThief/simple-failover-java

核心的SimpleFailover接口和PriorityFailover类没有传递第三方依赖,非常容易整合。

多样的消息功能

延迟消息

延迟消息是非常重要的业务功能,不过RocketMQ内置的延迟消息只能支持几个固定的延迟级别,所以我们又开发了单独的Delay Server来调度延迟消息:

上图这个结构没有直接将延迟消息发到Delay Server,而是更换Topic以后存入RocketMQ。这样的好处是可以复用现有的消息发送接口(以及上面的所有扩展能力)。对业务来说,只需要在构造消息的时候额外指定一个延迟时间字段即可,其它用法都不变。

事务消息

RocketMQ 4.3版本以后支持了事务消息,可以保证本地事务和消费发送同时成功或者失败,对于一些业务场景很有帮助。事务消息的用法和原理有很多资料,这里就不细述了。但关于事务消息的实践网上资料较少,我们可以给出一些建议。

首先,事务消息功能一直在不断完善,应该使用最新的版本,至少是4.6.1以后的版本,可以避免很多问题。

其次,事务消息性能是不如普通消息的,它在内部实际上会生成3个消息(一阶段1个,二阶段2个),所以性能大约只有普通消息的1/3,如果事务消息量大的话,要做好容量规划。回查调度线程也只有1个,不要用极限压力去考验它。

最后有一些参数注意事项。在broker的配置中:

  • transientStorePoolEnable这个参数必须保持默认值false,否则会有严重的问题。
  • endTransactionThreadPoolNums是事务消息二阶段处理线程大小,sendMessageThreadPoolNums则指定一阶段处理线程池大小。如果二阶段的处理速度跟不上一阶段,就会造成二阶段消息丢失导致大量回查,所以建议endTransactionThreadPoolNums应该大于sendMessageThreadPoolNums,建议至少4倍。
  • useReentrantLockWhenPutMessage设置为true(默认值是false),以免线程抢锁出现严重的不公平,导致二阶段处理线程长时间抢不到锁。
  • transactionTimeOut默认值6秒太短了,如果事务执行时间超过6秒,就可能导致消息丢失。建议改到1分钟左右。

生产者client也有一个注意事项,如果有多组broker,并且是2副本(有1个Slave),应该打开retryAnotherBrokerWhenNotStoreOK,以免某个Slave出现故障以后,大量消息发送失败。

分布式对账监控

除了比较一些常规的监控手段以外,我们开发了一个监控程序做分布式对账。可以发现我们的集群以及我们提供的SDK是否有异常。

具体做法是在每个Broker上都建立一个监控专用的Topic,监控程序使用我们自己提供的SDK框架来连接集群(就像我们的业务用户那样),监控生产者会给每个集群发送少量消息。然后检查发送是否成功:

发送成功

成功

刷盘超时

Slave超时

Slave不可用

发送失败

具体错误码

生产者只对这些结果进行打点,不判断是否正常,具体到监控(或者演练)场景可以配置不同的报警规则。

消费者收到了消息会通过TCP旁路Ack生产者,生产者这边会做分布式对账,将对账结果打点:

  • 收到消息
  • 消息丢失(或超时未收到消息)
  • 重复收到消息
  • 消息生成到最终消费的时间差
  • Ack生产者失败(由消费者打点)

同样监控程序只负责打点,报警规则可另外配置。

这套机制也可以用于分布式性能压测和故障演练。在做压测的时候,每个消息都Ack的话,对生产者的内存压力很大,因为它发出去的消息,需要在内存中保留一段时间(直到到达这个消息的对账时间),这段时间消费者Ack或者重复Ack都需要记录。所以我们实现了按比例抽样对账的功能,开启以后只有需要对账的消息才会在内存中保留一段时间。

顺便说一下,我们做压测时,合格的标准是异步生产不失败、消费不延迟、每一个消息都不丢失。这样做是为了保证压测时能给出更加准确的,可供线上系统参考的性能数字,而不是制造理想条件,追求一个大的数字。比如异步生产比同步生产更脆弱(压测client如果同步生产,broker抖动的时候,同步client会被堵塞导致发送速度降低,于是降低了broker压力,消息发送不容易失败,但是会看到发送速率在波动),更贴近生产环境的实际情况,我们就选择异步生产来评估。

性能优化

Broker默认的参数在我们的场景下(SSD、同步复制、异步刷盘)不是最优的,有的参数也许在大多数场景下都不是最优的。我们列出一些重要的参数,供大家参考:

参数

默认值

说明

flushCommitLogTimed

False

默认值不合理,异步刷盘这个参数应该设置成true,导致频繁刷盘,对性能影响极大

deleteWhen

04

几点删除过期文件的时间,删除文件时有很多磁盘读,这个默认值是合理的,有条件的话还是建议低峰删除

sendMessageThreadPoolNums

1

处理生产消息的线程数,这个线程干的事情很多,建议设置为2~4,但太多也没有什么用。因为最终写commit log的时候只有一个线程能拿到锁。

useReentrantLockWhenPutMessage

False

如果前一个参数设置比较大,这个最好设置为True,避免高负载下自旋锁空转消耗CPU。

sendThreadPoolQueueCapacity

10000

处理生产消息的队列大小,默认值可能有点小,比如5万TPS(异步发送)的情况下,卡200ms就会爆。设置比较小的数字可能是担心有大量大消息撑爆内存(比如100K的话,1万个的消息大概占用1G内存,也还好),具体可以自己算,如果都是小消息,可以把这个数字改大。可以修改broker参数限制client发送大消息。

brokerFastFailureEnable

True

Broker端快速失败(限流),和下面两个参数配合。这个机制可能有争议,client设置了超时时间,如果client还愿意等,并且sendThreadPoolQueue还没有满,不应该失败,sendThreadPoolQueue满了自然会拒绝新的请求。但如果client设置的超时时间很短,没有这个机制可能导致消息重复。可以自行决定是否开启。理想情况下,能根据client设置的超时时间来清理队列是最好的。

waitTimeMillsInSendQueue

200

200ms很容易导致发送失败,建议改大,比如1000

osPageCacheBusyTimeOutMills

1000

Page cache超时时间,如果内存比较多,比如32G以上,建议改大点

得益于简单、几乎0依赖的部署模式,使得我们部署小集群的成本非常低;不对社区版本进行魔改,保证我们可以及时升级;统一SDK入口方便集群维护和功能升级;通过复合小集群+自动负载均衡实现多机房多活;充分利用RocketMQ的功能比如事务消息、延迟消息(增强)来满足业务的多样性需求;通过自动的分布式对账,对每一个Broker以及我们的SDK进行正确性监控;本问也进行了一些性能参数的分享,但写的比较简单,基本只说了怎么调,但没能细说为什么,以后我们会另写文章详述。目前RocketMQ已经应用在公司在大多数业务线,期待将来会有更好的发展!

原文链接

本文为阿里云原创内容,未经允许不得转载。

快手基于RocketMQ的在线消息系统建设实践的更多相关文章

  1. Kafka 分布式的,基于发布/订阅的消息系统

    Kafka是一种分布式的,基于发布/订阅的消息系统.主要设计目标如下: 通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能. 高吞吐量:即使是非常 ...

  2. 滴滴出行基于RocketMQ构建企业级消息队列服务的实践

    小结: 1. https://mp.weixin.qq.com/s/v6NM3UgX-qTI7yO1QPCJrw 滴滴出行基于RocketMQ构建企业级消息队列服务的实践 原创: 江海挺 阿里巴巴中间 ...

  3. 基于Django的在线考试系统

    概述 基于Django的在线考试系统,适配电脑端,可以实现出题,答题,排行榜,倒计时等等等功能 详细 代码下载:http://www.demodashi.com/demo/13923.html 项目目 ...

  4. 分布式事务之如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性?

    导读 在之前的文章中我们介绍了如何基于RocketMQ搭建生产级消息集群,以及2PC.3PC和TCC等与分布式事务相关的基本概念(没有读过的读者详见

  5. 基于JSP的在线考试系统-JavaWeb项目-有源码

    开发工具:Myeclipse/Eclipse + MySQL + Tomcat 系统简介: 网络考试系统主要用于实现高校在线考试,基本功能包括:自动组卷.试卷发布.试卷批阅.试卷成绩统计等.本系统结构 ...

  6. 基于SSM的在线考试系统

    本系统功能非常完善,页面美观大方,技术新颖,选用主流数据库Mysql,表数量及结构适当,如果你需要做在线考试或者其它考试类系统,这个系统将非常有用. 其实,任何考试系统,无非试题不一样,所以如果你是做 ...

  7. 基于SSM开发在线考试系统 Java源码

    实现的关于在线考试的功能有:用户前台:用户注册登录.查看考试信息.进行考试.查看考试成绩.查看历史考试记录.回顾已考试卷.修改密码.修改个人信息等,后台管理功能(脚手架功能不在这里列出),科目专业管理 ...

  8. 美团点评基于 Flink 的实时数仓建设实践

    https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651749037&idx=1&sn=4a448647b3dae5 ...

  9. 基于Web在线考试系统的设计与实现

    这是一个课程设计的文档,源码及文档数据库我都修改过了,貌似这里复制过来的时候图片不能贴出,下载地址:http://download.csdn.net/detail/sdksdk0/9361973   ...

  10. 基于CNONIX国家标准的出版社ERP系统建设分享

    目录 一.出版社ERP系统建设面临的三大挑战 在系统建设中如何贯彻CNONIX国家标准 新ERP系统建设面临的挑战 技术体系及架构选择面临的挑战 二.系统建设实施过程控制 项目组织管控 项目技术管控 ...

随机推荐

  1. 记录一次报错,程序启动,MySql自动关闭

    关于初级程序员,对于安装mysql,以及配置可能会报几次错 有时候虽然进行第二次安装成功,但是第一次的残留文件还在,可能引起报错 在这里记录一次我的报错 程序启动导致Mysql自动断开,需要手动打开 ...

  2. JDBC反序列化

    JDBC反序列化攻击 介绍 JDBC(Java DataBase Connectivity)是一种用于执行Sql语句的Java Api,即Java数据库连接,是Java语言中用来规范客户端程序如何来访 ...

  3. 说说如何在Vue项目中应用TypeScript?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.前言 与link类似 在VUE项目中应用typescript,我们需要引入一个库vue-property-decorator, 其是基 ...

  4. 记录--没有await,如何处理“回调地狱”

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 太长不看 不要嵌套使用函数.给每个函数命名并把他们放在你代码的顶层 利用函数提升.先使用后声明. 处理每一个异常 编写可以复用的函数,并把 ...

  5. 你的DDPG/RDPG为何不收敛?

    园子好多年没有更过了,草长了不少.上次更还是读博之前,这次再更已是博士毕业2年有余,真是令人唏嘘.盗链我博客的人又见长,身边的师弟也问我挖的几个系列坑什么时候添上.这些着实令我欣喜,看来我写的东西也是 ...

  6. 15 分钟带你感受 CSS :has() 选择器的强大

    最近看到了许多关于 :has() 选择器的知识点,在此总结下来. MDN 对 :has() 选择器 的解释是这样的: CSS 函数式伪类  :has()  表示一个元素,如果作为参数传递的任何相对选择 ...

  7. JS 为什么0==““ 会是true

    0 是逻辑的 false 1 是逻辑的 true空字符串是逻辑的 false null 是逻辑的 false NaN==任何 都是false 所以:空字符串是逻辑的 false , 0是逻辑的fals ...

  8. java实战:多属性排序

    多属性排序的核心点就是对Arrays.sort()和Collections.sort()方法的Comparator进行重写 Arrays.sort()的三种用法 1.1.Arrays.sort(int ...

  9. JDBCUtil 连接MYSQL数据库Java工具类

    1 package com.reliable.util; 2 import java.sql.Connection; 3 import java.sql.DriverManager; 4 import ...

  10. Python爬取腾讯视频电影名称和链接(一)

    1 import requests 2 import json 3 from bs4 import BeautifulSoup #网页解析获取数据 4 import sys 5 import re 6 ...