zoukankan      html  css  js  c++  java
  • 基于项目内部的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数据库并分片时,连接池会将请求打在相应的数据节点上,并对数据进行聚合;
  • 相关阅读:
    ArrayList removeRange方法分析
    LinkedHashMap源码分析(基于JDK1.6)
    LinkedList原码分析(基于JDK1.6)
    TreeMap源码分析——深入分析(基于JDK1.6)
    51NOD 2072 装箱问题 背包问题 01 背包 DP 动态规划
    51 NOD 1049 最大子段和 动态规划 模板 板子 DP
    51NOD 1006 最长公共子序列 Lcs 动态规划 DP 模板题 板子
    8月20日 训练日记
    CodeForces
    CodeForces
  • 原文地址:https://www.cnblogs.com/hlkawa/p/12773943.html
Copyright © 2011-2022 走看看