(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.*还是有许多比较不错的算法和实现,可以深挖下。

时间: 2024-08-30 21:09:35

(HttpClient超时机制)timeout调度算法探讨的相关文章

HttpClient超时机制(安全问题处理:访问超大文件控制)

背景      最近一直在做项目,其中的一个功能点,主要是访问外部网站并获取页面的字符串,具体的网站url完全是由用户输入,所以存在一定的安全隐患.     从测试来看,如果给定的一部电影的url地址,链接会一直不能被关闭,直到数据流被读完,如果来个几十次这样的请求,应用估计也差不多崩溃了   说明:   项目中使用的HttpClient版本是3.0.1 测试 一般的HttpClient使用例子: 1.MultiThreadedHttpConnectionManager manager = ne

Android超时机制的处理(很不错)

由于手机端应用的响应,与当时的无线通信网络状况有很大的关联.而通信网络往往具有不稳定,延迟长的特点.所以,在我们的应用程序中,当我们请求网络的时候,超时机制的应用就显得特别重要. 超时机制主要有: 1.HTTP请求超时机制 2.Socket通信超时机制 HTTP请求超时机制 public static void main(String[] args){ long a=System.currentTimeMillis(); try{ URL myurl = new URL("http://www.

Android超时机制

  由于手机端应用的响应,与当时的无线通信网络状况有很大的关联.而通信网络往往具有不稳定,延迟长的特点.所以,在我们的应用程序中,当我们请求网络的时候,超时机制的应用就显得特别重要. 超时机制主要有: 1.HTTP请求超时机制 2.Socket通信超时机制 HTTP请求超时机制 public static void main(String[] args){ long a=System.currentTimeMillis(); try{ URL myurl = new URL("http://ww

Android&amp;#183;HTTP超时机制

由于手机端应用的响应,与当时的无线通信网络状况有很大的关联.而通信网络往往具有不稳定,延迟长的特点.所以,在我们的应用程序中,当我们请求网络的时候,超时机制的应用就显得特别重要. 超时机制主要有: 1.HTTP请求超时机制 2.Socket通信超时机制 HTTP请求超时机制 public static void main(String[] args){ long a=System.currentTimeMillis(); try{ URL myurl = new URL("http://www.

C# Socket连接请求超时机制实现代码分享

 这篇文章主要介绍了C# Socket连接请求超时机制实现,下面提供代码分享,大家可以参考使用 .Net的System.Net.Sockets.TcpClient和System.Net.Sockets.Socket都没有直接为Connect/BeginConnect提供超时控制机制.因此,当服务器未处于监听状态,或者发生网络故障时,客户端连接请求会被迫等待很长一段时间,直到抛出异常.默认的等待时间长达20~30s..Net Socket库的SocketOptionName.SendTimeout

php脚本运行时的超时机制详解_php实例

在做php开发的时候,经常会设置max_input_time.max_execution_time,用来控制脚本的超时时间.但却从来没有思考过背后的原理. 趁着这两天有空,研究一下这个问题. 超时配置 php的ini配置如何起作用,这是一个老生常谈的话题了. 首先,我们在php.ini里进行配置.当php启动的时候(php_module_startup阶段),会尝试读取ini文件并解析.解析过程简单来说,是分析ini文件,提取出其中合法的键值对,并保存到configuration_hash表.

HBase最佳实践-客户端超时机制

上篇博文结合一起线上问题介绍了HBase客户端基于退避算法的重试机制,并分析得出在某些场景下如果重试策略设置不当会导致长时间的业务阻塞.除了重试机制外,业务童鞋最关心的就是超时机制了.客户端超时设置对整个系统的稳定性以及敏感性至关重要,一旦没有超时设置或超时时间设置过长,服务器端的长时间卡顿必然会引起客户端阻塞等待,进而影响上层应用.好在HBase提供了多个客户端参数设置超时,主要包括 hbase.rpc.timeout / hbase.client.operation.timeout / hb

您还有心跳吗?超时机制分析

问题描述 在C/S模式中,有时我们会长时间保持一个连接,以避免频繁地建立连接,但同时,一般会有一个超时时间,在这个时间内没发起任何请求的连接会被断开,以减少负载,节约资源.并且该机制一般都是在服务端实现,因为client强制关闭或意外断开连接,server端在此刻是感知不到的,如果放到client端实现,在上述情况下,该超时机制就失效了.本来这问题很普通,不太值得一提,但最近在项目中看到了该机制的一种糟糕的实现,故在此深入分析一下. 问题分析及解决方案 服务端一般会保持很多个连接,所以,一般是创

Netty 超时机制及心跳程序实现

本文介绍了 Netty 超时机制的原理,以及如何在连接闲置时发送一个心跳来维持连接. Netty 超时机制的介绍 Netty 的超时类型 IdleState 主要分为: ALL_IDLE : 一段时间内没有数据接收或者发送 READER_IDLE : 一段时间内没有数据接收 WRITER_IDLE : 一段时间内没有数据发送 在 Netty 的 timeout 包下,主要类有: IdleStateEvent : 超时的事件 IdleStateHandler : 超时状态处理 ReadTimeou