带连接池的netty客户端核心功能实现剖解
带连接池的netty客户端核心功能实现剖析
带连接池的netty的客户端核心功能实现剖析
本文为原创,转载请注明出处
源码地址:
https://github.com/zhangxianwu/light-netty-client
1、连接池
由于TCP连接的建立和关闭分别会经历三次握手和四次挥手,而三次握手和四次挥手都是系统开销很大的操作。如果每次一个新的请求发起时,都为其新建一个连接,在请求处理完毕后,再将这个新的连接关闭,这样处理的代价是高昂的,尤其是在请求本身的处理逻辑比较简单时,那么新建和关闭连接的开销在整个请求处理中占的比例就会越大。
因此,需要采用连接池,将连接缓存起来,以便后续的复用。
一个TCP连接可用一个四元组标示(源IP、源端口、目的IP、目的端口),当必须为一个新的请求建立连接后,服务端处理完该请求并通过该连接发送响应,客户端接收到响应后,将该连接还回到池中。
1.1 连接的存储
池采用ConcurrentHashMap<String, LinkedBlockingQueue<Channel>>存储连接:
Key:
目的IP + 目的端口(对于一个固定的客户端来说,它所处的源IP和源端口是不变的);
value:
如果只为每个目的IP和目的端口缓存一个连接,那么在高并发的场景或者请求本身处理比较耗时的情况下,请求获取连接的延时会比较严重,因此在当前请求从池里获取连接超时后,需要根据实际的需要新建连接,并将新建的连接存储下来,所以采用LinkedBlockingQueue<Channel>实现多个连接的存储。
然而每个连接的存储会有一定的内存开销,所以在高并发的场景下,不能无限制的创建和存储连接,需要做最大数量的限制。如果能够从连接创建的地方做到最大数量限制,那么最终缓存的连接数量也就实现了最大数量的限制(最大连接数的控制在后面分析)。
1.2 连接的入池和出池
a)连接何时入池:
往pipeline添加一个handler(取名为NettyChannelPoolHandler),并继承SimpleChannelInboundHandler,实现channelRead0方法,当服务端返回响应,并被客户端decode后,会发出channelRead的inbound事件,该事件的处理会经过channelRead0方法,在该方法中,如果发现decode后的msg是HttpContent类型,且响应头中不包含“Connection: close”,则在通知调用方响应结果已接受后,将该连接返回到池中。
说明:如果服务端tomcat设置了maxKeepAliveRequests参数,则一个keep-alive连接处理的请求数达到这个配置后,在该连接处理的最后一个请求的响应头中,就会设置“Connection: close”,表示连接会被关闭。所以这个连接不需要放回到池中。
b)连接何时出池:
1、新请求从池中获取连接
2、连接被关闭
1.3 最大连接数的控制
采用ConcurrentHashMap<String, Semaphore>记录当前每个目的IP和目的端口组合能够新建的连接数量。
a)信号量的初始化:
客户端初始化时可以指定每个目的IP和目的端口组合的最大允许存活的连接数量。如果未指定,则设置为默认值,譬如200,表示对于某个目的IP和目的端口组合,可以同时允许最大存活200个连接。
b)信号量的减少:
每次新建连接前,基于信号量做tryAquire操作,如果tryAquire成功,则再执行新建连接操作。
c)信号量的恢复:
新建连接失败或连接被关闭,则需要基于信号量的tryRelease操作进行恢复。
在netty中,connect操作会返回channelFuture,为其添加listener:如果future的isSuccess返回false,则说明新建连接失败,需要恢复信号量;如果isSuccess返回true,则说明新建连接成功,此时为新建channel的closeFuture添加listener,执行信号量恢复操作。
在新建连接成功后,会执行发送请求操作,即调用channel的writeAndFlush操作,该操作也会返回一个future,需要为该future添加CLOSE_ON_FAILURE(netty提供)这个listener。
1.4 空闲连接处理:
在业务低峰期,池中的连接大部分处于空闲状态,是一种浪费,因此需要对空闲连接进行清理。
Netty提供了IdleStateHandler,通过指定允许的最大空闲时间,当某个连接空闲时间超过这个值后,会发出userEventTriggered的Inbound事件,在NettyChannelPoolHandler中捕获该事件,如果发现事件的类型是IdleStateEvent,则调用channel.close()方法关闭连接,这样,在之前为该连接添加的listener就会收到close事件,然后将连接出池,并恢复控制最大连接数的信号量。
1.5 连接的获取策略:
基于以下先后顺序获取连接:
策略1:首先从池中获取连接(调用LinkedBlockingQueue.pool(),不等待,获取不到立即返回null),如果获取不到连接,则进入第二种策略
策略2:创建新连接,如果信号量已用完或者创建连接失败,则进入第三种策略
策略3:再次从池中获取连接(调用LinkedBlockingQueue.(long timeout, TimeUnit unit),等待timeoute时间后,如果获取不到连接,则返回null)。如果此时还是获取不到连接,则抛出获取连接失败的异常。
当并发程度很大或者服务端处理请求比较耗时,如果信号量的初始值设置的比较小,则会导致部分请求获取连接有一定的延时,甚至会获取连接超时。此时,可以采用以下措施之一:
a)增大信号量的初始值
b)在策略2中,由调用方指定是否在信号量已用完的情况下,强制创建新连接。注意对于强制创建的连接,不需要执行信号量的acquire和release操作,也不需要进行入池和出池操作。那么如何区分一个连接是正常创建的还是强制创建的呢?基于netty,可以通过channel的attr(AttributeKey<T> key)方法进行标示。
2、如何通知调用方响应结果已收到
在netty中,一切都是异步的,那么调用方通过客户端发起请求后,如何得知请求已处理完毕,响应结果已返回?
在每次获取连接前,首先新建一个自定义的NettyResponseFuture,在获取连接后,将该future通过channel的attr(AttributeKey<T> key)方法添加到channel中,然后在发送请求后,将该NettyResponseFuture立即返回给调用方。NettyResponseFuture中包含以下属性和方法:
private final CountDownLatch latch = new CountDownLatch(1);
private volatile boolean isDone = false;
private volatile boolean isCancel = false;
private final AtomicBoolean isProcessed = new AtomicBoolean(false);
private volatile NettyHttpResponseBuilder responseBuilder;
private volatile Channel channel;
public boolean cancel(Throwable cause) {
if (isProcessed.getAndSet(true)) {
return false;
}
responseBuilder = new NettyHttpResponseBuilder();
responseBuilder.setSuccess(false);
responseBuilder.setCause(cause);
isCancel = true;
latch.countDown();
return true;
} public NettyHttpResponse get() throws InterruptedException, ExecutionException {
latch.await();
return responseBuilder.build();
} public NettyHttpResponse get(long timeout, TimeUnit unit) throws TimeoutException,
InterruptedException {
if (!latch.await(timeout, unit)) {
throw new TimeoutException();
}
return responseBuilder.build();
} public boolean done() {
if (isProcessed.getAndSet(true)) {
return false;
}
isDone = true;
latch.countDown();
return true;
}
通过get方法获取返回结果,在响应未返回时,latch.await()会一直阻塞。而latch.countdown只有在cancel和done方法中被调用。那什么时候会调用cancel或done方法呢呢?三个时机:
1) connect失败,则connect返回的future中注册的listener会调用cancel方法;
2) channel被关闭,则channel对应的的closefuture中注册的Listner会调用cancel方法;
3) 服务端正常返回响应时,NettyChannelPoolHandler的channelRead0方法会调用done方法
说明:
a)由于isProcessed可能会被netty的io线程和外部线程并发修改,因此采用atomicBoolean的cas操作进行修改
b)由于isDone和isCancel只会被一个线程修改,因此不需要采用AtomicBoolean类型。但这两个属性会被其他线程访问,因此需要定义为volatile,保证线程间的可见性
带连接池的netty客户端核心功能实现剖解的更多相关文章
- Netty自带连接池的使用
一.类介绍1.ChannelPool——连接池接口 2.SimpleChannelPool——实现ChannelPool接口,简单的连接池实现 3.FixedChannelPool——继承Simple ...
- 带有连接池的Http客户端工具类HttpClientUtil
一.背景 业务开发中,经常会遇到通过http/https向下游服务发送请求.每次都要重复造轮子写HttpClient的逻辑,而且性能.功能参差不齐.这里分享一个高性能的.带连接池的通用Http客户端工 ...
- 曹工杂谈:花了两天时间,写了一个netty实现的http客户端,支持同步转异步和连接池(1)--核心逻辑讲解
背景 先说下写这个的目的,其实是好奇,dubbo是怎么实现同步转异步的,然后了解到,其依赖了请求中携带的请求id来完成这个连接复用:然后我又发现,redisson这个redis客户端,底层也是用的ne ...
- 支持并发的httpclient(基于tcp连接池以及netty)
闲来无事,将曾经自己写的一个库放出来吧. . 有的时候会有这样子的需求: (1)serverA通过HTTP协议来訪问serverB (2)serverA可能会并发的像B发送非常多HTTP请求 类似于上 ...
- springboot使用自带连接池连接postgre
Application配置spring.datasource.url=jdbc:postgresql://***:5432/postgresspring.datasource.username=pos ...
- Hibernate的查询,二级缓存,连接池
Hibernate的查询,二级缓存,连接池 1.Hibernate查询数据 Hibernate中的查询方法有5中: 1.1.Get/Load主键查询 使用get或者load方法来查询,两者之间的区别在 ...
- 非常老的话题 SQLSERVER连接池
原文:非常老的话题 SQLSERVER连接池 非常老的话题 SQLSERVER连接池 写这篇文章不是说要炒冷饭,因为园子里有非常非常多关于SQLSERVER连接池的文章,但是他们说的都是引用MSDN里 ...
- Hibernate查询、连接池、二级缓存
Hibernate第三天: 1. 对象状态 2. session缓存 3. lazy懒加载 4. 映射 一对一对映射 组件/继承映射 目标: 一.hibernate查询 二.hibernate对连接池 ...
- Hibernate【查询、连接池、逆向工程】
前言 在Hibernate的第二篇中只是简单地说了Hibernate的几种查询方式....到目前为止,我们都是使用一些简单的主键查询阿...使用HQL查询所有的数据....本博文主要讲解Hiberna ...
随机推荐
- CUDA程序设计(二)
算法设计:直方图统计 直方图频数统计,也可以看成一个字典Hash计数.用处不是很多,但是涉及CUDA核心操作:全局内存.共享内存.原子函数. 1.1 基本串行算法 这只是一个C语言练习题. #def ...
- android布局详解
http://blog.163.com/zhangzheming_282/blog/static/117920962013072502787/ AbsoluteLayout——绝对布局 必 ...
- Win10 利用安装盘启用 .NET Framework 3.5
以管理员身份运行命令提示符,在“管理员:命令提示符”窗口中输入以下命令:dism.exe /online /enable-feature /featurename:netfx3 /Source:D:\ ...
- OpenResty 通过二级域名做跳转
if ( $host ~* (\b(?!www\b)\w+)\.\w+\.\w+ ) { #获取nba.test.com域名中的nba set $subdomain $1; } location / ...
- ACM 另一种阶乘问题
另一种阶乘问题 时间限制:3000 ms | 内存限制:65535 KB 难度:1 描述 大家都知道阶乘这个概念,举个简单的例子:5!=1*2*3*4*5.现在我们引入一种新的阶乘概念,将原来 ...
- 洛谷 P1803 凌乱的yyy Label:Water 贪心
题目背景 快noip了,yyy很紧张! 题目描述 现在各大oj上有n个比赛,每个比赛的开始.结束的时间点是知道的. yyy认为,参加越多的比赛,noip就能考的越好(假的) 所以,他想知道他最多能参加 ...
- APP测试流程
1 APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间,一般测试时间为两三周(即15个工作日),根据项目情况以及版本质量可适当缩短或延长测试时间.正式测试前先向 ...
- Linux下多进程编程之exec函数语法及使用实例
exec函数族 1)exec函数族说明 fork()函数用于创建一个子进程,该子进程几乎复制了父进程的全部内容,但是,这个新创建的进程如何执行呢?exec函数族就提供了一个在进程中启动另一个程序执行的 ...
- OSG+VS2010+win7环境搭建---OsgEarth编译
OSG+VS2010+win7环境搭建---OsgEarth编译 转:http://www.cnblogs.com/hnfxs/p/3161261.html Win7下 osg+vs2010环境搭建 ...
- linux下定时重启tomcat
工具/原料 linux tomcat 方法/步骤 编写tomcat_shutdown.sh: #!/bin/sh export JAVA_HOME=/home/oracle/jdk1..0_67/ e ...