为了账号安全,请及时绑定邮箱和手机立即绑定

放弃Kafka,滴滴出行选择基于RocketMQ构建企业级消息队列服务

标签:
Premiere

本文的主要内容包括以下几个方面:

  1. 滴滴的消息技术选型

  2. 为什么选择RocketMQ

  3. 如何构建自己的消息队列服务

  4. RocketMQ扩展改造

  5. RocketMQ使用经验

1. 滴滴的消息技术选型

1.1 消息历史

webp

image-20181014102848377.png


如图,初期公司内部没有专门的团队维护消息队列服务,所以消息队列使用方式较多,主要以kafka为主,有业务直连的,也有通过独立的服务转发消息的。另外有一些团队也会用 RocketMQ、Redis的list,甚至会用比较非主流的beanstalkkd。导致的结果就是,比较混乱,无法维护,资源使用也很浪费。

1.2 弃用kafka

一个核心业务在使用kafka的时候,出现了集群数据写入抖动非常严重的情况,经常会有数据写失败。

主要有两点原因:

  1. 随着业务增长,topic的数据增多,集群负载增大,性能下降。

  2. 我们用的是kafka 0.8.2那个版本,有个bug,会导致副本重新复制,复制的时候有大量的读,我们存储盘用的又是机械盘,导致磁盘IO过大,影响写入。

所以我们决定做自己的消息队列服务。

webp

![image-20181014113200764.png](https://upload-images.jianshu.io/upload_images/10425061-8d679ffbcd6af652.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

首先需要解决上面的解决业务方消息生产失败的问题。因为这个kafka用的是发布/订阅模式,一个topic的订阅方会有很多,涉及到的下游业务也就非常多,没办法一口气直接替换kafka,迁移到新的一个消息队列服务上。
所以我们当时的方案是加了一层代理,然后利用codis作为缓存,解决了kafka不定期写入失败的问题,如上图。
就是当后面的kafka出现不可写入的时候,我们就会先把数据写入到codis中,然后延时进行重试,直到写成功为止。

1.3 选择RocketMQ

经过一系列的调研和测试之后,我们决定采用RocketMQ。具体原因在后面会介绍。

为了支持多语言环境、解决一些迁移和某些业务的特殊需求,我们又在消费侧加上了一个代理服务。

然后形成了这么一个核心框架。业务端只跟代理层交互。中间的消息引擎,负责消息的核心存储。

在之前的基本框架之后,我们后面就主要围绕三个方向做。

一个是迁移。把之前提到的所有五花八门的队列环境,全部迁移到我们上面。这里面的迁移方案后面会跟大家介绍一下。

第二个就是功能迭代和成本性能上的优化。

最后一个重点就是服务化,业务直接通过平台界面来申请资源,申请到之后直接使用。

1.4 演进中的架构

webp

image-20181014113633110.png

这张图是我们消息队列服务的一个比较新的现状。

先纵向看,上面是生产的客户端,包括了7种语言。然后是我们的生产代理服务。

在中间的是我们的消息存储层。目前主要的消息存储引擎是RocketMQ。然后还有一些在迁移过程中的Kafka。还有一个chronos,它是我们延迟消息的一个存储引擎。

再下面就是消费代理。

消费代理同样提供了多种语言的客户端。然后还支持多种协议的消息主动推送功能。包括HTTP 协议 RESTful方式。结合我们的groovy脚本功能,还能实现将消息直接转存到redis、hbase和hdfs上。更多的下游存储,我们都在陆续接入。

除了存储系统之外,我们还对接了实时计算平台,像Flink,Spark,Storm这些平台,我们也都提供了支持。

左边是我们的用户控制台和运维控制台。这个是我们服务化的重点。

用户在需要使用队列的时候,就通过界面申请topic,填写各种信息,包括身份信息,消息的峰值流量,消息大小,消息格式等等。

然后消费方,通过我们的界面,就可以申请消费。

运维控制台,主要负责我们集群的管理,自动化部署,流量调度,状态显示之类的功能。

最后所有运维和用户操作会影响线上的配置,都会通过zookeeper进行同步。

2. 为什么选择RocketMQ

因为从实际测试结果来看,RocketMQ的效果更好。

主要围绕两个测试进行。

2.1 测试-topic数量的支持

如下图所示,测试环境:

Kafka 0.8.2

RocketMQ 3.4.6

1.0 Gbps Network

16 threads

webp

image-20181014114750847.png

这张图是Kafka和RocketMQ在不同topic数量下的吞吐测试。横坐标是每秒消息数,纵坐标是测试case。同时覆盖了有无消费,和不同消息体的场景。一共8组测试数据,每组数据分别在topic个数为16、32、64、128、256时获得的,每个topic包括8个partition。下面四组数据是发送消息大小为128字节的情况,上面四种是发送2k消息大小的情况。on 表示消息发送的时候,同时进行消息消费,off表示仅进行消息发送。

先看最上面一组数据,用的是kafka,开启消费,每条消息大小为2048字节。可以看到,随着topic数量增加,到256 topic之后,吞吐极具下。
可以先看最上面的一组结果,用的是Kafka,开启消费,每条消息是2kb(2048)。可以看到,随着topic数量增加,到256个topic之后,吞吐急剧下降。

第二组是是RocketMQ。可以看到,topic增大之后,影响非常小。

第三组和第四组,是上面两组关闭了消费的情况。结论基本类似,整体吞吐量会高那么一点点。

下面的四组跟上面的区别是使用了128字节的小消息体。可以看到,kafka吞吐受topic数量的影响特别明显。对比来看,虽然topic比较小的时候,RocketMQ吞吐较小,但是基本非常稳定,对于我们这种共享集群来说比较友好。

2.2 测试-延迟

  • Kafka

测试环境:

Kafka 0.8.2.2

topic=1/8/32

Ack=1/all,replica=3

测试结果:如下图

webp

image-20181014153801681.png

(横坐标对应吞吐,纵坐标对应延迟时间)

上面的一组的3条线对应ack=3,需要3个备份都确认后才完成数据的写入。

下面的一组的3条线对应ack=1,有1个备份收到数据后就可以完成写入。

可以看到下面一组只需要主备份确认的写入,延迟明显较低。

每组的三条线之间主要是topic数量的区别,topic数量增加,延迟也增大了。

  • RocketMQ

测试环境:

RocketMQ 3.4.6

brokerRole=ASYNC/SYNC_MASTER,  2 Slave

flushDiskType=SYNC_FLUSH/ASYNC_FLUSH

测试结果:如下图

webp

image-20181014153954985.png

上面两条是同步刷盘的情况,延迟相对比较高。下面的是异步刷盘。

橙色的线是同步主从,蓝色的线是异步主从。

然后可以看到在副本同步复制的情况下,即橙色的线,4w的tps之内都不超过1ms。用这条橙色的线和上面Kafka的图中的上面三条线横向比较来看,kafka超过1w tps 就超过1ms了。kafka的延迟明显更高。

3. 如何构建自己的消息队列服务

3.1 问题与挑战

webp

challenge.png

面临的挑战(顺时针看):

  • 客户端语言。需要支持PHP、GO、Java、C++。

  • 只有3个开发人员。

  • 决定用RocketMQ,但是没看过源码。

  • 上线时间紧,线上的kafka还有问题。

  • 可用性要求高。

使用RocketMQ时的两个问题:

  • 客户端语言支持不全,它主要支持Java,而我们还需要支持PHP、Go、C++,RocketMQ目前提供的Go的sdk我们测的有一些问题。

  • 功能特别多,如tag、property、消费过滤、RETRY topic、死信队列、延迟消费之类的功能,非常丰富。但是这个对我们稳定性维护来说,挑战非常大。

解决办法,如下图所示:

  • 使用Thrift RPC框架来解决跨语言的问题。

  • 简化调用接口。可以认为只有两个接口,send用来生产,pull用来消费。

主要策略就是坚持KISS原则(Keep it simple, stupid),保持简单,先解决最主要的问题,让消息能够流转起来。

然后我们把其他主要逻辑都放在了proxy这一层来做,比如限流、权限认证、消息过滤、格式转化之类的。这样,我们就能尽可能地简化客户端的实现逻辑,不需要把很多功能用各种语言都写一遍。

webp

image-20181014164732619.png

3.2 迁移方案

架构确定后,接下来是我们的一个迁移过程。


webp

image-20181014183257694.png

迁移这个事情,在pub-sub的消息模型下,会比较复杂。因为下游的数据消费方可能很多,上游的数据没法做到一刀切流量,这就会导致整个迁移的周期特别长。然后我们为了尽可能地减少业务迁移的负担,加快迁移的效率,我们在proxy层提供了双写和双读的功能。

  • 双写:Procucer Proxy同时写RocketMQ和kafka。

  • 双读:Consumer Proxy同时从RocketMQ和kafka消费数据。

有了这两个功能之后,我们就能提供以下两种迁移方案了。

3.2.1 双写

生产端双写,同时往kafka和rocketmq写同样的数据,保证两边在整个迁移过程中都有同样的全量数据。kafka和RocketMQ有相同的数据,这样下游的业务也就可以开始迁移。

如果消费端不关心丢数据,那么可以直接切换,切完直接更新消费进度。

如果需要保证消费必达,可以先在Consumer Proxy设置消费进度,消费客户端保证没有数据堆积后再去迁移,这样会有一些重复消息,一般客户端会保证消费处理的幂等。

生产端的双写其实也有两种方案:

  1. 客户端双写,如下图:

webp



作者:qingfekg
链接:https://www.jianshu.com/p/1efeb2e79926


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消