本文作者王文锋
Horizon.ai研发工程师,
Apache RocketMQ Committer
Apache Go Client项目负责人
今天和大家来谈一个有关消息队列的话题,消息队列作为最常见的互联网中间件,广泛的应用于业务之间异步解耦、分布式事务的数据一致性同步、流量削峰填谷、分布式缓存更新等场景中。在目前Go的生态里面,大家最常用的消息队列之一就是NSQ了,NSQ最大的优势就是简单易用,但在一些对稳定性和可靠性要求比较苛刻的场景中,或者一些业务更复杂的场景中,NSQ会略显不足。
在如今的2020年,一个Gopher做MQ的技术选型的时候,还有没有更好的选择?这时候很多同学可能会想到Kafka,不过今天给大家介绍的并不是Kafka,而是另一款优秀的消息队列产品——Apache RocketMQ。在文章的后半部分,会有一个RocketMQ和Kafka的对比,希望能对大家有所启发。鉴于篇幅有限,今天主要侧重于讲解应用面,具体的技术细节不会过多展开,将来有机会再和大家进行分享。
本文字数:3441字
精读时间:10分钟
也可在5分钟内完成速读
01
Apache RocketMQ
Apache RocketMQ 在2016年被阿里巴巴捐献给了 Apache 基金会,经过孵化后成为了 Apache 的顶级项目。2011年阿里巴巴中间件团队自主研发了RocketMQ 消息中间件,具有单机亿级消息堆积能力,且能支持严格的消息顺序。在2016年的时候,阿里巴巴线上所有消息全部通过RocketMQ来转发投递,2016年(暂时没有最新数据)双十一当天达到了万亿级消息量,峰值几千万TPS,创造了当时国内乃至世界上最大的消息流转记录。在2017年的时候,RocketMQ的单机TPS就成功突破了50w。RocketMQ开源多年以来,已广泛的应用于国内各种企业的各种场景中。在最新的v4.6版本中,主要特性主要有:
基于Topic模型的消息发布/订阅
支持集群消费/广播消费
支持顺序消息
支持定时/延时消息
支持事务消息
支持消息重试和消费过滤
高可靠性和稳定性
多种Connector
支持k8s Operator
支持多租户和ACL
支持Prometheus
不过,由于RocketMQ是基于Java实现,长期以来在Go语言社区并未广泛使用,很多社区的小伙伴也对其知之甚少。而在这几天,RocketMQ Go Client 2.0.0-rc1正式发布,正好借此机会,向社区的小伙伴进行下相关的介绍。
02
Go Client
背景
如上图所示,在1.7号的时候,RocketMQ社区正式发布了2.0-rc1版本,在这之前,已经发布了3个alpha版本,目前最新版本的代码已经在头条和斗鱼线上有了较大规模的应用。因此虽然Go Client项目是一个完全社区驱动的项目,但还是请大家放心使用(PS: 大部分的坑我们都已经踩完啦),社区对issue的响应也是比较快速的。
在2.0之前,我们基于cgo开发了RocketMQ 1.2.x版本(图中1.2.x的小版本没有体现),由于Apache不允许发行版携带任何的二进制文件,所以需要使用1.2.x版本的同学自行编译RocketMQ CPP项目,而CPP项目依赖了不少c++库,编译起来实在是酸爽,以及很多Gopher并没有c++背景,出了问题,没办法自行排查,只能反馈给CPP的同学,导致沟通成本高、排查周期长、使用体验差等问题,因此我决定参考Java Client使用Go开发一个native版本的Client,解决以上问题。
从实现上来说,RocketMQ Java Client是一个典型的富客户端,应用了较多的设计模式和开发范式,存在整体逻辑较复杂,代码耦合度过高等问题,为了避免在Go项目里面引入这些问题,易于社区参与,我们将简单和可维护性放到了首位,基于Go的特性对Client进行了全新的设计。不过在具体开发过程中,对个别地方的把控还是做的不够好,略显粗糙和带着些许Java的味道。这块后续还有不少工作需要做,期待社区能有感兴趣的同学一起参与进来。
使用
require (
github.com/apache/rocketmq-client-go v2.0.0-rc1
)
生产消息
p, _ := rocketmq.NewProducer(
producer.WithNameServer([]string{"127.0.0.1:9876"}),
producer.WithRetry(2),
)
err := p.Start()
// error handler
msg := primitive.NewMessage("test-topic", []byte("Hello Golang!"))
res, err := p.SendSync(context.Background(), msg)
// response and error handler
err = p.Shutdown()
if err != nil {
fmt.Printf("shutdown producer error: %s", err.Error())
}
完整的代码请参考:
https://github.com/apache/rocketmq-client-go/blob/native/examples/producer/simple/main.go, 这是一个最简单的例子。
批量、异步等更多场景的例子请参考:
https://github.com/apache/rocketmq-client-go/blob/native/examples/producer
消费消息
c, _ := rocketmq.NewPushConsumer(
consumer.WithGroupName("testGroup"),
consumer.WithNameServer([]string{"127.0.0.1:9876"}),
)
selector := consumer.MessageSelector{}
msgHandler := func(ctx context.Context,
msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
for i := range msgs {
fmt.Printf("recevied message: %v \n", msgs[i])
}
return consumer.ConsumeSuccess, nil
}
err := c.Subscribe("test-topic", selector, )
// error handler
// 注意: consumer实例启动前至少需要订阅1个topic,因为consumer启动后立马开始执行
// 负载均衡逻辑,没有Topic订阅信息会退出
err = c.Start()
// error handler & gracefully shutdown
完整的代码请参考:
https://github.com/apache/rocketmq-client-go/blob/native/examples/consumer/simple/main.go, 这是一个最简单的例子。
顺序、广播等更多场景的例子请参考:
https://github.com/apache/rocketmq-client-go/blob/native/examples/consumer
高级用法
Interceptor
在Go Client,我们实现了Interceptor接口
type Invoker func(ctx context.Context, req, reply interface{}) error
type Interceptor func(ctx context.Context, req, reply interface{}, next Invoker) error
熟悉gRPC的同学是不是比较眼熟?我们使用了gRPC风格的Interceptor替换了Java中的RPCHook,在体验上变的更加友好。在使用方面,也基本类似,一般都是下面这个样子的:
func DemoInterceptor() primitive.Interceptor {
return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
// do before
if msgs, ok := req.([]*primitive.Message); ok {
// do something
}
err := next(ctx, req, reply)
// do after
switch reply.(type) {
case *primitive.SendResult:
// do something
case *consumer.ConsumeResult:
// do something
default:
}
return err
}
}
ACL
RocketMQ支持对不同的`topic`设置ACL,在Go Client中,producer和consumer都支持Credential参数,实现topic的私有访问。
type Credentials struct {
AccessKey string
SecretKey string
SecurityToken string
}
func WithCredentials(c primitive.Credentials) Option {
return func(options *consumerOptions) {
options.ClientOptions.Credentials = c
}
}
// consumer也一样
c, err := rocketmq.NewPushConsumer(
...,
consumer.WithCredentials(primitive.Credentials{
AccessKey: "abcdef",
SecretKey: "123456",
}),
)
ACL在服务端具体的配置方法可以参考文末的文章链接。
结构化日志
Go Client项目默认使用了`logrus`作为默认的日志框架,不过为了满足大家个性化的需求,我们抽象出了日志接口,方便大家定制化进行开发:
type Logger interface {
Debug(msg string, fields map[string]interface{})
Info(msg string, fields map[string]interface{})
Warning(msg string, fields map[string]interface{})
Error(msg string, fields map[string]interface{})
Fatal(msg string, fields map[string]interface{})
}
内部的默认实现:
https://github.com/apache/rocketmq-client-go/blob/native/rlog/log.go#L66。
大家有需要的话可以进行参考。
上述几种高级用法,可能是企业中最高频的几个场景,剩下的大家若感兴趣的话可以通过前文的`examples`链接进行探索。
03
对比Kafka
RocketMQ和Kafka都是非常优秀的开源消息队列产品,其应用场景也比较类似,性能方面二者均能达到一个极高的吞吐量,因此孰强孰弱更多的是一个仁者见仁智者见智的问题。就我个人认为,RocketMQ相对Kafka,最关键的两点在于:
Kafka依赖ZooKeeper作为HA方案,引入了外部依赖,提高了运维成本。而RocketMQ不依赖任何的第三方软件
对于国内用户而言,RocketMQ是由阿里巴巴捐赠,也有一些同学参与社区开源,加上社区参与的同学也大多都是中国人,因此在沟通和问题响应上是有优势的
下面是我列一个功能方面的对比表格,方便大家参考
功能 | RocketMQ | Kafka |
可靠性 | 同步/异步落盘 | 异步刷盘 |
HA | master-slave | 依赖zk |
单机TPS | 百万级 | 百万级 |
投递实时性 |
长轮询, 平均~5ms | 取决于轮询频率 |
消息失败重试 | 支持 |
不支持 |
严格顺序消息 | 支持 |
不支持 |
定时消息 | 支持 |
不支持 |
事务消息 | 支持 |
不支持 |
消息轨迹 | 支持 |
不支持 |
消息堆积 | 上亿 |
上亿 |
消息过滤 | 服务端+客户端 | 客户端 |
消息查询 | 支持 | 不支持 |
消息回溯 | 直接支持 | 间接支持 |
其中需要进一步说明的:
可靠性: RocketMQ理论上不丢消息, 基于RocketMQ的阿里云MQ可靠性达10个9,Kafka可能会丢消息
定时消息: RocketMQ不支持秒级设置,支持的是按级别设置,总共16个延时级别从几秒到几小时不等
04
最后
目前RocketMQ Go Client项目已经实现了大部分的功能,也在若干国内一线的互联网企业得到了规模应用,但在一些地方还存在不足:
高级特性如消息回溯、消息轨迹、事务消息还处于试用阶段,需要进一步迭代完善
部分的实现还不够优雅,需要2-3个迭代通过局部重构进行优化(当然会保持兼容)
Pull模式的支持还不够完善,会在下一个版本做大量的优化
由于v2.0版本主要的目标是对齐功能,性能方面没有做太大的优化,故存在较大的优化空间
单元测试覆盖率较低
我们后续会陆续的解决上述问题。借此宝地,感谢所有参与Go Client项目的同学,尤其是字节跳动的徐建海和斗鱼的刘思远两位同学,他们除了贡献PR以外,对 Go Clietn 的落地提供了巨大的帮助。同时也期待社区能有更多感兴趣的小伙伴加入到我们队伍里面来~
目前,Go Client 项目正在参加 RocketMQ 社区年度优秀项目评选活动,期待路过Gopher在微信花10s左右为我们项目支持一票:点击阅读原文即可进入投票链接。
05
参考文章
1.阿里开源消息中间件 RocketMQ 的前世今生
http://jm.taobao.org/2016/11/29/apache-rocketmq-history
2.RocketMQ 迈入 50 万 TPS 消息俱乐部
http://jm.taobao.org/2017/03/23/20170323
3.RocketMQ Motivation
http://rocketmq.apache.org/docs/motivation
4.RocketMQ ACL 使用指南
5.RocketMQ在今日头条消息服务平台和容灾体系建设方面的实践与思考