HttpClient超时机制算法探讨

前面提到了一个需要管理所有request请求的timeout,原先文章的一种处理方式是起一个异步线程的方式,通过jdk的unsafe的await机制控制timeout。 

存在的问题:

1.  创建新线程的开销不小。

2.  大量线程的调度和切换,引起不必要的context switch

和同事在沟通的过程中,提到一种新思路,就是有一个monitor线程来管理所有request的timeout。

  1. 启动一个monitor thread,是一个while true运行
  2. 每个请求创建之前都先注册到monitor,比如什么时候过期和对应的request句柄,完成后注销。 
  3. 运行的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 下定时器的实现方式分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值