基于项目内部的MQ手写连接池

最近,项目收到中间件团队的报告,我们的应用连接他们的中间件(项目内部的MQ)连接数太大了,要求我们做一些调整。然后看了下我们的代码,发现我们接收和发送MQ消息的方式是每次新建一个连接然后关闭连接(询问了之前的同事,目前因为某些原因现在只能采取这种方式发送消息),但是每连接一次都会new一个对象出来,感觉挺占用资源的,而且万一没有及时释放掉资源,一直占用连接资源就对服务端压力也很大,讨论了一番决定采用连接池方式,这样我们可以自己控制连接数量。

我们基于【2W1h】方式来讨论连接池:什么是连接池(what)?我们内部项目的MQ为什么需要连接池(why)?怎么样做一个基于我们内的的MQ做连接池 (how)?

what: 什么是连接池?

深入思考连接池的本质,但不要思考的过于复杂~~

连接: 是网络中用于传输数据的通道; ”连接“才是我们要真正去使用的对象,“池”是用来管理多个连接的一种方式。

池: 是一种容器的概念,做存储的。在编程中我们往往使用数组,链表,队列,Map来表示。
所以“连接池”中的“连接”肯定是已经建立的好的长连接,比如tcp连接,websocket连接等,即取即用,用完放回。

跟据下游类型,我们常见的有数据库连接池,缓存连接池,服务连接池。在编程中,我们还会经常碰到进程池,线程池,协程池,内存池,对象池等。

why: 我们内部项目的MQ为什么需要连接池?

其实开头的第一句话已经回答了这个问题。

连接池除了能非常方便的对连接进行管理外,而且在高吞吐的连接池大大提高数据的传输的效率。提高效率主要在于下面两个方面:

1.避免反复的三次握手和四次握手

长连接的建立需要进行三次握手,而连接的释放需要进行四次握手,这是发生在系统层面的两个动作,对于单条连接来说耗时微乎其微,
但来高吞吐场景时,耗时则不能忽略。所以连接池的及取即用和用完放回的特性,避免了大量三次握手和四次握手的无效耗时,
从而节省系统资源。

2. 增加并行车道,实现全双工并行

数据通信包括单工,半双工和全双工。单工通信如下图,数据只能从A到B,不符合访问下游服务的场景。

 

半双工通信如下图,数据可以从A到B,也可以从B到A,但是同一时刻只能一个方向上的数据传输,通道利用率是50%。

 

 全双工通信如下图,可同时存在从A到B和从B到A的数据传输,通道利用率是100%。长连接就是全双工通信。

 

在IO密集型的互联网应用中,一条全双工通信通道仍然无法满足数据吞吐的需求时,该如何解决?在互联网性能测试指标中有个这样一个公式:QPS(吞吐量)= 并发数/平均响应时间,在平均相响应时间不变的情况下,适度增加并发数可以提升吞吐量;所以采用多条双全工通信的方式可以在一定程度上提高吞吐量,而连接池就是最好的实现方式。

总结一下:为什么需要连接池?

1.方便管理连接
2.避免反复的三次握手和四次握手
3.更好的实现双全工并行

how: 怎么样做一个基于我们内的的MQ做连接池?

实现一个连接池,最关键的是均衡和保活,如下图:

我们项目组实现方式的思路如下:

1.MqClient类,初始化连接池,设置初始连接数量,最大连接数量,获取资源,释放资源,伪代码如下:

MqClient {

 ConcurrentHashMap<String queueKey,ArrayBlockingQueue<Mqconnection> queue> queueMap;
 long initConnect = 5;
 long maxConnect = 10;
 AtomicLong capacity;
 
 
 init(){
    for(){
    // 初始化5个连接
    mqConnecions.offer(mqConnecion);
    capacity.getAndIncrement();
    }        
 }
 
 get(String queueKey){    
    // 根据queKey获取queue 
     
     // 获取mqCoonnection    
     if(mqConnection.peek()){
        return mqConnection.poll();
     }
     // 队列没有到最大值继续创建连接
     if(capacity.get() < maxConnect){
         // 继续创建新的连接,并塞入队列
     }
     // 递归重试get()    
 }
 
 free(String queueKey, Mqconnection connection){
     // 如果可用并且队列大小没有超过最大连接值塞入队列,否则主动断开连接并丢弃
 }    
}

2.MqClientHelper(辅助类,负责监听和保活)

MqClientHelper {
    MqClient mqclient;
    
    listenMqAcLog(){
     // 监听mq相关的日志    
    }
    
    // 发送心跳
    sendHeartbeat(){
        
    }
}

当然上面我们项目的第一版的方案的还是比较的粗糙的,还有很多地方不完善,比如有些方法需要加锁操作等,而且通常我们的连接池会连接下游多个节点。如下图所示:

 一般来说,相对比较完善的连接池,有如下几个特性:

1.高可用:下游任意一个server宕机时,连接池关闭相关无效连接,防止被client访问;
2.可扩展:下游增加一个server节点时,连接池会发现并建立到新server节点的连接,供client访问;
3.负载均衡:连接池会根据下游server的服务能力的高低分配数据请求;
4.中间件:当下游server是类似MYSQL数据库并分片时,连接池会将请求打在相应的数据节点上,并对数据进行聚合;
posted @ 2020-11-22 19:35  Brian_Huang  阅读(463)  评论(0编辑  收藏  举报