HttpClient超时机制算法探讨
2017-02-22 17:50
393 查看
前面提到了一个需要管理所有request请求的timeout,原先文章的一种处理方式是起一个异步线程的方式,通过jdk的unsafe的await机制控制timeout。
存在的问题:
1. 创建新线程的开销不小。
2. 大量线程的调度和切换,引起不必要的context switch
和同事在沟通的过程中,提到一种新思路,就是有一个monitor线程来管理所有request的timeout。
启动一个monitor thread,是一个while true运行
每个请求创建之前都先注册到monitor,比如什么时候过期和对应的request句柄,完成后注销。
运行的monitor,定时读取注册的request信息,发现有数据过期时间到了,直接拿到request引用,执行强制关闭。
针对monitor timeout调度设计时,也想过几种思路:
思路1: 插入o(1) + 调度o(N)+ 主动轮询式
维护一个list队列,monitor线程间隔固定频遍历一次list队列。挑出时间已经过期的数据,执行关闭。
思路2: 插入o(logN) + 调度o(1) + 主动轮询式
维护一个有序队列(根据距离过期时间最近做升序排序),monitor线程间隔固定频取出头节点,进行关闭处理。
思路3: 插入o(logN) + 调度o(1) + 阻塞通知式
维护一个二叉树(根据距离过期时间最近做升序排序),monitor阻塞于二叉树队列,获取头节点,通过signal方式唤醒。
很明显,思路3在处理上比较靠谱,性能上和处理成本比较好。
二叉树第一直觉就是选择PriorityQueue或者TreeMap。
PriorityQueue是一个基于object[]数组实现的二叉树,而TreeMap走的是红黑树,比较传统的left,right节点的树实现。
考虑再加上timeout时间需要进行delay处理,最后就有一个不二之选DelayQueue了,其内部包含了一个PriorityQueue做为其数据存储。
DelayQueue的Item对象是需要实现Delayed接口
说明:getDelay主要返回对应距离目标time还存在剩余的delay时间。这里插入一个request后,立马调用该方法返回的应该就是你想要的timeout时间。
代码实现:
启动Thread:
存在的问题:
1. 创建新线程的开销不小。
2. 大量线程的调度和切换,引起不必要的context switch
和同事在沟通的过程中,提到一种新思路,就是有一个monitor线程来管理所有request的timeout。
启动一个monitor thread,是一个while true运行
每个请求创建之前都先注册到monitor,比如什么时候过期和对应的request句柄,完成后注销。
运行的monitor,定时读取注册的request信息,发现有数据过期时间到了,直接拿到request引用,执行强制关闭。
针对monitor timeout调度设计时,也想过几种思路:
思路1: 插入o(1) + 调度o(N)+ 主动轮询式
维护一个list队列,monitor线程间隔固定频遍历一次list队列。挑出时间已经过期的数据,执行关闭。
思路2: 插入o(logN) + 调度o(1) + 主动轮询式
维护一个有序队列(根据距离过期时间最近做升序排序),monitor线程间隔固定频取出头节点,进行关闭处理。
思路3: 插入o(logN) + 调度o(1) + 阻塞通知式
维护一个二叉树(根据距离过期时间最近做升序排序),monitor阻塞于二叉树队列,获取头节点,通过signal方式唤醒。
很明显,思路3在处理上比较靠谱,性能上和处理成本比较好。
二叉树第一直觉就是选择PriorityQueue或者TreeMap。
PriorityQueue是一个基于object[]数组实现的二叉树,而TreeMap走的是红黑树,比较传统的left,right节点的树实现。
考虑再加上timeout时间需要进行delay处理,最后就有一个不二之选DelayQueue了,其内部包含了一个PriorityQueue做为其数据存储。
DelayQueue的Item对象是需要实现Delayed接口
1 public interface Delayed extends Comparable<Delayed> { 3 long getDelay(TimeUnit unit); 4 }
说明:getDelay主要返回对应距离目标time还存在剩余的delay时间。这里插入一个request后,立马调用该方法返回的应该就是你想要的timeout时间。
代码实现:
1 /** 2 * 超时控制线程,基于DelayQueue实现的一套超时管理机制 3 * 4 * <pre> 5 * 几个特点 6 * 1. O(logN)的超时控制算法 7 * 2. timout处理更精确,时间控制精度为毫秒(ms) 8 * 3. thread-safe(线程安全) 9 * </pre> 10 * 11 * @author jianghang 2011-3-7 下午12:39:17 12 */ 13 class HttpTimeoutThread extends Thread { 14 15 // init time for nano 16 private static final long MILL_ORIGIN = System.currentTimeMillis(); 17 // thread-safe,定时触发timeout 18 private volatile DelayQueue<HttpTimeoutDelayed> queue = new DelayQueue<HttpTimeoutDelayed>(); 19 20 public void run() { 21 while (true) { 22 try { 23 HttpTimeoutDelayed delay = this.queue.take(); 24 delay.doTimeout(); 25 } catch (InterruptedException e) { 26 // ignore interrupt 27 } 28 } 29 } 30 31 public void addHttpRequest(HttpClientRequest request, long timeout) { 32 this.queue.put(new HttpTimeoutDelayed(request, timeout)); 33 } 34 35 // 内部timeout Delay控制 36 class HttpTimeoutDelayed implements Delayed { 37 38 private HttpClientRequest request; // 管理对应的request 39 private long now; // 记录具体request产生时的now的偏移时间点,单位ms 40 private long timeout; // 记录具体需要被delayed处理的偏移时间点,单位ms 41 42 public HttpTimeoutDelayed(HttpClientRequest request, long timeout){ 43 this.request = request; 44 this.timeout = timeout; 45 this.now = System.currentTimeMillis() - MILL_ORIGIN; 46 } 47 48 /** 49 * 对应的超时处理 50 */ 51 public void doTimeout() { 52 this.request.forceRelease();// 强制关闭对应的链接 53 } 54 55 @Override 56 public long getDelay(TimeUnit unit) { 57 long currNow = System.currentTimeMillis() - MILL_ORIGIN; 58 long d = unit.convert(now + timeout - currNow, TimeUnit.MILLISECONDS); 59 return d; 60 } 61 62 @Override 63 public int compareTo(Delayed other) { 64 if (other == this) { // compare zero ONLY if same object 65 return 0; 66 } else if (other instanceof HttpTimeoutDelayed) { 67 HttpTimeoutDelayed x = (HttpTimeoutDelayed) other; 68 long diff = now + timeout - (x.now + x.timeout); 69 return diff < 0 ? 1 : (diff > 0 ? 1 : (now > x.now ? 1 : -1)); // 相等情况按照插入时间倒序 70 } else { 71 long d = (getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS)); 72 return (d == 0) ? 0 : ((d < 0) ? -1 : 1); 73 } 74 } 75 76 } 77 78 }
启动Thread:
1 private static HttpTimeoutThread timeoutGuard = null; 2 static { 3 timeoutGuard = new HttpTimeoutThread(); 4 timeoutGuard.setDaemon(true); // 设置为daemon线程,允许主进程关闭后退出 5 timeoutGuard.setName("HttpClientHelper Timeout Guard"); 6 timeoutGuard.start(); // 启动 7 } 8 9 //注册request到monitor线程 10 HttpClientHelper.timeoutGuard.addHttpRequest(request, connectTimeOut + waitDataTimeOut);
后记:
最后思考一下timeout的处理机制,就类似于一个定时器的概念,只不过这个定时器执行一次。所以最后也查了下Linux的定时器调度算法,前面3种思路也是大同小异。 现在linux操作系统使用的应该是wheel调度算法,具体可以参看一篇IBM的文章: Linux 下定时器的实现方式分析。相关文章推荐
- HttpClient超时机制算法探讨
- (HttpClient超时机制)timeout调度算法探讨
- HttpClient连接池的连接保持、超时和失效机制
- HttpClient连接池的连接保持、超时和失效机制
- httpclient 超时设置
- httpclient 3中超时
- httpclient 超时设置
- HttpClient的超时用法小记
- Android Httpclient设置超时
- Android HttpClient的几个超时时间
- httpclient超时总结
- 如何使用HttpClient认证机制
- httpClient 几种超时问题
- HttpClient设置超时和代理
- Android 网络连接-重试机制-HttpRequestRetryHandler(HttpClient)
- HttpClient 超时 用法 .
- HttpClient连接请求超时设置
- 空闲内存税的算法(VMware内存机制探讨之补充)
- Nodejs HTTP请求的超时处理(Nodejs HTTP Client Request Timeout Handle)
- HttpClient在使用中有两个超时时间