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

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

本文整理自滴滴出行消息队列负责人 江海挺 在Apache RocketMQ开发者沙龙北京站的分享。通过本文,您将了解到滴滴出行:

1. 在消息队列技术选型方面的思考;

2. 为什么选择 RocketMQ 作为出行业务的消息队列解决方案;

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

4. 在 RocketMQ 上的扩展改造实践;

5. 在 RocketMQ 上的实践经验。

江海挺:

滴滴出行消息队列负责人,Apache RocketMQ Contributor,大学毕业后一直在做消息队列领域相关的技术、产品和服务,积累了丰富的实践经验,沉淀了不少关于消息队列的思考。

 

滴滴出行的消息技术选型

1.1 历史

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

1.2 为什么弃用 Kafka

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

主要有两点原因:

  • 随着业务增长,Topic的数据增多,集群负载增大,性能下降;

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

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

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

1.3 为什么选择 RocketMQ

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

为了支持多语言环境、解决一些迁移和某些业务的特殊需求,我们又在消费侧加上了一个代理服务。然后形成了这么一个核心框架。业务端只跟代理层交互。中间的消息引擎,负责消息的核心存储。在之前的基本框架之后,我们后面就主要围绕三个方向做。

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

  • 功能迭代和成本性能上的优化。

  • 服务化,业务直接通过平台界面来申请资源,申请到之后直接使用。

1.4 演进中的架构

这张图是我们消息队列服务的一个比较新的现状。先纵向看,上面是生产的客户端,包括了7种语言。然后是我们的生产代理服务。在中间的是我们的消息存储层。目前主要的消息存储引擎是RocketMQ。然后还有一些在迁移过程中的Kafka。另一个是Chronos,它是我们延迟消息的一个存储引擎。

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

除了存储系统之外,我们也对接了实时计算平台,例如Flink,Spark,Storm,左边是我们的用户控制台和运维控制台。这个是我们服务化的重点。用户在需要使用队列的时候,就通过界面申请Topic,填写各种信息,包括身份信息,消息的峰值流量,消息大小,消息格式等等。然后消费方通过我们的界面,就可以申请消费。

运维控制台,主要负责我们集群的管理,自动化部署,流量调度,状态显示之类的功能。最后所有运维和用户操作会影响线上的配置,都会通过ZooKeeper进行同步。

 

为什么选择RocketMQ

我们围绕以下两个纬度进行了对比测试,结果显示RocketMQ的效果更好。

2.1 测试-topic数量的支持

测试环境:Kafka 0.8.2,RocketMQ 3.4.6,1.0 Gbps Network,16 threads

测试结果如下:

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

先看最上面一组数据,用的是Kafka,开启消费,每条消息大小为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

测试结果:

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

上面的一组的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

测试结果:

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

 

如何构建自己的消息队列

3.1 问题与挑战

面临的挑战(顺时针看)

  • 客户端语言,需要支持PHP、Go、Java、C++;

  • 只有3个开发人员;

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

  • 上线时间紧,线上的Kafka还有问题;

  • 可用性要求高。

使用RocketMQ时的两个问题:

  • 客户端语言支持不全,以Java为主,而我们还需要支持PHP、Go、C++;

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

针对以上两个问题的解决办法,如下图所示:

  • 使用ThriftRPC框架来解决跨语言的问题;

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

主要策略就是坚持KISS原则(Keep it simple, stupid),保持简单,先解决最主要的问题,让消息能够流转起来。然后我们把其他主要逻辑都放在了proxy这一层来做,比如限流、权限认证、消息过滤、格式转化之类的。这样,我们就能尽可能地简化客户端的实现逻辑,不需要把很多功能用各种语言都写一遍。

3.2 迁移方案

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

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

  • 双写:ProcucerProxy同时写RocketMQ和Kafka;

  • 双读:ConsumerProxy同时从RocketMQ和Kafka消费数据。

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

3.2.1 双写

生产端双写,同时往Kafka和RocketMQ写同样的数据,保证两边在整个迁移过程中都有同样的全量数据。Kafka和RocketMQ有相同的数据,这样下游的业务也就可以开始迁移。如果消费端不关心丢数据,那么可以直接切换,切完直接更新消费进度。如果需要保证消费必达,可以先在ConsumerProxy设置消费进度,消费客户端保证没有数据堆积后再去迁移,这样会有一些重复消息,一般客户端会保证消费处理的幂等。

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

  • 客户端双写,如下图:

业务那边不停原来的kafka 客户端。只是加上我们的客户端,往RocketMQ里追加写。这种方案在整个迁移完成之后,业务还需要把老的写入停掉。相当于两次上线。

  • Producer Proxy双写,如下图:

业务方直接切换生产的客户端,只往我们的proxy上写数据。然后我们的proxy负责把数据复制,同时写到两个存储引擎中。这样在迁移完成之后,我们只需要在Proxy上关掉双写功能就可以了。对生产的业务方来说是无感知的,生产方全程只需要改造一次,上一下线就可以了。

所以表面看起来,应该还是第二种方案更加简单。但是,从整体可靠性的角度来看,一般还是认为第一种相对高一点。因为客户端到Kafka这一条链路,业务之前都已经跑稳定了。一般不会出问题。但是写我们Proxy就不一定了,在接入过程中,是有可能出现一些使用上的问题,导致数据写入失败,这就对业务方测试质量的要求会高一点。然后消费的迁移过程,其实风险是相对比较低的。出问题的时候,可以立即回滚。因为它在老的Kafka上消费进度,是一直保留的,而且在迁移过程中,可以认为是全量双消费。

以上就是数据双写的迁移方案,这种方案的特点就是两个存储引擎都有相同的全量数据。

3.2.2 双读

特点:保证不会重复消费。对于P2P 或者消费下游不太多,或者对重复消费数据比较敏感的场景比较适用。

这个方案的过程是这样的,消费先切换。全部迁移到到我们的Proxy上消费,Proxy从Kafka上获取。这个时候RocketMQ上没有流量。但是我们的消费Proxy保证了双消费,一旦RocketMQ有流量了,客户端同样也能收到。然后生产方改造客户端,直接切流到RocketMQ中,这样就完成了整个流量迁移过程。运行一段时间,比如Kafka里的数据都过期之后,就可以把消费Proxy上的双消费关了,下掉Kafka集群。

整个过程中,生产直接切流,所以数据不会重复存储。然后在消费迁移的过程中,我们消费Proxy上的group和业务原有的group可以用一个名字,这样就能实现迁移过程中自动rebalance,这样就能实现没有大量重复数据的效果。所以这个方案对重复消费比较敏感的业务会比较适合的。这个方案的整个过程中,消费方和生产方都只需要改造一遍客户端,上一次线就可以完成。

 

RocketMQ扩展改造

说完迁移方案,这里再简单介绍一下,我们在自己的RocketMQ分支上做的一些比较重要的事情。

首先一个非常重要的一点是主从的自动切换。

熟悉RocketMQ的同学应该知道,目前开源版本的RocketMQ broker 是没有主从自动切换的。如果你的Master挂了,那你就写不进去了。然后slave只能提供只读的功能。当然如果你的topic在多个主节点上都创建了,虽然不会完全写不进去,但是对单分片顺序消费的场景,还是会产生影响。所以呢,我们就自己加了一套主从自动切换的功能。

第二个是批量生产的功能。

RocketMQ4.0之后的版本是支持批量生产功能的。但是限制了,只能是同一个ConsumerQueue的。这个对于我们的Proxy服务来说,不太友好,因为我们的proxy是有多个不同的topic的,所以我们就扩展了一下,让它能够支持不同Topic、不同Consume Queue。原理上其实差不多,只是在传输的时候,把Topic和Consumer Queue的信息都编码进去。

第三个,元信息管理的改造。

目前RocketMQ单机能够支持的Topic数量,基本在几万这么一个量级,在增加上去之后,元信息的管理就会非常耗时,对整个吞吐的性能影响相对来说就会非常大。然后我们有个场景又需要支持单机百万左右的Topic数量,所以我们就改造了一下元信息管理部分,让RocketMQ单机能够支撑的Topic数量达到了百万。

后面一些就不太重要了,比如集成了我们公司内部的一些监控和部署工具,修了几个bug,也给提了PR。最新版都已经fix掉了。

 

RocketMQ使用经验

接下来,再简单介绍一下,我们在RocketMQ在使用和运维上的一些经验。主要是涉及在磁盘IO性能不够的时候,一些参数的调整。

5.1 读老数据的问题

我们都知道,RocketMQ的数据是要落盘的,一般只有最新写入的数据才会在PageCache中。比如下游消费数据,因为一些原因停了一天之后,又突然起来消费数据。这个时候就需要读磁盘上的数据。然后RocketMQ的消息体是全部存储在一个append only的 commitlog 中的。如果这个集群中混杂了很多不同topic的数据的话,要读的两条消息就很有可能间隔很远。最坏情况就是一次磁盘IO读一条消息。这就基本等价于随机读取了。如果磁盘的IOPS(Input/Output Operations Per Second)扛不住,还会影响数据的写入,这个问题就严重了。

值得庆幸的是,RocketMQ提供了自动从Slave读取老数据的功能。这个功能主要由slaveReadEnable这个参数控制。默认是关的(slaveReadEnable = false bydefault)。推荐把它打开,主从都要开。这个参数打开之后,在客户端消费数据时,会判断,当前读取消息的物理偏移量跟最新的位置的差值,是不是超过了内存容量的一个百分比(accessMessageInMemoryMaxRatio= 40 by default)。如果超过了,就会告诉客户端去备机上消费数据。如果采用异步主从,也就是brokerRole等于ASYNC_AMSTER的时候,你的备机IO打爆,其实影响不太大。但是如果你采用同步主从,那还是有影响。所以这个时候,最好挂两个备机。因为RocketMQ的主从同步复制,只要一个备机响应了确认写入就可以了,一台IO打爆,问题不大。

5.2 过期数据删除

RocketMQ默认数据保留72个小时(fileReservedTime=72)。然后它默认在凌晨4点开始删过期数据(deleteWhen="04")。你可以设置多个值用分号隔开。因为数据都是定时删除的,所以在磁盘充足的情况,数据的最长保留会比你设置的还多一天。又由于默认都是同一时间,删除一整天的数据,如果用了机械硬盘,一般磁盘容量会比较大,需要删除的数据会特别多,这个就会导致在删除数据的时候,磁盘IO被打满。这个时候又要影响写入了。

为了解决这个问题,可以尝试多个方法,一个是设置文件删除的间隔,有两个参数可以设置,

  • deleteCommitLogFilesInterval = 100(毫秒)。每删除10个commitLog文件的时间间隔;

  • deleteConsumeQueueFilesInterval=100(毫秒)。每删除一个ConsumeQueue文件的时间间隔。

另外一个就是增加删除频率,把00-23都写到deleteWhen,就可以实现每个小时都删数据。

5.3 索引

默认情况下,所有的broker都会建立索引(messageIndexEnable=true)。这个索引功能可以支持按照消息的uniqId,消息的key来查询消息体。索引文件实现的时候,本质上也就是基于磁盘的个一个hashmap。如果broker上消息数量比较多,查询的频率比较高,这也会造成一定的IO负载。所以我们的推荐方案是在Master上关掉了index功能,只在slave上打开。然后所有的index查询全部在slave上进行。当然这个需要简单修改一下MQAdminImpl里的实现。因为默认情况下,它会向Master发出请求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/324486.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[信息安全] 1.密码工具箱

0. 何谓安全? 对于信息安全性的重要性,我想大家都不会否认。那么具体来说应该具有哪些特性才能称之为安全呢?举个简单的例子:我给你发送一条消息“借给我100元”,当你收到这条消息并且处理后你的账户里面会少出来100块…

深入理解TCP/IP协议-TCP建立与终止连接

转载自 深入理解TCP/IP协议-TCP建立与终止连接 一、引言 TCP 是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。连接创建与终止的状态变化图如下: 二、三次握手建立连接 过程如下: 客户端发送一个 SY…

在Docker中运行asp.net core 跨平台应用程序

概述 Docker已经热了有一两年了,而且我相信这不是一个昙花一现的技术,而是一个将深远影响我们日后开发和部署、运营应用系统的一种创新(很多人将其作为devops的一种非常重要的基石)。学习docker的最好方式,莫过于它的…

java中的Queue队列的用法

大家好,欢迎来到雄雄的小课堂,今天给大家分享的是“java中的Queue队列的用法” 前言:好多人对Queue不是很熟悉,毕竟平时也不怎么用,遇到集合要么List要么map这些常用的,殊不知,java中还有个Que…

SpringCloud Netflix Eureka

文章目录一、 Eureka简介Eureka组件二、 Eureka和Zookeeper 对比1 什么是CAP定理2 基于CAP定理比对Eureka和Zookeeper三、 搭建Eureka注册中心1 POM文件2 配置文件application.yml3 启动类4 访问Eureka Server WEB服务管理平台四、 Eureka 服务管理平台介绍1 Eureka Server服务…

使用枚举定义常量更好点儿

大家好,欢迎来到雄雄的小课堂,昨天给大家分享的是“java中的Queue队列的用法示例”,今天,分享的主题是“java中,推荐使用枚举定义常量”。 前言:常量,相信大家多不会陌生,常量值一般…

SpringCloud Netflix Ribbon

文章目录一、 Ribbon简介二、 使用Ribbon开发微服务1 创建springcloud工程 和 commons子模块2 开发服务提供者 - ribbonappservice3 开发服务消费者 - ribbonappclient三、 集中式与进程内负载均衡区别四、 Ribbon常见的负载均衡策略1 Ribbon中的常用负载均衡简介2 配置负载均衡…

Entity Framework Core 生成跟踪列

注意:我使用的是 Entity Framework Core 2.0 (2.0.0-preview2-final)。正式版发布时,功能可能存在变动。 当您设计数据库时,有时需要添加列以跟踪记录何时更改,以及谁进行了更改。例如,您添加以下列: Cre…

老师,我们想看到您的笑容!

“老师,你可以对我们笑笑吗?”今天偶然遇见一位学生在吃饭的路上和我说道。我冲他点了点头,笑道:“好呀”!是啊,我是好久没有把笑声带回班级中了。1目前,4班都在倾尽全力的做项目,试…

阿里巴巴开源 Spring Cloud Alibaba,加码微服务生态建设

转载自 阿里巴巴开源 Spring Cloud Alibaba,加码微服务生态建设 本周,Spring Cloud联合创始人Spencer Gibb在Spring官网的博客页面宣布:阿里巴巴开源 Spring Cloud Alibaba,并发布了首个预览版本。随后,Spring Cloud…

微软发布Azure Stack更多细节,预计9月交付

在近日举行的微软全球合作伙伴大会上,微软宣布Azure Stack现在开始接受预定,预计9月份就可以交付。Azure Stack是微软公有Azure云的私有云实现。和其他私有云提供商不同,微软将把Azure Stack作为一项基于消费的服务,这和其公有云的…

今天你们表现的真棒!!!

12月5日在报告厅举行了“2020级青鸟4班 HTML网页设计大赛”。从一个洁白如纸的空白页面,到布满五彩斑斓样式的cool页面,是同学们一个字母一个单词的敲打出来的。从头脑空白啥都不会说到现在的条理清晰张嘴就来的演讲,是同学们时时刻刻写稿子背…

再有人问你Netty是什么,就把这篇文章发给他

转载自 再有人问你Netty是什么,就把这篇文章发给他 本文基于Netty4.1展开介绍相关理论模型,使用场景,基本组件、整体架构,知其然且知其所以然,希望给大家在实际开发实践、学习开源项目提供参考。 这是一篇万字长文&a…

SpringCloud Netflix Hystrix

文章目录一、 Hystrix简介1 什么是灾难性雪崩效应2 什么是Hystrix二、 服务降级(Ribbon中)三、 服务熔断(Ribbon中)(服务降级的强化版)四、 请求缓存(Ribbon中)(不推荐)(查询频率高,修改频率低时谨慎使用)五、 Openfeign的雪崩处理1 服务降级…

[信息安全] 3.HTTPS工作流程

0. 简单回顾 在前面两篇博客中介绍了密码相关的一些基本工具,包括(对称密码,公钥密码,密码散列函数,混合密码系统,消息认证码码,数字签名,伪随机数,数字证书&#xff09…

如何实现省市关联的下拉列表

前言:在某些电商网站或者APP中,通常填写地址时,会有这样的功能:当我们选择的省份是“山东”时,则城市的下拉列表里所展示的便是山东的城市,当选择的省份是“山西”时,城市的下拉列表所展示的便是…

什么样的事才是有意义的

有时候就在想,真正什么样的事才算有意义呢?

在Azure Container Service创建Kubernetes(k8s)群集运行ASP.NET Core跨平台应用程序

引子 在此前的一篇文章中,我介绍了如何在本地docker环境中运行ASP.NET Core跨平台应用程序,看起来非常不错,不是吗?那么,如果我们希望真正在实际的生产环境去部署和运行这个应用程序,应该怎么做呢&#xf…

这也许是你不曾留意过的 Mybatis 细节

转载自 这也许是你不曾留意过的 Mybatis 细节 Mybatis 可以说是 Java 后端的必备技能,可能你和我一样经常使用到它。但有时 cv 多了,会忘记了一些细节处理,比如为什么要加上这个注解?它的作用是什么等等。 这篇文章是我以前写的…

Nacos整合Ribbon实现客户端负载均衡

启动类去掉RibbonClient注解 10 50 100 1 权重优先调用 注意:启动类加RibbonClient注解 2 集群优先调用 3 元数据基于版本优先调用