队列作为一种比较抽象的数据结构,在程序世界中被广泛的应用,而实现方式和形态也各式各样,有使用进程内堆栈实现的,如stl库中的queue;有基于管道、Shmem实现的,如常见的同机进程间通信模型,而随着分布式系统应用越来越广泛,跨机通信的场景需来需多,面临的问题不仅是消息投递问题,分布式系统普适性的挑战也随着应用场景的多样性而越来越多。
一个优秀的分布式消息队列,个人分析应该具备以下的能力:高吞吐、低时延(因场景而异),传输透明,伸缩性强,有冗灾能力,一致性顺序投递,同步+异步的发送方式,完善的运维和监控工具,开源。但上面的能力有一些在设计理念上可能是相悖的,或者是应用场景不同,在最终的实现上会有所侧重。以腾讯互娱内部广泛使用的TBUS\TBUSD为例,最为看重的是一致性顺序投递以及低时延,但传输上做不到透明,需要使用者手工初始化队列,了解整个网格的拓扑,另外故障后也需要手工处理。和TSFG的负责人沟通,新版本在研发中的TBUS正在解决这个问题,从而减少使用者的学习和运营成本。
从消息传输模型上,大致可以抽象为以下几种:
点对点模型(Point-to-point)
基础模型中,只有一个发送者、一个接收者和一个分布式队列。如下图所示:
生产者消费者模型(Producer–consumer)
如果发送者和接收者都可以有多个部署实例,甚至不同的类型;但是共用同一个队列,这就变成了标准的生产者消费者模型。在该模型,三个角色一般称为生产者(Producer)、分布式队列(Queue)、消费者(Consumer)。
发布订阅模型(PubSub)
如果只有一类发送者,发送者将产生的消息实体按照不同的主题(Topic)分发到不同的逻辑队列。每种主题队列对应于一类接收者。这就变成了典型的发布订阅模型。在该模型,三个角色一般称为发布者(Publisher),分布式队列(Queue),订阅者(Subscriber)。
看下业界,开源的分布式消息队列有很多种,侧重的维度也略有不同,包括支持的消息模型也有一些差异,如果按是否有独立进程来看,可以分为两个大类:
既然业界有如此丰富的组件,是否可以找到一种比TBUS更优的同时也适合游戏服务器的组件呢?带着这个问题,作者对Kafka、Nats、Nats-streaming进行了测试,主要关注时延、吞吐量、消息安全性这三个维度上。测试方法如下:搭建了两台机器,发送者和接收者在同一台物理机,broker部署在另一台机,两台机器ping时延在0.8ms左右。 测试结果如下:
得益于JAVA的跨平台能力,Kafka不需要安装,可直接运行。 因为Kafka借助zookeeper进行节点的故障探测与路由管理,因些需要先启动zookeeper。 测试版本为kafka_2.10-0.10.0.1 ,测试的时候碰到一些小问题,启动不成功,后面看了下,kafka启动不成功,常见的有两个问题,一是内存不够,因为java虚拟机运行时需要配置内存大小,如果内存不够可以调整下运行脚本。 二是jmx服务,注释掉bin/kafka-run-class.sh 下的这几行就ok。
# JMX settings #if [ -z "$KAFKA_JMX_OPTS" ]; then # KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false " #fi # JMX port to use #if [ $JMX_PORT ]; then # KAFKA_JMX_OPTS="$KAFKA_JMX_OPTS -Dcom.sun.management.jmxremote.port=$JMX_PORT " #fi
因为Kafka本周对消息是持久化的,可靠性不需要测试,因此主要关注时延和吞吐量,测试结果显示吞吐量能达到65M/s, 很显然是进行过了合包,但时延的表现不是很理想,无送包有多小,消息发送后,Consumer收到的时延的最小极限在32ms,调整了很多参数,都无然进行一步优化。 路由模式上,Kafka只支持发布\订阅模型,即一个消息只能被一个访阅者收到,在这一点Nats更丰富一些。 整理的测试结果来看,Kafka做为一个分布式日志\流水\经营分析系统,还是很不错的,难怪很多2B的系统以及电商金融类的产品都在使用。
正如上面介绍的,Nats是由原Ruby-Nats作者Derek Collison设计开发,目前由Apcera维护,由golang语言编写,研发团队只有5个人,受限于团队的规模,因此在API的支持上有一定的局限性。感兴趣的同学可以看下源码,协程的功能划分十分清晰,一些用法也很巧妙。 Nats和Nats-streaming最大的区别在于,Nats异步模式需要发送者自己处理消息丢失的问题,即不保证消息的“100%投递成功”,也不做消息暂存, 而Nats-streaming解决了这个问题。 在吞吐量、时延测试上,二者表现也十分优异。 作者实测结果显示,100W条消息投递时耗时在3-4s,1秒可以投递30-40w条消息,1k条消息投递耗时在1毫秒,完全可以满足像多人游戏等对时延比较挑剔的场景。
从路由模式上,Nats的支持非常丰富,支持以下三种:
Publish Subscribe
发布订阅模式,一对多,一个消息多个订阅者都可以收到,类似广播的场景。支持同步和异步调用。
Request Reply
发送应答模式,Nats支持一对一和一对多的发送应答模式,可以手工指定有几个订阅者可以收到。发送应答模式采用同步调用。
Queue
队列模式,一个消息发布后,只有一个访阅者会收到,支持同步和异步调用。这个模式对于一些无状态处理服务十分有用,比如数据仓库的无状态接入层,接入层可部署多台物理机组成一个集群,每个物理机无状态,采用这个方式即达到了冗灾能力,同时也可以保证每一个消息只会被处理一次。
从测试结果来看,Nats-streaming在安全性、时延、吞吐量上都可以达到一个比较好的水平,唯一不足的是API对各语言支持的还不够,CAPI可能要到2017年才能release.
分布式消息队列种类很多,没有精力一一测试,在网上找了一个比较权威的测试结果跟大家分享下。
测试包量和发布速率如下所示,每次测试持续时间在30S以上。
256B requests at 3,000 requests/sec (768 KB/s)
1KB requests at 3,000 requests/sec (3 MB/s)
5KB requests at 2,000 requests/sec (10 MB/s)
1KB requests at 20,000 requests/sec (20.48 MB/s)
1MB requests at 100 requests/sec (100 MB/s)
时延统计为一次发布并收到回包的整体耗时,在不同的包量和发布量的分布如下:
Nats VS Redis
Kafka VS RabbitMQ
http://bravenewgeek.com/tag/message-queues/
http://bravenewgeek.com/dissecting-message-queues/
https://dzone.com/articles/benchmarking-nats-streaming-and-apache-kafka