Seata通信模块分析

Seata使用Netty实现客户端与服务端基于TCP的通信。服务端通过AbstractNettyRemotingServer处理消息,利用ServerHandler和ExecutorService执行异步操作。客户端则在AbstractNettyRemotingClient基础上增加断线重连和批量发送功能。双方通过RpcMessage对象进行消息交换,使用ProtocolV1Decoder进行解码。通信中涉及的组件如ChannelManager、NettyClientChannelManager管理和优化连接。
摘要由CSDN通过智能技术生成

Seata通信模块分析

Server

整体概览

在seata项目中,client与server是利用netty来完成基于tcp的通信的。在server模块中,RemotingServer接口定义了Server的基本功能,AbstractNettyRemoting实现了远程消息的处理、同步/异步发送、任务超时管理等基本功能,AbstractNettyRemotingServer基于AbstractNettyRemoting近一步进行封装,定义了处理远程消息的ServerHandler,实现了RemotingServer中的业务接口。而NettyRemotingServer则是负责进行业务处理器的组装。

image-20210723173004783.png

AbstractNettyRemoting

在AbstractNettyRemoting中,所有需要同步发送中的消息都被放在一个ConcurrentHashMap中,key是同步消息的id,value是自定义的MessageFuture。同步消息在发送时首先会初始化一个MessageFuture,通过MessageFuture中的CompletableFuture来实现异步结果的阻塞等待与异步结果设置的功能(同Netty中的ChannelPromise)。而AbstractNettyRemoting的构造函数中,会初始化一个定时任务,来定时的从这些Map中清理掉所有已经超时的同步消息请求。

protected Object sendSync(Channel channel, RpcMessage rpcMessage, long timeoutMillis) throws TimeoutException {
   
    if (timeoutMillis <= 0) {
   
        throw new FrameworkException("timeout should more than 0ms");
    }
    if (channel == null) {
   
        LOGGER.warn("sendSync nothing, caused by null channel.");
        return null;
    }

    MessageFuture messageFuture = new MessageFuture();
    messageFuture.setRequestMessage(rpcMessage);
    messageFuture.setTimeout(timeoutMillis);
    futures.put(rpcMessage.getId(), messageFuture);

    channelWritableCheck(channel, rpcMessage.getBody());

    String remoteAddr = ChannelUtil.getAddressFromChannel(channel);
    doBeforeRpcHooks(remoteAddr, rpcMessage);

    channel.writeAndFlush(rpcMessage).addListener((ChannelFutureListener) future -> {
   
        if (!future.isSuccess()) {
   
            MessageFuture messageFuture1 = futures.remove(rpcMessage.getId());
            if (messageFuture1 != null) {
   
                messageFuture1.setResultMessage(future.cause());
            }
            destroyChannel(future.channel());
        }
    });

    try {
   
        Object result = messageFuture.get(timeoutMillis, TimeUnit.MILLISECONDS);
        doAfterRpcHooks(remoteAddr, rpcMessage, result);
        return result;
    } catch (Exception exx) {
   
        LOGGER.error("wait response error:{},ip:{},request:{}", exx.getMessage(), channel.remoteAddress(),
            rpcMessage.getBody());
        if (exx instanceof TimeoutException) {
   
            throw (TimeoutException) exx;
        } else {
   
            throw new RuntimeException(exx);
        }
    }
}

在发送请求之前,首先会检查对应channel是否可写,然后调用自定义的hook,hook的加载利用的是EnhancedServiceLoader实现的SPI,最后会注册一个发送监听器,当发送失败时会关闭channel。值得一提的是在检查channel是否可写时用到了锁,当channel不可用时会调用wait释放锁,而当channel中的channelWritabilityChanged回调后再notify所有的写操作。从全局角度去看不同的channel用的是同一个锁,虽然牺牲了一定的并发效率,但是当channel不可写时通常意味着I/O此时遇到了瓶颈,如果不受控制的将数据写入到channel中,数据将会在channel的ChannelOutboundBuffer缓冲中排队,会造成数据积压的恶性循环引发OOM,用最简单明了的方法避免了这个问题。

private void channelWritableCheck(Channel channel, Object msg) {
   
    int tryTimes = 0;
    synchronized (lock) {
   
        while (!channel.isWritable()) {
   
            try {
   
                tryTimes++;
                if (tryTimes > NettyClientConfig.getMaxNotWriteableRetry()) {
   
                    destroyChannel(channel);
                    throw new FrameworkException("msg:" + ((msg == null) ? "null" : msg.toString()),
                        FrameworkErrorCode.ChannelIsNotWritable);
                }
                lock.wait(NOT_WRITEABLE_CHECK_MILLS);
            } catch (InterruptedException exx) {
   
                LOGGER.error(exx.getMessage());
            }
        }
    }
}

异步消息发送与同步消息发送类似,省去了放入到futureMap的过程。再来看下处理消息的过程,实际上处理过程与Sentinel的处理过程也比较的类似,都是获取到消息体的类型,然后从处理器map中获取到对应的处理器,与Sentinel不一样的是,Sentinel的处理是在netty的I/O线程中直接处理请求的,seata server提供了在ExecutorService中异步执行代码的功能,主要是为了处理一些例如包含数据库的阻塞I/O操作避免阻塞通信的I/O线程。

protected void processMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception {
   
    if (LOGGER.isDebugEnabled()) {
   
        LOGGER.debug(String.format("%s msgId:%s, body:%s", this, rpcMessage.getId(), rpcMessage.getBody()));
    }
    Object body = rpcMessage.getBody();
    if (body instanceof MessageTypeAware) {
   
        MessageTypeAware messageTypeAware = (MessageTypeAware) body;
        final Pair<RemotingProcessor, ExecutorService> pair = this.processorTable.get((int) messageTypeAware.getTypeCode());
        if (pair != null) {
   
            if (pair.getSecond() != null) {
   
                try {
   
                    pair.getSecond().execute(() -> {
   
                        try {
   
                            pair.getFirst().process(ctx, rpcMessage);
                        } catch (Throwable th) {
   
                            LOGGER.error(FrameworkErrorCode.NetDispatch.getErrCode(), th.getMessage(), th);
                        } finally {
   
                            MDC.clear();
                        }
                    });
                } catch (RejectedExecutionException e) {
   
                    LOGGER.error(FrameworkErrorCode.ThreadPoolFull.getErrCode(),
                        "thread pool is full, current max pool size is " + messageExecutor.getActiveCount());
                    if (allowDumpStack) {
   
                        String name = ManagementFactory.getRuntimeMXBean().getName();
                        String pid = name.split("@")[0];
                        int idx = new Random().nextInt(100);
                        try {
   
                            Runtime.getRuntime().exec("jstack " + pid + " >d:/" + idx + ".log");
                        } catch (IOException exx) {
   
                            LOGGER<
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值