RocketMQ操作路由表ReadWriteLock

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

前言

持续的深度思考和创作和坚持还是很难的,最近项目比较忙,公司的人力资源减少,很多事情都需要推动,挺难的。

很多空降的架构师,方法论一套一套的,比如线上出现了一个问题各种方法论撤了很多,其实对于实际的问题解决帮助很小。有些架构师专注于编码规范,sona检查,注释规范,或者codereview的时候,show方法论,比如异步的捕获,异常流程分类处理,但很多时候不需要这么复杂,什么搞本地异常表,线程异常中断处理一堆,但就是不深入思考业务,解决实际问题。

吐槽一下,作为专注技术的大头兵,还是多研究研究优秀的源码吧。

RocketMQ操作路由表ReadWriteLock

基础知识:
java并发锁ReentrantReadWriteLock读写锁源码分析

所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读锁(锁降级);如果一个线程对资源加了读锁,其他线程可以继续加读锁。

在看RocketMQ路由注册机制的源码的时候可以看到里面用了很多读写锁,ReadWriteLock,为什么用读写锁呢? 我们先看下路由注册的主要场景

image.png

1、Broker 每 30s 向 NameServer 发送心跳包,心跳包中包含主题的路由信息(主题的读写队列数、操作权限等),NameServer 会通过 HashMap 更新 Topic 的路由信息,并记录最后一次收到 Broker 的时间戳。
2、NameServer 以每 10s 的频率清除已宕机的 Broker,NameServr 认为 Broker 宕机的依据是如果当前系统时间戳减去最后一次收到 Broker 心跳包的时间戳大于 120s。
3、消息生产者以每 30s 的频率去拉取主题的路由信息,即消息生产者并不会立即感知 Broker 服务器的新增与删除。

public class RouteInfoManager {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
    private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
    private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
    private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
    private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
    private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;

我们可以看到Topic管理用的是HashMap,我们知道HashMap是线程不安全的,在并发场景用hashMap是需要加锁的?那我们又有疑问为什么不用ConcurrentHashMap呢?或者用synchronized同步锁的。

当然是可以的,但是用ReadWriteLock 的性能更高,ReadWriteLock 适用于读多写少的情况,合理使用可以进一步提高并发效率。

使用写锁的场景

public void deleteTopic(final String topic) {
    try {
        try {
            this.lock.writeLock().lockInterruptibly();
            this.topicQueueTable.remove(topic);
        } finally {
            this.lock.writeLock().unlock();
        }
    } catch (Exception e) {
        log.error("deleteTopic Exception", e);
    }
}

在看下读锁的场景

// 查询所有topic列表
public byte[] getAllTopicList() {
    TopicList topicList = new TopicList();
    try {
        try {
            this.lock.readLock().lockInterruptibly();
            topicList.getTopicList().addAll(this.topicQueueTable.keySet());
        } finally {
            this.lock.readLock().unlock();
        }
    } catch (Exception e) {
        log.error("getAllTopicList Exception", e);
    }

    return topicList.encode();
}

DEMO

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {

    private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    public static void main(String[] args) {
        new Thread(()->read()).start();
        new Thread(()->read()).start();
        new Thread(()->write()).start();
        new Thread(()->write()).start();
    }

    private static void read() {
        String threadName = Thread.currentThread().getName();
        readLock.lock();
        try {
            System.out.println(threadName + "-获取读锁(readLock),读取数据...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(threadName + "-释放读锁(readLock)");
            readLock.unlock();
        }
    }

    private static void write() {
        String threadName = Thread.currentThread().getName();
        writeLock.lock();
        try {
            System.out.println(threadName + "-获取写锁(writeLock),写入数据...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(threadName + "-释放读锁(writeLock)");
            writeLock.unlock();
        }
    }
}

image.png

总结

读写锁的规则,读读共享、其他都互斥(写写互斥、读写互斥、写读互斥)。所以在读多写少的场景下,ReadWriteLock的性能是高于synchronized和ReentrantLock的

猜你喜欢

转载自juejin.im/post/7112073205333901349
今日推荐