概述
Route是一个高性能的发布订阅中间件
- 去中心化(任何节点都是对等的)
- 弹性伸缩,高可用和分区容错性(AP),最终一致性
- 高订阅数,高性能(见下面性能测试)
- 支持批量化操作
- ...
什么地方适合使用Route?
Route适合在需要高性能高订阅数,但是又允许消息丢失的场景下使用。(route不保证消息一定送达)
安装
docker运行
$ docker run -d --name route -p 4000:4000 --restart=unless-stopped routeio/route
k8s部署集群
集群将会默认暴露出30400端口用于访问,如果不需要此端口则需要自行修改k8s.yaml文件
$ git clone https://github.com/wiqun/route.git
$ cd route
$ kubectl.exe apply -f .\k8s.yaml
更详细的安装及配置请参考: route
性能测试
- Intel(R) Xeon(R) X5650
- cpu限制为1核
- 打压机与被打压机处于不同网段(数据传输需要经过网关)
- 每条连接定时发布一条消息到对应的主题并且持续压测10分钟
| 连接数(等同于订阅数) | TPS | CPU使用率 | 平均耗时(受网关的性能影响) |
|---|---|---|---|
| 3000 | 1W1+ | 60~70% | 100ms+ |
| 10000 | 1W+ | 70+% | 300ms+ |
如果切换为批量操作的模式,性能将会显著提升。
这里CPU使用率不能达到100%可能是由于网关的的处理速度达不到要求导致的。
压测例子请见: route-go
为什么这么快?
- 只专注于高性能发布订阅场景,舍弃了一定的消息可靠性(万有一失的设计理念)
- 1+N的协程模式(1个主协程+N个IO协程)。主协程负责所有订阅树操作,从而避免引入锁(锁带来的性能问题)。
- 批量化操作。批量化能减少IO的次数,也能提高带宽的利用效率
设计
Route总体可以分为三大模块,如下图:
1. Broker
-
Broker主要负责维持客户端的连接,读取连接来的请求,以及将请求的处理结果返回给连接,所做的事情比较简单
-
Broker目前只支持Websocket协议
2. Service
-
Service主要职责是处理Broker或者Swarm转发过来的请求
-
目前的请求类型由4种:
LocalBatchTopicOp(内含Sub,Unsub),LocalQuery,BatchPubMessage -
为什么要把Sub和Unsub请求放在同一个结构体呢?其实是利用Chan的特性来保证订阅操作FIFO特性(例如:先Sub后Unsub,先Unsub后Sub将会产生不同的结果)
-
Broker来的
BatchPubMessage和Swarm来的BatchPubMessage的处理行为有些许不一样-
Broker: 会推送给所有订阅该Topic的订阅者
-
Swarm: 只会推送给订阅该Topic并且
LocalSubscriberType == LocalSubscriberDirect的订阅者
-
-
Service内部维护着一个 Topic->List 的map, 内部协程结构为1+N,1个主协程,所有map操作只能由主协程执行。N个IO协程,主要将结果写入到对应的订阅者(写入都是IO操作)
-
对于
Subscriber接口有两个比较关键的方法,ID()标识订阅者具有唯一性,ConcurrentId()主要用于防止IO并发写入问题
3. Swarm
Swarm主要负责集群状态的互相同步,以及新节点的发现
Swarm使用Gossip协议来进行集群状态的同步
Swarm维护着一个类CRDT状态(下面简称为state),其Proto文件为:
package crdt;
message State {
map<uint64, TopicMap> set = 1;
}
message TopicMap {
map<string, int64> map= 1;
}
其含义如下图:
- 第2层的PeerName每个节点都有,在集群范围内唯一
- 第3层表示订阅的Topic
- 第4层是个int64,是TimeStamp+IsAdd(Add状态可以等同于此Topic被订阅的意思)的组合,最低位代表着IsAdd,其63位表示最后更新IsAdd的毫秒时间戳
对于某个Topic都可能出现以下状态迁移:
但是myself和other的Topic状态迁移会有些许不同:
3.1 myself
| local \ remote | ADD | DEL |
|---|---|---|
| 无 | 强制合并为DEL | DELTime如果还没过期则合并为DEL状态,否则则忽视 |
| ADD | 由于remote的TimeStamp不可能大于local的,所以都是忽视此变更(如果出现说明是未知错误) | 强制合并为ADD |
| DEL | 由于remote的TimeStamp不可能大于local的,所以都是忽视此变更(如果出现说明是未知错误) | 取两者最新的版本(由于存在节点断线重连的情况,所以取最新版本) |
特别的: 每过一段时间会对State进行扫描删除DELTime已经过期的Topic,从而将状态从DEL转为无(gc)
值得注意的是,myself区域绝大部分TopicMap状态变化只能是本节点自己更新(其他节点只是合并更新),只有一种情况除外:当本节点断线时,其他节点会强制设置myself区域的TopicMap所有item为Del状态并且DelTime时间
3.2 other
| local \ remote | ADD | DEL |
|---|---|---|
| 无 | 合并为ADD | DELTime如果还没过期则合并为DEL状态,否则则忽视 |
| ADD | 取两者最新的版本 | 取两者最新的版本 |
| DEL | 取两者最新的版本 | 取两者最新的版本 |
特别的: 每过一段时间会对State进行扫描删除DELTime已经过期的Topic,从而将状态从DEL转为无(gc)
最后
项目地址: github.com/wiqun/route
本人的其他开源项目:
- github.com/MMMzq/bot_t… (一个Flutter Toast库)
- github.com/auto-flutte… (一个完整的Flutter自动化测试方案)
联系邮箱: vivask770@163.com