应用场景:

在RPC框架中,使用Netty作为高性能的网络通信框架时,每一次服务调用,都需要与Netty服务端建立连接的话,很容易导致Netty服务器资源耗尽。所以,想到连接池技术,将与同一个Netty服务器地址建立的连接放入池中维护,同一个地址的连接确保只建立一次。这样,凡是连接同一个Netty服务器的客户端,拿到的都是同一个连接,不需要新建连接,就可以大大减少连接的个数,从而大幅度提升服务器性能。

用一张图说明一下设计思路:

解释一下,途中主要定义了两个类,ConnectClient是负责管理连接的,是一个抽象类,init和send是抽象方法,而NettyClient是负责与Netty服务器通信的,它继承了ConnectClient,并实现了它的几个抽象方法,init方法中会与Netty服务器建立连接,send方法会向Netty服务器发送消息。

值得注意的是,ConnectClient类中有一个非抽象方法,就是asyncSend(),它里面调用了自己的send()抽象方法。抽象方法不能直接调用,必须拿到NettyClient这个具体实现类的实例对象才能调。这个asyncSend()方法有一个关键的入参,即Class<? extends ConnectClient> netImpl,它是一个Class类型,是从外部传进来的,所以这就比较有灵活性了,好处就是这个ConnectClient类不需要依赖任何具体的实现,只要传进一个自己的子类的Class即可,它就可以用这个Class通过反射的方式创建出具体的实现类的实例对象,然后调用其send方法。可以理解成ConnectClient用asyncSend方法包装了NettyClient的send方法,目的是让外部不要直接调用NettyClient中的send方法,而是调用自己的asyncSend方法,然后在这个asyncSend方法中,会先获取连接,再调用NettyClient中的send方法发送消息。

模拟代码:

下面就通过几段代码来模拟Tcp客户端和Tcp服务器端建立连接并发送消息的场景。
这里并没有真的使用Netty框架,因为本文不是讲怎么使用Netty框架,而是分享如何管理连接。

首先,模拟一个Tcp服务端程序(就当做是Netty的服务器):

 /**
* 模拟TCP服务端
*
* @author syj
*/
public class NetChannel { /**
* 建立连接
*
* @param host
* @param port
*/
public void connect(String host, int port) {
System.out.println("模拟连接TCP服务器成功: host=" + host + ",port=" + port);
} /**
* 发送消息
*
* @param msg
*/
public void send(String msg) {
System.out.println("模拟向TCP服务器发送消息成功:" + msg);
}
}

定义一个Tcp客户端,负责与Netty服务器通信:

 /**
* 模拟TCP客户端
*
* @author syj
*/
public class NetClient extends ConnectClient { // 模拟TCP服务器
private NetChannel channel; /**
* 建立连接
*
* @param address 格式 host:port, 例如 192.168.1.103:9999
* @throws Exception
*/
@Override
public void init(String address) throws Exception {
if (address == null || address.trim().length() == 0) {
throw new RuntimeException(">>>> address error");
}
String[] split = address.split(":");
if (split.length != 2) {
throw new RuntimeException(">>>> address error");
}
String host = split[0];
int port = Integer.valueOf(split[1]);
channel = new NetChannel();
channel.connect(host, port);
} /**
* 发送消息
*
* @param msg
* @throws Exception
*/
@Override
public void send(String msg) throws Exception {
channel.send(msg);
}
}

连接管理类:

该类使用一个ConcurrentHashMap作为连接池,来保存与TCP服务器建立的连接,key是TCP服务器的地址,value是连接对象。

由于是多线程环境,为保证线程安全问题,使用synchronized加锁,避免一个连接被创建多次。

由于可能会有很多针对同一个TCP服务器的连接请求,使用lockClientMap来管理锁,同一个TCP服务器的请求使用同一把锁,保证同一个TCP服务器的连接只创建一次。

这样既保证了线程安全,又能降低性能消耗。

 import java.util.concurrent.ConcurrentHashMap;

 /**
* TCP连接管理
*
* @author syj
*/
public abstract class ConnectClient { /**
* 建立连接
*
* @param address
* @throws Exception
*/
public abstract void init(String address) throws Exception; /**
* 发送消息
*
* @param msg
* @throws Exception
*/
public abstract void send(String msg) throws Exception; /**
* 发送消息
*
* @param address
* @param msg
* @param netImpl
* @throws Exception
*/
public static void asyncSend(String address, String msg, Class<? extends ConnectClient> netImpl) throws Exception {
ConnectClient connect = ConnectClient.getConnect(address, netImpl);
connect.send(msg);
} // 连接池
private static volatile ConcurrentHashMap<String, ConnectClient> connectClientMap;
// 锁
private static volatile ConcurrentHashMap<String, Object> lockClientMap = new ConcurrentHashMap<>(); /**
* 获取连接
* 确保同一个TCP服务器地址对应的连接只建立一次
*
* @param netImpl
* @return
* @throws Exception
*/
public static ConnectClient getConnect(String address, Class<? extends ConnectClient> netImpl) throws Exception {
// 创建连接池
if (connectClientMap == null) {
synchronized (ConnectClient.class) {
if (connectClientMap == null) {
connectClientMap = new ConcurrentHashMap<>();
}
}
} // 获取连接
ConnectClient connectClient = connectClientMap.get(address);
if (connectClient != null) {
return connectClient;
} // 获取锁,同一个地址使用同一把锁
Object lock = lockClientMap.get(address);
if (lock == null) {
lockClientMap.putIfAbsent(address, new Object());
lock = lockClientMap.get(address);
}
synchronized (lock) {
connectClient = connectClientMap.get(address);
if (connectClient != null) {
return connectClient;
} // 新建连接
ConnectClient client = netImpl.newInstance();
client.init(address);
// 放入连接池
connectClientMap.put(address, client);
}
connectClient = connectClientMap.get(address);
return connectClient;
}
}

任务类用于并发连接测试:

 import java.util.UUID;

 /**
* 任务
*
* @author syj
*/
public class Task implements Runnable { private Class<? extends ConnectClient> netType;// 客户端类型
private String address;
private long count; public Task(String address, long count, Class<? extends ConnectClient> netType) {
this.address = address;
this.count = count;
this.netType = netType;
} @Override
public void run() {
try {
String uuid = UUID.randomUUID().toString().replace("-", "");
String msg = String.format("%s \t %s \t %s \t %s", Thread.currentThread().getName(), count, address, uuid);
ConnectClient.asyncSend(address, msg, netType);
// 模拟业务耗时
Thread.sleep((long) (Math.random() * 1000));
} catch (Exception e) {
e.printStackTrace();
}
}
}

测试类(模拟了10个TCP服务器的地址和端口):

通过一个死循环来模拟测试高并发场景下,连接的线程安全和性能表现。

 import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* 模拟TCP客户端并发获取连接发送消息
*
* @author syj
*/
public class App { // TCP服务器通信地址和端口
public static final String[] NET_ADDRESS_ARR = {
"192.168.1.101:9999",
"192.168.1.102:9999",
"192.168.1.103:9999",
"192.168.1.104:9999",
"192.168.1.105:9999",
"192.168.1.106:9999",
"192.168.1.107:9999",
"192.168.1.108:9999",
"192.168.1.109:9999",
"192.168.1.110:9999"
}; public static ExecutorService executorService = Executors.newCachedThreadPool();
public static volatile long count;// 统计任务执行总数 public static void main(String[] args) {
while (true) {
try {
Thread.sleep(5);// 防止 CPU 100%
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.execute(new Task(NET_ADDRESS_ARR[(int) (Math.random() * 10)], ++count, NetClient.class));
executorService.execute(new Task(NET_ADDRESS_ARR[(int) (Math.random() * 10)], ++count, NetClient.class));
}
}
}

测试结果:

 模拟连接TCP服务器成功: host=192.168.1.107,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-14 14 192.168.1.107:9999 3f31022b959e4962b00b0719fa206416
模拟连接TCP服务器成功: host=192.168.1.108,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-37 37 192.168.1.108:9999 2e4e4c6db63145f190f76d1dbe59f1c4
模拟连接TCP服务器成功: host=192.168.1.106,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-49 49 192.168.1.106:9999 e50ea4937c1c4425b647e4606ced7a1f
模拟连接TCP服务器成功: host=192.168.1.103,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-17 17 192.168.1.103:9999 21cfcd3665aa4688aea0ac90b68e5a22
模拟连接TCP服务器成功: host=192.168.1.102,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-25 25 192.168.1.102:9999 bbdbde3e28ab4ac0901c1447ac3ddd3f
模拟连接TCP服务器成功: host=192.168.1.101,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-10 10 192.168.1.101:9999 08cc445cc06a44f5823a8487d05e3e30
模拟连接TCP服务器成功: host=192.168.1.105,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-45 45 192.168.1.105:9999 3e925cf96b874ba09c59e63613e60662
模拟连接TCP服务器成功: host=192.168.1.104,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-53 53 192.168.1.104:9999 2408dab5c0ca480b8c2593311f3ec7d5
模拟向TCP服务器发送消息成功:pool-1-thread-13 13 192.168.1.105:9999 5a3c0f86046f4cb99986d0281e567e31
模拟向TCP服务器发送消息成功:pool-1-thread-36 36 192.168.1.107:9999 b85d9d79461d4345a2da8f8dd00a572a
模拟向TCP服务器发送消息成功:pool-1-thread-9 9 192.168.1.102:9999 c2895f68a33745d7a4370034b6474461
模拟向TCP服务器发送消息成功:pool-1-thread-41 41 192.168.1.102:9999 a303193a58204e7fadaf64cec8eaa86d
模拟向TCP服务器发送消息成功:pool-1-thread-59 59 192.168.1.101:9999 08785c0acfc14c618cf3762d35055e9b
模拟向TCP服务器发送消息成功:pool-1-thread-54 54 192.168.1.107:9999 6fa8e3939a904271b03b78204d4a146a
模拟向TCP服务器发送消息成功:pool-1-thread-15 15 192.168.1.102:9999 229989d1405b49cdb31052b081a33869
模拟向TCP服务器发送消息成功:pool-1-thread-7 7 192.168.1.107:9999 8e3c8d1007a34a01b166101fae30449c
模拟连接TCP服务器成功: host=192.168.1.109,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-8 8 192.168.1.109:9999 ca63dd93685641d19c875e4809e9a8dc
模拟向TCP服务器发送消息成功:pool-1-thread-1 1 192.168.1.106:9999 cd9f473797de46ef8361f3b8b0a6d575
模拟向TCP服务器发送消息成功:pool-1-thread-27 27 192.168.1.102:9999 872d825fd64e409d8b992e12e0372daa
模拟向TCP服务器发送消息成功:pool-1-thread-3 3 192.168.1.103:9999 baace7f8f06242f68cac0c43337e49cf
模拟向TCP服务器发送消息成功:pool-1-thread-39 39 192.168.1.108:9999 bc0d70348f574cbba449496b3142e518
模拟向TCP服务器发送消息成功:pool-1-thread-55 55 192.168.1.106:9999 95ba7c57a1d84c18a6ab328eb01e85f1
模拟向TCP服务器发送消息成功:pool-1-thread-38 38 192.168.1.108:9999 a571001c573c4851a4bb1e0dcb9a204a
模拟向TCP服务器发送消息成功:pool-1-thread-4 4 192.168.1.104:9999 dcdd6093afc345e39453883cf049fa21
模拟向TCP服务器发送消息成功:pool-1-thread-28 28 192.168.1.106:9999 0ba4362898f84335bb336d17780855fc
模拟向TCP服务器发送消息成功:pool-1-thread-47 47 192.168.1.108:9999 db993121a9934558942a09a9d9a8e03f
模拟向TCP服务器发送消息成功:pool-1-thread-30 30 192.168.1.102:9999 a0e50592deca471b9c5982c83d00f303
模拟连接TCP服务器成功: host=192.168.1.110,port=9999
模拟向TCP服务器发送消息成功:pool-1-thread-51 51 192.168.1.110:9999 41703aba37ca47148d23d6826264a05a
模拟向TCP服务器发送消息成功:pool-1-thread-5 5 192.168.1.102:9999 15f453cc0a7743f79dc105963f39f946
模拟向TCP服务器发送消息成功:pool-1-thread-52 52 192.168.1.105:9999 9ca521963bf84c418335e7702e471fa9
模拟向TCP服务器发送消息成功:pool-1-thread-40 40 192.168.1.101:9999 bec1d265b7dc46f5afebc42fea10a313
模拟向TCP服务器发送消息成功:pool-1-thread-26 26 192.168.1.104:9999 a44662dc498045e78eb531b6ee6fc27b
模拟向TCP服务器发送消息成功:pool-1-thread-11 11 192.168.1.109:9999 6104c4fd2dab4d44af86f0cd1e3e272d
模拟向TCP服务器发送消息成功:pool-1-thread-24 24 192.168.1.105:9999 344025da2a6c4190a87403c5d96b321e
模拟向TCP服务器发送消息成功:pool-1-thread-22 22 192.168.1.110:9999 aa0b4c48527446738d28e99eef4957f5
模拟向TCP服务器发送消息成功:pool-1-thread-23 23 192.168.1.107:9999 79f5fc4278164cd68ac1a260322e6f68
模拟向TCP服务器发送消息成功:pool-1-thread-56 56 192.168.1.109:9999 39c38939ced140058f25fe903a3b1f4f
模拟向TCP服务器发送消息成功:pool-1-thread-18 18 192.168.1.109:9999 c29ea09b5f264b488f3e15e91c5f2bd5

可见,与每个TCP服务器的连接只会建立一次,连接得到复用。

Netty服务器连接池管理设计思路的更多相关文章

  1. 关于.NET大数据量大并发量的数据连接池管理

    转自:http://www.cnblogs.com/virusswb/archive/2010/01/08/1642055.html 我以前对.NET连接池的认识是错误的,原来以为在web.confi ...

  2. 教你正确地利用Netty建立连接池

    一.问题描述 Netty是最近非常流行的高性能异步通讯框架,相对于Java原生的NIO接口,Netty封装后的异步通讯机制要简单很多. 但是小K最近发现并不是所有开发人员在使用的过程中都了解其内部实现 ...

  3. Spring学习11-Spring使用proxool连接池 管理数据源

    Spring 一.Proxool连接池简介及其配置属性概述   Proxool是一种Java数据库连接池技术.是sourceforge下的一个开源项目,这个项目提供一个健壮.易用的连接池,最为关键的是 ...

  4. Spring使用proxool连接池 管理数据源

    一.Proxool连接池简介及其配置属性概述 Proxool是一种Java数据库连接池技术.是sourceforge下的一个开源项目,这个项目提供一个健壮.易用的连接池,最为关键的是这个连接池提供监控 ...

  5. loadrunner:判断是否服务器连接池瓶颈

    分析Web Resources中的Connections per second可以判断是否服务器连接池瓶颈. connections per second会给出两种不同状态的连接数:中断的连接和新建的 ...

  6. HttpPoolUtils 连接池管理的GET POST请求

    package com.nextjoy.projects.usercenter.util.http; import org.apache.http.Consts; import org.apache. ...

  7. Redis缓存连接池管理

    import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.util.Assert;import ...

  8. JAVA自定义连接池原理设计(一)

    一,概述 本人认为在开发过程中,需要挑战更高的阶段和更优的代码,虽然在真正开发工作中,代码质量和按时交付项目功能相比总是无足轻重.但是个人认为开发是一条任重而道远的路.现在本人在网上找到一个自定义连接 ...

  9. MySQL-第十五篇使用连接池管理连接

    1.数据库连接池的解决方案是: 当应用程序启动时,系统主动建立足够的数据库连接,并将这些连接组成一个连接池.每次应用程序请求数据库连接时,无需重新打开连接,而是从连接池中取出已有的连接使用,使用完后不 ...

随机推荐

  1. 2019-2020-1 20199301《Linux内核原理与分析》第五周作业

    第四章·系统调用的三层机制(上) 本章的重点在于用户态程序如何触发系统调用? 一.用户.内核.中断 IntelX86有四种不同的执行级别.Linux操作系统中只采用了其中的0和3两个特权级别,分别对应 ...

  2. Java中使用BufferedReader的readLine()方法和read()方法来读取文件内容

    目标:读文件 编程时,有很多时候需要读取本地文件,下面介绍一下读取方式: 读单行文件 package com; import java.io.*; import java.util.ArrayList ...

  3. Spring源码窥探之:xxxAware接口

    Aware接口是一个标志性接口,继承此接口的接口xxxAware的实现类,在容器创建完成后,会回调实现方法,下面举例: 1. 有很多xxxAware接口,下面举两个例子 /** * descripti ...

  4. c字符数组之两头堵模型

    char *其实就是char[length]的首元素地址  实验环境:centos7下qt5.11 中文char类型占3个字节 char[length]="特别车队"其实等价于ch ...

  5. kth-largest-element

    Find the kth largest element in an unsorted array. Note that it is the kth largest element in the so ...

  6. Spring入门(一)——IOC

    1. IOC定义 Inversion of Control,减低计算机代码间的耦合度,对象的创建交给外部容器完成,不用再new了 2. 流程 2.1 创建Bean对象 package bean; pu ...

  7. CF1185F Two Pizzas 状压

    你发现 pizza 种类数不会很多,状压一下就可以了 code: #include <bits/stdc++.h> #define M 11 #define N 100005 #defin ...

  8. Excel、CSV文件处理

    1.Excel中以\t做为列分隔符,换行符作为行分隔符 使用c#导出excel的时候,当数字太长时,如身份证号,导出后的excel就会显示为科学计数法.如“511122154712121000”会显示 ...

  9. 修改windows网络参数,让上网更快

    管理员运行CMD,运行 netsh int tcp show global 查询活动状态... TCP 全局参数 ------------------------------------------- ...

  10. BurpSuite经常拦截firefox报文如success.txt的解决办法

    因为工作需要经常使用Burp对收发报文进行检测,平时习惯使用火狐浏览器,但是火狐浏览器经常进行一些登录状态的检测,导致Burp拦截中出现大量的火狐报文,如http://detectportal.fir ...