HTTP异步Client源码解析

我们知道Netty作为高性能通信框架,优点在于内部封装了管道的连接通信等操作,用户只需要调用封装好的接口,便可以很便捷的进行高并发通信。类似,在Http请求时,我们通过调用HttpClient,内部使用java NIO技术,通过引入连接池概念,来提高Http的并发能力,本文主要讲解该客户端内部是如何实现并发能力提高的原理。Http客户端分为同步和异步方式,以下示例展示了最基本的异步使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom()
        .setConnectTimeout(5000)
        .setSocketTimeout(0)
        .setConnectionRequestTimeout(3000);
HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create().setDefaultRequestConfig(requestConfigBuilder.build())
// 这些参数会用来生成PoolingNHttpClientConnectionManager,若PoolingNHttpClientConnectionManager自定义了,那么这些参数也就无效了
        .setMaxConnPerRoute(10).setMaxConnTotal(30);
//配置io线程
IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
        setIoThreadCount(Runtime.getRuntime().availableProcessors())
        .setSoKeepAlive(true)
        .build();
DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);

ioReactor.setExceptionHandler(new IOReactorExceptionHandler() {
    @Override
    public boolean handle(IOException e) {
        System.out.println("dsdsdsd");
        return true;
    }
    @Override
    public boolean handle(RuntimeException e) {
        System.out.println("dsssd");
        return true;

    }
});
// 设置channel连接池并发参数
PoolingNHttpClientConnectionManager poolingNHttpClientConnectionManager = new PoolingNHttpClientConnectionManager(ioReactor);
poolingNHttpClientConnectionManager.setDefaultMaxPerRoute(5);
poolingNHttpClientConnectionManager.setMaxTotal(80);
httpClientBuilder.setConnectionManager(poolingNHttpClientConnectionManager);


// 初始化Client并启动
CloseableHttpAsyncClient client = HttpAsyncClients.custom().
        setConnectionManager(poolingNHttpClientConnectionManager)
        .build();
client.start();

final HttpGet request = new HttpGet("http://1.1.1.2:9200/indexName/_search");

// 异步查询
client.execute(request, new FutureCallback<HttpResponse>() {
    @Override
    public void completed(HttpResponse result){
        try {

            System.out.println(EntityUtils.toString(result.getEntity()));
        } catch (Exception e) {
            e.fillInStackTrace();
        }
    }
    @Override
    public void failed(Exception ex) {
        ex.fillInStackTrace();
    }
    @Override
    public void cancelled() {
        System.out.println("cancelled");
    }
});
Thread.sleep(10000);
client.close();

使用上没啥好说的,我们就直接以数据流流向为主线,看内部是如何使用连接池进行请求处理的。需要注意的是,若我们自定义了poolingNHttpClientConnectionManager对象,那么在requestConfigBuilder中设置的连接并发将不生效。

客户端内部初始化

pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.12</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.10</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore-nio</artifactId>
    <version>4.4.12</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpasyncclient</artifactId>
    <version>4.1.4</version>
</dependency>

目前httpclient已经升级到了5.x,本文源码基于4.X

InternalHttpAsyncClient客户端

我们需要关注下InternalHttpAsyncClient及其基类CloseableHttpAsyncClientBase,这里把重要的属性都罗列出来:

1
2
3
4
5
6
// 线程池管理者
private final NHttpClientConnectionManager connmgr; 
//MainClientExec 请求发送接收时的处理 
private final InternalClientExec exec;
// 类似netty的boss线程,负责管道建立连接
private final Thread reactorThread;

下图是客户端初始化时创建的一些重要的对象:
 


PoolingNHttpClientConnectionManager:根据名称就可以看到,是连接池管理者。
CPool:连接池,存放了当前连接池的连接信息,比如全局空闲连接available、每个route独自的Pool,后面会详细介绍。

连接建立线程+请求处理线程

客户端内部会创建两类线程,类似netty的boss和worker线程,分别用来创建连接管道:AbstractMultiworkerIOReactor、以及请求发送线程:BaseIOReactor。本文中,也复用netty的称呼,分别将这两类线程称呼为boss线程和worker线程。boss线程在CloseableHttpAsyncClientBase构造函数初始化时初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (threadFactory != null && handler != null) {
    this.reactorThread = threadFactory.newThread(new Runnable() { 

        @Override
        public void run() {
            try {
                // 比如当线程接收到数据,就跑到IOEventDispatch里面了
                final IOEventDispatch ioEventDispatch = new InternalIODispatch(handler);
                // 将跑到PoolingNHttpClientConnectionManager.execute()
                connmgr.execute(ioEventDispatch);
            } catch (final Exception ex) {
                log.error("I/O reactor terminated abnormally", ex);
            } finally {
                status.set(Status.STOPPED);
            }
        }

    });
} else {
    this.reactorThread = null;
}

boss线程真正工作的地方是在AbstractMultiworkerIOReactor,我们需要注意的是selector选择器(会在AbstractMultiworkerIOReactor构造时产生),每当需要构建管道时,都会向该selector上注册OP_CONNECT事件。AbstractMultiworkerIOReactor初始化代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public void execute(//eventDispatch=InternalIODispatch
        final IOEventDispatch eventDispatch) throws InterruptedIOException, IOReactorException {
    synchronized (this.statusLock) {
        this.status = IOReactorStatus.ACTIVE; 
        // Start I/O dispatchers
        for (int i = 0; i < this.dispatchers.length; i++) {
            final BaseIOReactor dispatcher = new BaseIOReactor(this.selectTimeout, this.interestOpsQueueing);
            dispatcher.setExceptionHandler(exceptionHandler);
            this.dispatchers[i] = dispatcher;
        }
        for (int i = 0; i < this.workerCount; i++) {
            final BaseIOReactor dispatcher = this.dispatchers[i];
            this.workers[i] = new Worker(dispatcher, eventDispatch);
            // 产生的线程名称都是"I/O dispatcher 120"
            this.threads[i] = this.threadFactory.newThread(this.workers[i]);
        }
    }
    try {
        // I/O dispatcher开头的线程名称
        for (int i = 0; i < this.workerCount; i++) {
            if (this.status != IOReactorStatus.ACTIVE) {
                return;
            }
            this.threads[i].start();
        }
        // 无线死循环了,除非管道关闭
        for (;;) { 
            final int readyCount;
            try {
                // 默认睡眠1s
                readyCount = this.selector.select(this.selectTimeout);
            } catch (final InterruptedIOException ex) {
                throw ex;
            } catch (final IOException ex) {
                throw new IOReactorException("Unexpected selector failure", ex);
            }
            // 如果有需要处理的事件, 则进入processEvents流程, 实际的连接过程就在这里
            if (this.status.compareTo(IOReactorStatus.ACTIVE) == 0) {
                // 纯粹管连接的地方
                processEvents(readyCount);
            }

            // Verify I/O dispatchers
            for (int i = 0; i < this.workerCount; i++) {
                final Worker worker = this.workers[i];
                final Throwable ex = worker.getThrowable();
                if (ex != null) {
                    throw new IOReactorException(
                            "I/O dispatch worker terminated abnormally", ex);
                }
            }
        }
    } finally {
        doShutdown();
        synchronized (this.statusLock) {
            this.status = IOReactorStatus.SHUT_DOWN;
            this.statusLock.notifyAll();
        }
    }
}

具体做了如下事情:
1.构建n个worker线程,线程名称是I/O dispatcher n开头的, n可以在IOReactorConfig初始化时设置,默认为cpu的个数。
2.启动n个worker线

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值