你不知道的-Route |Go主题月

360 阅读4分钟

概述

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分钟
连接数(等同于订阅数)TPSCPU使用率平均耗时(受网关的性能影响)
30001W1+60~70%100ms+
100001W+70+%300ms+

如果切换为批量操作的模式,性能将会显著提升。

这里CPU使用率不能达到100%可能是由于网关的的处理速度达不到要求导致的。

压测例子请见: route-go

为什么这么快?

  1. 只专注于高性能发布订阅场景,舍弃了一定的消息可靠性(万有一失的设计理念)
  2. 1+N的协程模式(1个主协程+N个IO协程)。主协程负责所有订阅树操作,从而避免引入锁(锁带来的性能问题)。
  3. 批量化操作。批量化能减少IO的次数,也能提高带宽的利用效率

设计

Route总体可以分为三大模块,如下图:

Route层级结构.PNG

1. Broker

  1. Broker主要负责维持客户端的连接,读取连接来的请求,以及将请求的处理结果返回给连接,所做的事情比较简单

  2. Broker目前只支持Websocket协议

2. Service

  1. Service主要职责是处理Broker或者Swarm转发过来的请求

  2. 目前的请求类型由4种: LocalBatchTopicOp(内含Sub,Unsub),LocalQuery,BatchPubMessage

  3. 为什么要把Sub和Unsub请求放在同一个结构体呢?其实是利用Chan的特性来保证订阅操作FIFO特性(例如:先Sub后Unsub,先Unsub后Sub将会产生不同的结果)

  4. Broker来的BatchPubMessage和Swarm来的BatchPubMessage的处理行为有些许不一样

    • Broker: 会推送给所有订阅该Topic的订阅者

    • Swarm: 只会推送给订阅该Topic并且 LocalSubscriberType == LocalSubscriberDirect 的订阅者

  5. Service内部维护着一个 Topic->List 的map, 内部协程结构为1+N,1个主协程,所有map操作只能由主协程执行。N个IO协程,主要将结果写入到对应的订阅者(写入都是IO操作)

  6. 对于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;
}

其含义如下图:

状态抽象图.PNG

  • 第2层的PeerName每个节点都有,在集群范围内唯一
  • 第3层表示订阅的Topic
  • 第4层是个int64,是TimeStamp+IsAdd(Add状态可以等同于此Topic被订阅的意思)的组合,最低位代表着IsAdd,其63位表示最后更新IsAdd的毫秒时间戳

对于某个Topic都可能出现以下状态迁移:

状态迁移图.PNG

但是myself和other的Topic状态迁移会有些许不同:

3.1 myself
local \ remoteADDDEL
强制合并为DELDELTime如果还没过期则合并为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 \ remoteADDDEL
合并为ADDDELTime如果还没过期则合并为DEL状态,否则则忽视
ADD取两者最新的版本取两者最新的版本
DEL取两者最新的版本取两者最新的版本

特别的: 每过一段时间会对State进行扫描删除DELTime已经过期的Topic,从而将状态从DEL转为无(gc)

最后

项目地址: github.com/wiqun/route

本人的其他开源项目:

联系邮箱: vivask770@163.com