从源码角度研究RocketMQ不仅可以深入研究一款高性能中间件的实现原理,更是可以大大提示编码能力,更是能加强对高并发等基础框架的实战能力。
首先我们来看一下RocketMQ NameServer的路由注册机制,从中窥探读写锁的使用场景。
1、RocketMQ Nameserver 路由注册机制
-
Broker 每 30s 向 NameServer 发送心跳包,心跳包中包含主题的路由信息(主题的读写队列数、操作权限等),NameServer 会通过 HashMap 更新 Topic 的路由信息,并记录最后一次收到 Broker 的时间戳。
-
NameServer 以每 10s 的频率清除已宕机的 Broker,NameServr 认为 Broker 宕机的依据是如果当前系统时间戳减去最后一次收到 Broker 心跳包的时间戳大于 120s。
-
消息生产者以每 30s 的频率去拉取主题的路由信息,即消息生产者并不会立即感知 Broker 服务器的新增与删除。
在上述场景中,Broekr 会定时向 NameServer 汇报路由信息,即更新操作,消费者、生产在拉取消息、发送消息时会经常去查询 NameServer 中的路由信息,即所谓的读操作。
在 Broker NameServer 中使用 HashMap 来存储 topic 的路由信息。“众所周知”HashMap 在多线程环境中并不安全,多线程环境下,需要加锁。
如下伪代码:
public List getTopicInfo(String topicName) {
synchronized(routerInfoHashMap) {
return routerInfoHashMap.get(topicName);
}
}
public void updateTopicInfo(String topicName,List ...) {
synchronized(routerInfoHashMap) {
return routerInfoHashMap.put(topicName, ...);
}
}
上面的代码有什么缺点呢?
试想一下,如果同一个时间,多个发送者,多个消费者都向nameserver查询topic的路由信息,你会发现所有的读请求都会排队执行,其并发性能可想而知的低下,此时读写锁就派上用场了。
读写锁的使用如下:
上述代码来源于org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager。
这样所有的读操作能并发执行,极大的提高了性能。
读写锁的规则:读锁与写锁互斥,读锁与读锁兼容,即可同时获取锁,举例如下:
例如同一个时间依次有线程 t1 t2 t3 申请读锁,t4,申请写锁,t5 申请读锁:
首先 t1,t2,t3 成功获取锁,t4,t5阻塞等待。
等t1,t2,t3释放锁后,t4获取锁,t5阻塞,然后t4释放锁,t5获取锁。
本文就介绍到这里了,读写锁的使用场景大家是否Get到了,如果本文对你有所帮助,希望一键三连表示支持。
见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过技术分享实现职场蜕变,成长为RocketMQ社区优秀布道师、大厂资深架构师,出版《RocketMQ技术内幕》一书,在CSDN中记录了我的成长历程,欢迎大家关注,私信,一起交流进步。
分享笔者一个硬核的RocketMQ电子书:
获取方式:微信搜索【中间件兴趣圈】,回复RMQPDF即可获取。
转载:https://blog.csdn.net/prestigeding/article/details/115361473