博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(转)(HttpClient超时机制)timeout调度算法探讨
阅读量:6321 次
发布时间:2019-06-22

本文共 4963 字,大约阅读时间需要 16 分钟。

HttpClient提供的超时设置只能设置等待连接的超时和读取超时。而我在做的一个爬虫出现这样的情况——倘若服务器返回一个“庞然大物”,一个线程将在这上面耗费大量时间。而之后又有“庞然大物”来,又有线程“陷”进去了。一个又一个,最后爬虫就瘫痪了。而我处理的办法是采用gzip和设置request的超时。关于gzip,在httpclient的example中有例程,而关于设置request的超时,一直没想到好的方案,最后在下面的文章中找到了。

————————————————————————————————华丽的分割线—————————————————————————————————————————

原文作者:agapple

原文地址:

提到了一个需要管理所有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
{2 3 long getDelay(TimeUnit unit);4 }

说明:getDelay主要返回对应距离目标time还存在剩余的delay时间。这里插入一个request后,立马调用该方法返回的应该就是你想要的timeout时间。

 

代码实现:

 

1 /** 2  * 超时控制线程,基于DelayQueue实现的一套超时管理机制 3  *  4  * 
 5  * 几个特点 6  * 1. O(logN)的超时控制算法 7  * 2. timout处理更精确,时间控制精度为毫秒(ms) 8  * 3. thread-safe(线程安全) 9  * 
10 * 11 * @author jianghang 2011-3-7 下午12:39:1712 */13 class HttpTimeoutThread extends Thread {14 15 // init time for nano16 private static final long MILL_ORIGIN = System.currentTimeMillis();17 // thread-safe,定时触发timeout18 private volatile DelayQueue
queue = new DelayQueue
();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 interrupt27 }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; // 管理对应的request39 private long now; // 记录具体request产生时的now的偏移时间点,单位ms40 private long timeout; // 记录具体需要被delayed处理的偏移时间点,单位ms41 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 @Override56 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 @Override63 public int compareTo(Delayed other) {64 if (other == this) { // compare zero ONLY if same object65 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的文章:

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

 

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

——————————————————————————————————华丽的结束线———————————————————————————————————————

PS:以上的代码是根据HttpClient3.0.1版本编写的。

转载于:https://www.cnblogs.com/deepinsea/archive/2013/05/10/3072252.html

你可能感兴趣的文章
正则表达式-贪婪与懒惰
查看>>
.NET中使用Redis
查看>>
PHP 函数dirname()使用实例
查看>>
jQuery attr方法修改onclick值
查看>>
魔术布局效果-使用本地JSON数据提供数据服务
查看>>
WCF分布式开发必备知识(2):.Net Remoting
查看>>
PHP与MYSQL中UTF8 中文排序例子
查看>>
【多线程】学习11
查看>>
如何在Datatable中取得每列的数据列宽度
查看>>
XML学习笔记
查看>>
任务调度开源框架Quartz动态加入、改动和删除定时任务
查看>>
C#、.NET网络请求总结(WebClient和WebRequest)
查看>>
[再寄小读者之数学篇](2014-11-20 计算二重积分)
查看>>
Material Designer的低版本兼容实现(八)—— Flat Button
查看>>
haha
查看>>
ContentProvider简单介绍
查看>>
基于jQuery的美食时间轴焦点图插件
查看>>
iOS开发-音乐播放
查看>>
用函数SendARP()获取局域网计算机的MAC地址
查看>>
locate 命令(转)
查看>>