jetty http client 实现分析

liqing00 2012-09-01

fromhttp://www.linuxso.com/architecture/23691.html

背景

谈到httpclient,可能大多数想到就是apache的那个httpclient或者jdk自带的urlconnection,也许有人会考虑使用netty,

无论如何,jetty的高性能实现总归是让人感到好奇,接下来我们一探究竟

样例

我们结合样例代码具体分析

初始化

httpClient=newHttpClient();

httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);

httpClient.setMaxConnectionsPerAddress(10);

httpClient.setThreadPool(newQueuedThreadPool(20));//max20threads

httpClient.setTimeout(5000);//5secondstimeout;ifnoserverreply,therequestexpire

httpClient.start();

运行

ContentExchangeexchange=newContentExchange(true){

@Override

protectedvoidonResponseComplete()throwsIOException{

if(getResponseStatus()==200){

Stringcontent=getResponseContent();

System.out.println(content);

}

}

@Override

protectedvoidonExpire(){

System.out.println("timeout");

}

};

exchange.setMethod("GET");

exchange.setURL("http://127.0.0.1:8080/simple?id=x");

httpClient.send(exchange);

代码分为两段

初始化:设置httpclient

运行:实例化ContentExchange,定义callback,本例定义了两个常用的callback:onResponseComplete和onExpire更多的callback可参考官方文档http://wiki.eclipse.org/Jetty/Tutorial/HttpClient

APP在调用httpClient.send(exchange);后不会象往常一样等待返回而是立即返回,如果有结果或者超时会通过上面的callback通知到APP

httpclient的原理及实现

1)httpclient的模型

SelectConnector:作为一个connection管理器,封装了selector和connection

HttpDestination:一个host的抽象一个HttpClient会连接到多个HttpDestination

HttpExchange:一次http请求的封装,一个HttpDestination会有多个HttpExchange以及多个AsyncHttpConnection

AsyncHttpConnection:HttpClient对某个HttpDestination的一个网络连接,底层包含一个对应的socket,可复用来完成多次请求,如果空闲太久会被废弃

SelectChannelEndPoint:socket的封装,AsyncHttpConnection和SelectChannelEndPoint一一对应,但AsyncHttpConnection承载了更多的东西

HttpGenerator:生成httprequest,在jettyserver中负责生成httpresponse

HttpParser:解析httpresponse,在jettyserver中负责解析httprequest

ThreadPool:线程池,httpclient需要使用线程池配合完成无阻塞IO,这个会在后面的httpclient整体架构分析中详述

Timeout:一个已时间排序的链表结构,链表中存储需要过期执行的task,这个会在后面流程分析详述

2)httpclient的整体架构

httpclient分为3组线程配合完成

selector线程组:数目可设置,默认为1,从_change队列中获取socket注册并扫描操作系统级别的网络事件,通常是socket可读,可写的信息,一旦发现有socket可读写,会将相关socket任务丢入_jobs队列供worker线程执行

worker线程组:数目根据并发的情况决定,从_jobs队列获取任务,如果任务阻塞会丢入_changes队列异步等待通知再干活

tick线程:数目1个,专门用于监控超时的请求以及空闲太久的连接

所有的线程都来自线程池,所以线程池最小为3,否则无法work

3)典型的场景分析

模拟一次请求

3.1)httpclient初始化

1-2设置两个超时链表,一个是超时请求链表,一个是超时连接链表

3启动httpbuffer

4启动线程池

5启动SelectConnector,此时会启动selector线程任务

6启动tick线程任务

3.2)jettyhttpclientruntime

3.2.1)httpClient.send(exchange)到底干了什么

1-2正如样例代码所示,APP设置HttpExchange,然后httpclient的send方法

2.1-2.2httpclient根据httpexchange获取对应httpdestination,并调用其send方法

2.2.1将次请求加入请求超时链表

2.2.2-2.2.3获取空闲连接,如果没有,则产生一个新的连接,并调用select进行注册,否则直接使用该连接,并将此连接丢入_jobs队列让worker线程完成请求

此时客户端就这样无阻塞的完成了

3.2.2)select线程如何参与这个场景

1-3selector线程从_change队列获取到新的socket,开始实例化SelectChannelEndPoint

4通知httpdesination连接完成,于是httpdetination将次连接丢入连接超时链表

5-6将此连接/请求丢入_jobs队列供worker线程使用

3.2.3)worker线程又如何参与这个场景

worker线程从队列中获取任务

1.1通过此连接发送请求,请求内容httpgenerator产生

1.2一发完请求立即通过httpparser读取响应,如果服务器够快,通常会读到响应

1.3如果服务器不能及时响应,那么调用SelectChannelEndPoint的updateKey。向select更新此时感兴趣读,并等待select异步通知

此时worker线程并不会阻塞等待服务返回,而是返回到线程池中去完成别的请求任务

3.2.4)tick线程又干了什么

轮询两个链表_timeoutQ、_idleTimeoutQ,没啥事休眠200ms

请求超时链表_timeoutQ

1从链表中删除自己

2执行链表取出的task,一个httpexchang中匿名内部类实例

2.1执行APP定义的callback:onExpire函数

2.2httpdesination专门维护一个exchangelist来跟踪进行中的请求,此时调用其exchangeExpired,删除list中该请求(可能此时list并没有该请求)

2.3关闭连接

连接超时链表_idleTimeoutQ

1从链表中删除自己

2关闭连接

httpdesination维护了两个list:_connections和_idle,前者跟踪该host的所有连接,后者跟踪该host的所有空闲连接,此时也会从这两个list删除连接

小结

从jettyhttpclient应该能感知到一个高性能的客户端的某种设计模式

worker线程异步干活,使得app线程无阻塞,app线程通常在web应用中也是一种服务线程,所以无阻塞特别重要,想想在jettyserver中使用jettyclient的场景

select线程通知网络ready事件,使得worker线程无阻塞,如果没有select线程,worker线程也失去了意义,对于app线程来说无非是压力堆积到了worker线程这边,worker线程迟早是瓶颈

tick线程,一种解决超时问题的设计

但这种模式未必适合那种性能很好且稳定的cacheserver,比如redis,memcache之类,如果后端处理够快,少量线程甚至单线程+队列都能work

无论如何比起常规的连接池模式强了不少

http://www.linuxso.com/architecture/23691.html

相关推荐