消息队列的对比测试与RocketMQ使用扩展

 
 
本文的主要内容包括以下几个方面:
  1. 原有的消息技术选型
  2. RocketMQ与kafka 测试对比
  3. 如何构建自己的消息队列服务
  4. RocketMQ扩展改造
  5. RocketMQ使用经验

1. 消息技术选型

1.1 消息历史

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

1.2 弃用kafka

一个核心业务在使用kafka的时候,出现了集群数据写入抖动非常严重的情况,经常会有数据写失败。
主要有两点原因:
  1. 随着业务增长,topic的数据增多,集群负载增大,性能下降。
  2. 我们用的是kafka 0.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进行同步。

2. 为什么选择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之后,吞吐极具下。
可以先看最上面的一组结果,用的是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
测试结果:如下图
 
(横坐标对应吞吐,纵坐标对应延迟时间)
上面的一组的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. 如何构建自己的消息队列服务

3.1 问题与挑战

 
面临的挑战(顺时针看):
  • 客户端语言。需要支持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这一层来做,比如限流、权限认证、消息过滤、格式转化之类的。这样,我们就能尽可能地简化客户端的实现逻辑,不需要把很多功能用各种语言都写一遍。
 

3.2 迁移方案

架构确定后,接下来是我们的一个迁移过程。
 
 
迁移这个事情,在pub-sub的消息模型下,会比较复杂。因为下游的数据消费方可能很多,上游的数据没法做到一刀切流量,这就会导致整个迁移的周期特别长。然后我们为了尽可能地减少业务迁移的负担,加快迁移的效率,我们在proxy层提供了双写和双读的功能。
  • 双写:Procucer Proxy同时写RocketMQ和kafka。
  • 双读:Consumer Proxy同时从RocketMQ和kafka消费数据。
有了这两个功能之后,我们就能提供以下两种迁移方案了。

3.2.1 双写

生产端双写,同时往kafka和rocketmq写同样的数据,保证两边在整个迁移过程中都有同样的全量数据。kafka和RocketMQ有相同的数据,这样下游的业务也就可以开始迁移。
如果消费端不关心丢数据,那么可以直接切换,切完直接更新消费进度。
如果需要保证消费必达,可以先在Consumer Proxy设置消费进度,消费客户端保证没有数据堆积后再去迁移,这样会有一些重复消息,一般客户端会保证消费处理的幂等。
生产端的双写其实也有两种方案:
  1. 客户端双写,如下图:
 
业务那边不停原来的kafka 客户端。只是加上我们的客户端,往RocketMQ里追加写。
这种方案在整个迁移完成之后,业务还需要把老的写入停掉。相当于两次上线。
  1. 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,这样就能实现没有大量重复数据的效果。
所以这个方案对重复消费比较敏感的业务会比较适合的。
 
这个方案的整个过程中,消费方和生产方都只需要改造一遍客户端,上一次线就可以完成。

4. RocketMQ扩展改造

说完迁移方案,这里再简单介绍一下,我们在我们的rocketmq分支上做的一些比较重要的事情。
 
首先一个非常重要的一点是主从的自动切换。熟悉RocketMQ的同学应该知道,目前开源版本的RocketMQ broker 是没有主从自动切换的。如果你的master挂了,那你就写不进去了。然后slave只能提供只读的功能。
当然如果你的topic在多个主节点上都创建了,虽然不会完全写不进去,但是对单分片顺序消费的场景,还是会产生影响。
所以呢,我们就自己加了一套主从自动切换的功能。
 
第二个是批量生产的功能。RocketMQ 4.0之后的版本是支持批量生产功能的。但是限制了,只能是同一个ConsumerQueue的。这个对于我们的proxy服务来说,不太友好,因为我们的proxy是有多个不同的topic的,所以我们就扩展了一下,让它能够支持不同topic、不同consume queue。原理上其实差不多,只是在传输的时候,把topic和consumerqueue的信息都编码进去。
 
第三个,目前RocketMQ 单机能够支持的topic数量,基本在几万这么一个量级,在增加上去之后,元信息的管理就会非常耗时,对整个吞吐的性能影响相对来说就会非常大。 然后我们有个场景又需要支持单机百万左右的topic数量,所以我们就改造了一下元信息管理部分,让RocketMQ单机能够支撑的topic数量达到了百万。
后面一些就不太重要了,比如集成了我们公司内部的一些监控和部署工具,修了几个bug,也给提了PR。最新版都已经修掉了。

5. RocketMQ使用经验

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

5.1 读老数据的问题

我们都知道,RocketMQ的数据是要落盘的,一般只有最新写入的数据才会在PageCache中。比如下游消费数据,因为一些原因停了一天之后,又突然起来消费数据。这个时候就需要读磁盘上的数据。
然后RocketMQ的消息体是全部存储在一个append only的 commitlog 中的。如果这个集群中混杂了很多不同topic的数据的话,要读的两条消息就很有可能间隔很远。最坏情况就是一次磁盘IO读一条消息。这就基本等价于随机读取了。如果磁盘的IOPS(Input/Output Operations Per Second)扛不住,还会影响数据的写入,这个问题就严重了。
 
值得庆幸的是,RocketMQ提供了自动从Slave读取老数据的功能。这个功能主要由slaveReadEnable这个参数控制。默认是关的(slaveReadEnable = false by default)。推荐把它打开,主从都要开。
这个参数打开之后,在客户端消费数据时,会判断,当前读取消息的物理偏移量跟最新的位置的差值,是不是超过了内存容量的一个百分比(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发出请求。
 
 
 
 

消息队列的对比测试与RocketMQ使用扩展的更多相关文章

  1. 消息队列比较-rabbitmq/kafka/rocketmq/ONS

    主要是比较这几种队列中间件: rabbitmq kafka rocketmq ONS 分以下几个维度来比较 高并发 毫无疑问KAFKA发消息的速度是最快的 ROCKETMQ/ONS次之 rabbitm ...

  2. 消息队列(七)--- RocketMQ延时发送和消息重试(半原创)

    本文图片和部分总结来自于参考资料,半原创,侵删 问题 Rocketmq 重试是否有超时问题,假如超时了如何解决,是重新发送消息呢?还是一直等待 假如某个 msg 进入了重试队列(%RETRY_XXX% ...

  3. RabbitMQ消息队列(五)-安装amqp扩展并订阅/发布Demo(.Net Core版)

    publish发布消息 新建一个Asp.Net Core控制台项目:PublishDemo 安装Nuget包 Install-Package RabbitMQ.Client 添加命名空间引用 usin ...

  4. 消息队列的一些场景及源码分析,RocketMQ使用相关问题及性能优化

    前文目录链接参考: 消息队列的一些场景及源码分析,RocketMQ使用相关问题及性能优化 https://www.cnblogs.com/yizhiamumu/p/16694126.html 消息队列 ...

  5. RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙

    消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以 ...

  6. 消息队列 ActiveMQ 、RocketMQ 、RabbitMQ 和 Kafka 如何选择?

    「 预计阅读 6 分钟 」 旁白:这是一篇拖更了N久的文章...0.0(看不见我~) 往期回顾 前端框架 jQuery 和 Vue 如何选择? 安全框架 Shiro 和 Spring Security ...

  7. RabbitMQ,RocketMQ,Kafka 几种消息队列的对比

    常用的几款消息队列的对比 前言 RabbitMQ 优点 缺点 RocketMQ 优点 缺点 Kafka 优点 缺点 如何选择合适的消息队列 参考 常用的几款消息队列的对比 前言 消息队列的作用: 1. ...

  8. 【框架学习与探究之消息队列--EasyNetQ(1)】

    前言 本文欢迎转载,实属原创,本文原始链接地址:http://www.cnblogs.com/DjlNet/p/7603554.html 废话 既然都是废话了,所以大家就可以跳过了,这里是博主有事没事 ...

  9. .NET Core微服务之基于EasyNetQ使用RabbitMQ消息队列

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.消息队列与RabbitMQ 1.1 消息队列 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...

  10. rabbit MQ 消息队列

    为什么会需要消息队列(MQ)? 一.消息队列概述消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息队列有 ...

随机推荐

  1. Spring的IOC容器类别概述

    Spring的IOC该如何理解呢? 平常在一个方法当中,若要用到外部另一个类里的非静态方法,首先,需要先通过new一个对象,再根据这个对象去调用其方法.若只需要一两个对象还好,一旦涉及的外部对象多了, ...

  2. sora未来在哪里,是否改变世界?

    什么是Sora?(Solo 社区投稿) Sora在日语中是天空的意思,是一种文本到视频的扩散模型,Sora与使用文本提示创建图像的 Dall-E 非常相似,Sora 使用文本提示创建短视频.Sora ...

  3. C++使用gnuplot-cpp库绘制图像

    最近想要对一些时变的变量进行可视化,搜索来搜索去选择了使用gnuplot这个工具. sudo apt-get install gnuplot sudo apt-get install gnuplot- ...

  4. 解决方案 | vb记住上次打开的文件夹

      Private Sub Button_ImportBasicData_Click(sender As Object, e As EventArgs) Handles Button_ImportBa ...

  5. TokenObtainPairSerialize

    TokenObtainPairSerializer是Django REST framework的SimpleJWT库提供的序列化器.它用于对用户凭据(如用户名和密码)进行序列化和验证,并在成功的身份验 ...

  6. AT_agc022_a 题解

    洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定字符串 \(S\) , 仅包含互不相同的小写字母, 你需要找到仅包含互不相同的小写 ...

  7. odoo 开发入门教程系列-一个新应用

    一个新应用 房地产广告模块 假设需要开发一个房地产模块,该模块覆盖未包含在标准模块集中特定业务领域. 以下为包含一些广告的主列表视图 form视图顶层区域概括了房产的重要信息,比如name,Prope ...

  8. springsecurity使用:登录与校验

    首先是引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  9. 如何查询MySQL存储的树形结构,层次结构

    表定义如下 如果我们需要在表中查询这个树状结构,通过SQL语句,有两种查询方法: 1.通过inner自连接查询,适用于简单的结构 SELECT * FROM course_category AS on ...

  10. .NET 开源快捷的数据库文档查询和生成工具

    前言 在实际项目开发中,需求变更和项目迭代是常态.要求我们能够迅速响应,对数据库结构进行相应的调整,如添加新表.更新现有表结构或增加字段等. 为了确保团队成员之间的信息同步,实时更新和维护数据库文档变 ...