应用场景:

在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. python开发基础-Pycharm快捷键

    1.编辑(Editing) Ctrl + Space 基本的代码完成(类.方法.属性)Ctrl + Alt + Space 快速导入任意类Ctrl + Shift + Enter 语句完成Ctrl + ...

  2. 用Visio画流程图

    一:基本流程图 主要用于创建流程图.顺序图.信息跟踪图.流程规划图和结构预测图,包含了形状.连接线和链接. 步骤: (1)打开Visio,单击"类别"->"流程图& ...

  3. pycharm 远程修改服务器代码

    首先在本地和服务器上下载pydevd pip3 install pydevd 然后在 设置SSH连接, 出现:java.net.ConnectException:Connection refused ...

  4. nginx添加系统服务(start|stop|restart|reload)

    nginx添加系统服务 1.编写脚本,名为nginx #vim /etc/init.d/nginx #!/bin/bash#chkconfig: - 99 20 #description: Nginx ...

  5. Spark 的两种核心 Shuffle (HashShuffle 与 与 SortShuffle) 的 的工作流程

    1. 参考博客:https://blog.csdn.net/qichangjian/article/details/88039576

  6. Spring asm

    Spring 获取类的详细信息,以及方法时候,是通过asm 字节码进行获取的,在平时中我们也可以依赖spring 进行处理我们的类 //可以获取类的详细信息,比如父类上谁,类上面的注解 ,是否上接口 ...

  7. ES 大批量写入提高性能的策略

    1.用bulk批量写入 你如果要往es里面灌入数据的话,那么根据你的业务场景来,如果你的业务场景可以支持让你将一批数据聚合起来,一次性写入es,那么就尽量采用bulk的方式,每次批量写个几百条这样子. ...

  8. greenplum常见问题及解决方法

    本文链接:https://blog.csdn.net/q936889811/article/details/85612046                文章目录 1.错误:数据库初始化:gpini ...

  9. Servlet实现注册

    1.Servlet实现注册的思路: 2.工程结构 3.功能实现: (1)html实现对数据的收集: <body bgcolor="aqua"> <center&g ...

  10. 【HTML】行内元素与块级元素

    一.行内元素与块级元素的三个区别 1.行内元素与块级元素直观上的区别 行内元素会在一条直线上排列,都是同一行的,水平方向排列 块级元素各占据一行,垂直方向排列.块级元素从新行开始结束接着一个断行. 2 ...