Netty Client实战——高并发连接池方案

文章目录


更多博客内容见个人博客: https://itboyer.github.io

引言

最近研究了Netty的相关技术,用于实施高并发场景下的消息通信,期间搜集了大量资料,围绕着netty的channel连接池的设计,这个稍微有些复杂的主题,做了大量功课,其中牵扯到蛮多技术点,要想在网上找到相关的又相对完整的参考文章,确实不太容易。在此记录一下实现的方案,用于技术沉淀。

首先,阅读本文之前需要具备一些基础知识:

  1. socket通信和长短连接
  2. 知道Netty的执行流程和相关API操作
  3. 理解什么是TCP半包,了解Netty提供的粘包和拆包解码器

在此贴出一些学习过程中遇到的优秀Blog
官方文档
分隔符解码器处理半包问题
netty实战-netty client连接池设计(Netty官方新版本中已经实现了简单的连接池,可以学习连接池的设计思想)

线程模型

首先,整个系统架构的线程模型如下:

线程模型

同步通信机制

其次我们需要关注单线程内的同步请求和响应
抛出问题:
Q1:如何实现基于Netty的“请求-响应”同步通信机制

Netty提供了异步IO和同步IO的统一实现,但是我们的需求其实和IO的同步异步并无关系。我们的关键是要实现请求-响应这种典型的一问一答交互方式。用于实现微服务之间的调用和返回结果获取,要实现这个需求,需要解决两个问题:
a. 请求和响应的正确匹配。
当服务端返回响应结果的时候,怎么和客户端的请求正确匹配起来呢?解决方式:通过客户端唯一的RequestId,服务端返回的响应中需要包含该RequestId,这样客户端就可以通过RequestId来正确匹配请求响应。
b. 请求线程和响应线程的通信。
因为请求线程会在发出请求后,同步等待服务端的返回。因此,就需要解决,Netty在接受到响应之后,怎么通知请求线程结果。

方案:使用LinkedBlockingQueue阻塞任务队列,使用take()获取相应的返回结果
首先需要对每一个请求标识一个全局唯一的标识,下面贴出核心代码:

NettyChannelPoolHandler.java

@Slf4j
public class ChannelTaskThread implements Callable<String> {
   


    /**
     * netty channel池
     */
    final NettyClientPool nettyClientPool = NettyClientPool.getInstance();

    private String message;

    public ChannelTaskThread(String message){
   
        this.message = message;
    }

    @Override
    public String call(){
   
         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        //同一个线程使用同一个全局唯一的随机数,保证从同一个池中获取和释放资源,同时使用改随机数作为Key获取返回值,时间戳+6位随机数
        long random = Long.valueOf(sdf.format(new Date())) * 1000000 + Math.round(Math.random() * 1000000);

        Channel channel = nettyClientPool.getChannel(random);
        log.debug("在链接池池中取到的Channel: "+ channel.id());
        UnpooledByteBufAllocator allocator = new UnpooledByteBufAllocator(false);
        ByteBuf buffer = allocator.buffer(20);
			//使用固定分隔符的半包解码器
        String msg = message + DataBusConstant.DELIMITER;
        buffer.writeBytes(msg.getBytes());
        NettyClientHandler tcpClientHandler = channel.pipeline().get(NettyClientHandler.class);
        ChannelId id = channel.id();
        log.info("SEND SEQNO[{}] MESSAGE AND CHANNEL id [{}]",random,id);

        String serverMsg = tcpClientHandler.sendMessage(buffer, channel);
        NettyClientPool.release(channel);
        return "请求SEQNO["+random+"] "+ serverMsg;
    }
}

NettyClientHandler.java

@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
   

    /**
     * 使用阻塞式LinkedBlockingQueue,对响应结果保存
     * 用于记录通道响应的结果集
     */
    private static final Map<Long, LinkedBlockingQueue<String>> RESULT_MAP = new ConcurrentHashMap<>();

    public String sendMessage(ByteBuf message,Channel ch) {
   
        LinkedBlockingQueue<String> linked = new LinkedBlockingQueue<>(1);
        //获取channel中存储的全局唯一随机值
        Long randomId = ch.attr(AttributeKey.<Long>valueOf(DataBusConstant.RANDOM_KEY)).get();
        RESULT_MAP.put(randomId,linked);
        ch.writeAndFlush(message);
        String res = null;
        try {
   
            //设置3分钟的获取超时时间或者使用take()--获取不到返回结果一直阻塞
            res = RESULT_MAP.get(randomId).poll(3,TimeUnit.MINUTES);
            RESULT_MAP.remove(randomId);
        }catch (Exception e){
   
            e.printStackTrace();
        }
        return res;
    }

    @Override
    public void channelRead
  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值