(HttpClient超时机制)timeout调度算法探讨

简介:

继上一篇文章: 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> {  
2.  
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);  
11.  
12.<span style="font-size: large;"><span style="white-space: normal;"><strong>  
13.</strong></span></span>  

后记:

最后思考一下timeout的处理机制,就类似于一个定时器的概念,只不过这个定时器执行一次。所以最后也查了下linux的定时器调度算法,前面3种思路也是大同小异。 

 

现在linux操作系统使用的应该是wheel调度算法,具体可以参看一篇IBM的文章: Linux 下定时器的实现方式分析

 

其对应的几种算法复杂度: 

 

实现方式 StartTimer StopTimer PerTickBookkeeping
基于链表 O(1) O(n) O(n)
基于排序链表 O(n) O(1) O(1)
基于最小堆 O(lgn) O(1) O(1)
基于时间轮 O(1) O(1) O(1)

 

 

ps :  最后感慨一下,java的确给我们封装了很多不错的工具包,比较方便。java.util.*还是有许多比较不错的算法和实现,可以深挖下。


相关文章
|
19天前
|
数据采集 算法 机器人
软件体系结构 - 调度算法(3) 单调速率调度算法
【4月更文挑战第19天】软件体系结构 - 调度算法(3) 单调速率调度算法
26 0
|
算法 Linux 数据处理
《操作系统》—— 处理机调度算法
《操作系统》—— 处理机调度算法
|
2月前
|
机器学习/深度学习 数据采集 算法
解码癌症预测的密码:可解释性机器学习算法SHAP揭示XGBoost模型的预测机制
解码癌症预测的密码:可解释性机器学习算法SHAP揭示XGBoost模型的预测机制
160 0
|
19天前
|
监控 算法 机器人
软件体系结构 - 调度算法(2) 最低松弛度优先
【4月更文挑战第19天】软件体系结构 - 调度算法(2) 最低松弛度优先
26 0
|
19天前
|
监控 算法 自动驾驶
软件体系结构 - 调度算法(1) 最早截至时间优先
【4月更文挑战第19天】软件体系结构 - 调度算法(1) 最早截至时间优先
25 0
|
3天前
|
算法 调度
考虑需求响应的微网优化调度模型【粒子群算法】【matlab】
考虑需求响应的微网优化调度模型【粒子群算法】【matlab】
|
3天前
|
算法 调度
【问题探讨】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究
【问题探讨】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究
|
3天前
|
算法 Serverless 调度
基于分布式ADMM算法的考虑碳排放交易的电力系统优化调度研究(matlab代码)
基于分布式ADMM算法的考虑碳排放交易的电力系统优化调度研究(matlab代码)
|
3天前
|
算法 调度
基于CCG算法的IEEE33配电网两阶段鲁棒优化调度matlab
基于CCG算法的IEEE33配电网两阶段鲁棒优化调度matlab
|
3天前
|
算法 调度
基于多目标粒子群算法的微电网优化调度-王金全
基于多目标粒子群算法的微电网优化调度-王金全