【Java并发】【AQS锁】锁在源码中的应用

1  前言

本节主要记录下基于 AQS 衍生出来的一些常用锁比如:CountDownLatch、‌‌ReentrantLock、Semaphore、‌‌ReentrantReadWriteLock 等他们在源码中的一些应用,好记性不如烂笔头。‌

2  CountDownLatch

2.1  RocketMQ 中 Broker 向 所有NameServer 的注册

RocketMQ 路由注册是通过 Broker 与 NameServer 的心跳功能实现的。 Broker启动时向集群中所有的NameServ巳r发送心跳语句,每隔 30s 向集群中所有NameServer发送心跳包, NameServer 收到 Broker 心跳包时会更新 brokerLiveTable 缓存中 BrokerLivelnfo 的 lastUpdateTimestamp ,然后 Nam巳 Server 每隔 10s 扫描 brokerLiveTable,如果连续 !20s 没有收到心跳包, NameServer将移除该Broker 的路由信息同时关闭 Socket连接。

那么这里比如我有 4个 NameServer,是循环遍历一个一个注册么?其实不是,这里就用到了线程池 + CountDownLatch,我们看下:

public List<RegisterBrokerResult> registerBrokerAll(
    final String clusterName,
    final String brokerAddr,
    final String brokerName,
    final long brokerId,
    final String haServerAddr,
    final TopicConfigSerializeWrapper topicConfigWrapper,
    final List<String> filterServerList,
    final boolean oneway,
    final int timeoutMills,
    final boolean enableActingMaster,
    final boolean compressed,
    final Long heartbeatTimeoutMillis,
    final BrokerIdentity brokerIdentity) {
    final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();
    List<String> nameServerAddressList = this.remotingClient.getAvailableNameSrvList();
    if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
        final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
        requestHeader.setBrokerAddr(brokerAddr);
        requestHeader.setBrokerId(brokerId);
        requestHeader.setBrokerName(brokerName);
        requestHeader.setClusterName(clusterName);
        requestHeader.setHaServerAddr(haServerAddr);
        requestHeader.setEnableActingMaster(enableActingMaster);
        requestHeader.setCompressed(false);
        if (heartbeatTimeoutMillis != null) {
            requestHeader.setHeartbeatTimeoutMillis(heartbeatTimeoutMillis);
        }
        RegisterBrokerBody requestBody = new RegisterBrokerBody();
        requestBody.setTopicConfigSerializeWrapper(TopicConfigAndMappingSerializeWrapper.from(topicConfigWrapper));
        requestBody.setFilterServerList(filterServerList);
        final byte[] body = requestBody.encode(compressed);
        final int bodyCrc32 = UtilAll.crc32(body);
        requestHeader.setBodyCrc32(bodyCrc32);
        // 创建一个计数器锁(数量为 nameServer 的个数)
        final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
        // 循环遍历
        for (final String namesrvAddr : nameServerAddressList) {
            // 往线程池中扔任务
            brokerOuterExecutor.execute(new AbstractBrokerRunnable(brokerIdentity) {
                @Override
                public void run0() {
                    try {
                        RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);
                        if (result != null) {
                            registerBrokerResultList.add(result);
                        }
                        LOGGER.info("Registering current broker to name server completed. TargetHost={}", namesrvAddr);
                    } catch (Exception e) {
                        LOGGER.error("Failed to register current broker to name server. TargetHost={}", namesrvAddr, e);
                    } finally {
                        // 计数器减1
                        countDownLatch.countDown();
                    }
                }
            });
        }
        try {
            // 超时等待都注册完毕
            if (!countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS)) {
                LOGGER.warn("Registration to one or more name servers does NOT complete within deadline. Timeout threshold: {}ms", timeoutMills);
            }
        } catch (InterruptedException ignore) {
        }
    }
    return registerBrokerResultList;
}

可以看到是通过遍历,向线程池中执行任务,每个线程注册完会让锁减1,最后下边 await 等待所有的线程都注册完毕。

2.2  RocketMQ 中 同步消息发送

RocketMQ 5.0版本消息的同步、异步、一次性发送都是基于 Netty 的异步回调机制来做的,那么对于同步发送来说,需要控制当前线程的阻塞以及超时控制,RocketMQ 这里用到了 CountDownLauch 以及它的 await(发送超时时间)来做的,我们看看:

// NettyRemotingAbstract#invokeSyncImpl 同步消息发送的最后落点
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
    final long timeoutMillis)
    throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
    //get the request id
    final int opaque = request.getOpaque();
    try {
        // 创建 ResponseFuture
        // 这里会有把锁 private final CountDownLatch countDownLatch = new CountDownLatch(1);
        // 锁的计数器为1
        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
        this.responseTable.put(opaque, responseFuture);
        final SocketAddress addr = channel.remoteAddress();
        // 基于 netty 的异步回调机制  operationComplete是回调监听
        channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> {
            if (f.isSuccess()) {
                responseFuture.setSendRequestOK(true);
                return;
            }
            responseFuture.setSendRequestOK(false);
            responseTable.remove(opaque);
            responseFuture.setCause(f.cause());
            // 
            responseFuture.putResponse(null);
            log.warn("Failed to write a request command to {}, caused by underlying I/O operation failure", addr);
        });
        // 阻塞当前线程 同步等待执行结果 即countDownLatch.await 来实现
        /**
         * ResponseFuture#waitResponse
         * public RemotingCommand waitResponse(final long timeoutMillis) throws InterruptedException {
         *     // 等待 timeoutMillis 即是发送超时时间 默认3秒
         *     this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
         *     return this.responseCommand;
         * }
         */ 
        RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
        if (null == responseCommand) {
            if (responseFuture.isSendRequestOK()) {
                throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
                    responseFuture.getCause());
            } else {
                throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
            }
        }
        return responseCommand;
    } finally {
        this.responseTable.remove(opaque);
    }
}

3  Semaphore

3.1  RocketMQ 生产者异步发送消息的并发量控制

RocketMQ 中发送消息的方式有一种是异步发送,异步就是将任务提交到异步发送的线程池里,RocketMQ 为了保证服务的稳定,在这里引入了两个信号量锁,一个是控制并发发送数量  一个是控制并发发送的总消息体大小,如下是创建生产者的时候实例化信息:

public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) {
    // 互相引用
    this.defaultMQProducer = defaultMQProducer;
    this.rpcHook = rpcHook;
    // 异步消息队列 队列大小默认 5万
    this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<>(50000);
    // 创建异步发送消息线程池
    this.defaultAsyncSenderExecutor = new ThreadPoolExecutor(
        Runtime.getRuntime().availableProcessors(),
        Runtime.getRuntime().availableProcessors(),
        1000 * 60,
        TimeUnit.MILLISECONDS,
        this.asyncSenderThreadPoolQueue,
        new ThreadFactoryImpl("AsyncSenderExecutor_"));
    // 信号量锁 控制最多允许同时发送多少个消息 默认1万
    if (defaultMQProducer.getBackPressureForAsyncSendNum() > 10) {
        semaphoreAsyncSendNum = new Semaphore(Math.max(defaultMQProducer.getBackPressureForAsyncSendNum(), 10), true);
    } else {
        semaphoreAsyncSendNum = new Semaphore(10, true);
        log.info("semaphoreAsyncSendNum can not be smaller than 10.");
    }
    // 信号量锁 控制最多同时发送消息的大小 默认100M
    if (defaultMQProducer.getBackPressureForAsyncSendSize() > 1024 * 1024) {
        semaphoreAsyncSendSize = new Semaphore(Math.max(defaultMQProducer.getBackPressureForAsyncSendSize(), 1024 * 1024), true);
    } else {
        semaphoreAsyncSendSize = new Semaphore(1024 * 1024, true);
        log.info("semaphoreAsyncSendSize can not be smaller than 1M.");
    }
    ...
}

然后在异步发送的时候,进行信号量锁的申请:

public void executeAsyncMessageSend(Runnable runnable, final Message msg, final BackpressureSendCallBack sendCallback,
    final long timeout, final long beginStartTime)
    throws MQClientException, InterruptedException {
    ExecutorService executor = this.getAsyncSenderExecutor();
    boolean isEnableBackpressureForAsyncMode = this.getDefaultMQProducer().isEnableBackpressureForAsyncMode();
    boolean isSemaphoreAsyncNumAquired = false;
    boolean isSemaphoreAsyncSizeAquired = false;
    int msgLen = msg.getBody() == null ? 1 : msg.getBody().length;
    try {
        // 在开启了降低后台服务压力的时候 默认是不开启的
        if (isEnableBackpressureForAsyncMode) {
            long costTime = System.currentTimeMillis() - beginStartTime;
            isSemaphoreAsyncNumAquired = timeout - costTime > 0
                    // 申请信号量锁(并发数量控制)
                && semaphoreAsyncSendNum.tryAcquire(timeout - costTime, TimeUnit.MILLISECONDS);
            if (!isSemaphoreAsyncNumAquired) {
                sendCallback.onException(
                    new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncNum timeout"));
                return;
            }
            costTime = System.currentTimeMillis() - beginStartTime;
            isSemaphoreAsyncSizeAquired = timeout - costTime > 0
                    // 申请信号量锁(并发消息体大小控制)
                && semaphoreAsyncSendSize.tryAcquire(msgLen, timeout - costTime, TimeUnit.MILLISECONDS);
            if (!isSemaphoreAsyncSizeAquired) {
                sendCallback.onException(
                    new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncSize timeout"));
                return;
            }
        }
        sendCallback.isSemaphoreAsyncSizeAquired = isSemaphoreAsyncSizeAquired;
        sendCallback.isSemaphoreAsyncNumAquired = isSemaphoreAsyncNumAquired;
        sendCallback.msgLen = msgLen;
        executor.submit(runnable);
    } catch (RejectedExecutionException e) {
        if (isEnableBackpressureForAsyncMode) {
            runnable.run();
        } else {
            throw new MQClientException("executor rejected ", e);
        }
    }
}

4  ReentrantLock

4.1  RocketMQ 中生产者启动后向所有的 Broker 发送心跳

在 DefaultMQProducerImpl 生产者启动的最后:

public void start(final boolean startFactory) throws MQClientException {
    ...// 向所有Broker发送心跳包,以维持连接
    this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();

    // 启动定时任务,如心跳检测等
    RequestFutureHolder.getInstance().startScheduledTask(this);
}

sendHeartbeatToAllBrokerWithLock 方法中:

private final Lock lockHeartbeat = new ReentrantLock();
public void sendHeartbeatToAllBrokerWithLock() {
    // 加锁
    if (this.lockHeartbeat.tryLock()) {
        try {
            if (clientConfig.isUseHeartbeatV2()) {
                this.sendHeartbeatToAllBrokerV2(false);
            } else {
                this.sendHeartbeatToAllBroker();
            }
        } catch (final Exception e) {
            log.error("sendHeartbeatToAllBroker exception", e);
        } finally {
            this.lockHeartbeat.unlock();
        }
    } else {
        log.warn("lock heartBeat, but failed. [{}]", this.clientId);
    }
}

5  ReentrantReadWriteLock 

6  小结

归纳总结是对技术的巩固以及认识的增强,看看人家别人怎么用的,什么场景下用的,用法上跟自己的有什么不同,甚至在自己写业务的时候,是不是能直接借鉴下,哈哈哈,受益颇多都,所以大家也要学会总结积累哈。本节就主要记录下锁的一些源码应用(会持续增加),有理解不对的地方欢迎指正哈。

posted @ 2024-10-24 07:56  酷酷-  阅读(19)  评论(0编辑  收藏  举报