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数据库并分片时,连接池会将请求打在相应的数据节点上,并对数据进行聚合;
  • 相关阅读:
    解决THINKCMF后台文章的相册图集只能上传一个图片的问题
    WordPress 在function.php 文件中方法中the_XXX方法失效
    Windows 增强版任务管理器-Process Explorer
    从Linux服务器下载网站文件
    SQLSERVER 免费对比数据库结构和数据的工具支持:SQL Server 2012, SQL Server 2008 and SQL Server 2005
    [UE4]蓝图继承方法:重写父类方法时,增加父类方法调用
    [UE4]蓝图使用GameMode重构
    [UE4]控制台命令,生成机器人
    [UE4]区分敌我
    [UE4]AnimOffset偏移动画
  • 原文地址:https://www.cnblogs.com/hlkawa/p/12773943.html
Copyright © 2011-2022 走看看