高可靠高性能的消息队列怎么去实现?

我们看看RPC调用的场景。服务A调用如图所示服务。在正常情况下,一般都不会有问题。但是在以下情况,服务A调用会遇到问题。

1.png

问题一:如果有流量高峰,服务B响应超时,会发生什么情况?

整个RPC调用链路都会受到影响,甚至发生雪崩。

问题二:服务A逻辑复杂,逻辑耦合严重,怎么做拆分?

把一些调用链路中可以异步调用的逻辑调整为消费MQ消息。

问题三:RPC调用,jar依赖问题?服务B升级,调用B的相关服务是否需要升级?

RPC服务需要依赖生成的接口描述jar,服务接口升级一般很难做到向前兼容,所以相关调用方也需要升级。

MQ是以消息为载体的可靠异步调用的框架,能很好的应对上面三个问题。流量削峰,MQ是天然支持的,因为MQ有可靠存储,可以落地。解耦合,交给MQ也很合适。因为MQ的接入方处理的是消息,做到向前兼容也是比较容易的。

2.png

使用MQ之后,服务A,B通过MQ做到松耦合,也能很好的应对流量高峰。

MQ很好很强大,是RPC的有效补充,那问题来了怎么实现一个可靠MQ?本文结合二手交易平台的特点,详细介绍转转ZZMQ架构设计实践。

消息队列架构设计

我们考虑的重点是:可靠,高性能,运维友好,接入方便,可以支持大量堆积,有效缓冲业务高峰,针对这些需求,我们做了一些设计上的考虑和取舍,最终形成了如下的架构方案。

3.jpeg

RegisterCenter 作为注册中心,负责路由服务,无状态,每个NameNode都是对等的,NameNode 可以任意水平扩展。NameNode 与broker和Client都建立了长连接。Broker 内是主从两台机器,slave从master拉取消息Log和消费Offset,做HA。Broker也可以方便地水平扩展,加入新机器,更新topic的路由信息,client会定时更新路由信息。Consumer 和Producer 都需要到注册中心注册,同时拉取Topic的路由信息。Management Protal 是用来管理集群,维护Topic的相关联系人信息。

存储设计

一个高性能ZZMQ的瓶颈和难点就是存储系统,存储系统关乎到性能,数据可靠存储实现是否简单,数据备份控制,消息状态的表示。

目前最常见的是以Kafka为代表的Log-append-file,文件顺序写,追求吞吐量,消息消费状态通过offset来控制,还有以各种存储引擎实现的,比如leveldb(activemq),rocksdb等。

4.jpeg

从这张图(http://queue.acm.org/detail.cfm?id=1563874),我们能看到磁盘顺序读写的性能甚至超过了内存随机访问的性能,能达到50多M/s。并且,相对来说,Log-append-file简单一些。最后选择了类似kafka的log-append-file。

Kafkatopic分成partition,顺序读写,partition有一个小的问题,单机不能支撑大量的topic,单机能支持几百分区,随着分区数量的增加,性能有所下降。而我们的场景的是:业务大部分topic的qps并不像kafka处理的日志文件那么高,希望能单机支撑更多的topic数量。

针对这一点,我们做了一些调整,topic消息本身不再分区,统一存储,使用更加轻量的Consumer Queue实现partition的功能。Consumer Queue存储的只是位置信息,更加轻量,能做到支持更多的topic。

消息写入pageCache,再Dispatch到对应的Consumer Queue中去,Consumer Queue只有消息Log的offset信息,以及大小和其他的一些元数据,占用很小的空间。这样设计,经过验证,单机能够支持几千队列。

这样的设计也带来了一个副作用,因为消息本身是统一存储,topic数量多的时候,消息的读取是随机读,性能有些许下降,目前,我们通过优化linuxpageCache和IO调度,开启系统预读,性能相对顺序读没有太大下降。

消息订阅模型

5.png

Consumergroup A 有两个消费者实例,B也有两个消费者实例,同时订阅了Topic-A,他们之间的消费进度是独立的。Group处理相同topic,消费逻辑一致的一个整体,包含不同的消费实例。

ConsumerGroup基本上是跟kafka的Group一致,由不同的ConsumerGroup来区分不同的消费组,维护Group对应的消费状态,能很方便的实现。

网络

基于netty实现了网络层的协议。Netty对nio,和reactor做了很好的封装,netty4.x 对direct buffer的利用,以及高效的buffer分配和回收算法,netty也解决了TCP协议的半包问题,使用方不需要自己组TCP包。用netty实现网络层协议,网络层的可靠由netty来做,减少了复杂度。

Push 还是Pull

这个设计的考虑主要是两个方面:慢消费,以及消费延迟。主动Push能做到低的消费延迟,但是对于慢消费,不能很好的应对,主动Push需要感知消费者的速率,不至于push 太快,把消费者压垮了。Pull模式,由消费者控制拉取的速度,能很好的应对慢消费的问题,但是,Pull模式对消费延迟不敏感,拉取的频率不好控制,处理不好有可能造成CPU使用率飙升。参照kafka的pull,实现了longpull。Consumer 与Broker建立长连接,Consumer发起拉取请求,如果有消息,Broker 返回消息,如果没有消息,broker hold住这个请求一段时间,等有消息再notify这个请求,返回给客户端。基于long pull,消费延迟能降到跟push差不多,同时又能由Consumer 控制拉取速度。

重复消息和顺序消息

消息重复在一个复杂网络条件下很难避免的,如果由MQ本身来做去重,代价太大,所以我们要求接入MQ的逻辑做好幂等(状态机或者版本号的一些机制)。顺序消息,跟kafka一样,Consumer queue跟partition类似,一个Queue内部,消费是有序的。如果要做到完全有序,只能一个Producer,一个Consumer。

高可用

Broker组由主从两台机器构成,可以配置的策略有同步双写和异步复制。默认情况,采用的异步复制,由slave去拉取master上面的消息Log和offset。Broker 接入了内部的监控系统,每分钟上报topic的消费情况和broker状态,能做到分钟级别发现异常消费。消息写入PageCache之后,默认是异步刷盘。也可以配置为同步刷盘,只有刷盘成功才会返回。

6.png

Broker的默认配置异步刷盘,Master-Slave异步复制。Client消费消费消息的可靠是通过Consume queue的offset来保证的,Client会定时上报已经消费成功的Offset信息。如果Client被异常kill掉,没有确认的消息会被Client 重新拉取,消费。

一条消息的生命过程

Proudcer通过与NameNode的路由找到topic的broker地址,producer发消息,netty序列化,通过TCP传输到对应的broker,broker写log的pageCache,返回。Broker Dispatch消息到对应的Consumer queue,broker唤醒刷盘线程,broker唤醒slave同步线程,slave拉取消息。

NotifyConsumer 拉取请求,经过netty序列化,通过tcp到达Consumer,Consumer消费成功,Client上报消费成功offset。

Broker持久化Offset,slave同步offset。三天之后,broker删除消息。

PS:关注360linker公众号,入官方社区取免费视频教程、知名单位招聘信息。交流分享IT圈学习经验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值