流量路由需求:转转RocketMQ设计实现

流量路由

随着业务的快速迭代,测试环境的频繁发布与稳定性的冲突愈演愈烈,在此背景下,转转自研了流量路由来解决这个问题,详见转转测试环境的服务治理实践

我们把测试环境分为稳定环境跟动态环境,稳定环境开发不允许部署,每天自动同步线上代码,动态环境就是业务迭代的功能点,一般是单独申请的一台机器。

流量路由对rpc与mq实现了: 只要动态环境有部署,调用或者mq消费就会打到动态环境,其余走稳定环境。

动态环境会自动部署一个nginx + 转转的总网关(Entry),业务只需要申请一台动态机,把自己修改的项目部署上去;
前端页面再修改一下域名,改到动态机,这样转转的app所有的地方都不会报错,并且改了的项目一定会打到流量。

需求

业务只需在动态机部署Entry+X(修改服务集),调用链路中所有途经X的服务都走动态机,其他走稳定环境。

流量路由需求

转换MQ需求

基于起始动态机IP的路由,动态机优先消费,如果动态机消费者不在线,由稳定环境消费。

设计与实现

流量路由本质是IP标签的路由,进程内标签通过ThreadLocal (TTL)传递,进程间标签通过网络传递

标签可拆解为流量打标签与节点打标签,对于mq来说

  • 流量打标:生产消息时,从上下文TheadLocal中获取路由标签,消息体携带IP标签,您可以在测试环境看到每条消息的properties内都带有ROUTER_TAG字段
  • 节点打标:即为消费组打标签,有两种方式
    • 第一种是为业务的consumer对象增加特定标记,标记是稳定环境消费者or动态环境消费者,但是由于mq Rebalance机制,同一个消费组分摊队列,我们期待的是每个动态环境都可以消费所有队列,所以此方式不可取
    • 第二种是给consumer group增加前缀,其中,动态环境增加ip前缀,稳定环境增加test前缀。我们把整个稳定环境作为一个消费组,每个动态环境单独作为一个消费组;这种方式简单易操作,并可以满足我们实际的需求

我们已经使用标签区分出了动态环境与稳定环境的消费组,剩下的就是消息的路由过滤了:拉取消息时,根据消息内标签与消费组做匹配,如果匹配成功才会被消费。

由于流量路由本机不在线,由稳定环境消费,所以我们需要知道消费组的在线情况。这里需要提一下mq的心跳机制:每个mq客户端会定时向broker上报心跳,broker内可以知道每个消费组的在线情况。所以消息过滤逻辑放在了broker端过滤。

MQ设计

关于消息过滤

目前消息过滤的实现比较简单,但并不完美,它会降低mq的QPS,MQ流量路由上到线上需要评估。

mq消息数据在CommitLog里,拉消息时,CommitLog内的数据会从mmap直接发送到SocketBuffer(取决于参数:transferMsgByHeap=false)。

流量路由之后,每条消息消费时,都要把CommitLog内数据从mmap加载到broker堆内存内,因为要读取消息的Properties内的RouterTag。

可以说,目前的流量路由实现打破了MQ的部分设计,增加了内存拷贝次数,消费时效性降低,broker堆内存压力增大;

同时也增加了mmap占用时间,进而可能影响到发消息,导致消息发送超时。

有一个可行的方案,但相对复杂,我们可以参考mq本身的tag过滤的实现。

ConsumerQueue内存储了定长的消息TAG的hashCode,拉消息时,broker根据ConsumerQueue内的TAG hashcode做过滤,ConsumerQueue很小,数据读到堆内存的影响可以忽略不计。

那么,我们也可以把RouterTag存储在ConsumerQueue内,存储内容为定长的IP,拉消息时仅需要读ConsumerQueue就可以做好过滤。

不过这样的话,我们就修改了ConsumerQueue的存储协议,如何把broker上历史数据做好兼容?

其实说简单也很简单,我们只需启动一个新的SLAVE,新的SLAVE从头同步MASTER的CommitLog数据;

新的SLAVE会自动根据MASTER的CommitLog数据重建ConsumerQueue,重建的ConsumerQueue即是新的存储协议的数据,再把SLAVE切成MASTER即可。

你可能感兴趣的:(流量路由需求:转转RocketMQ设计实现)