基于NIO实现公司API远程调用技术探讨
2021-03-01 06:28
标签:table 不同的 path catch custom throw 成功 ble 否则 前 言 目前公司一些工具会远程调用一些API,这些API调用有两个比较显著特点。 1、消耗时间比较长,无论是报表调用的API,还是 backend ws API 单次调用平均达到20 s 左右。 2、返回来的数据有时也会比较大,我见过单次调用返回的数据有可能有3MB 左右。 基于上述特点,很显然有优化的空间,当然这个优化的方法很多,比如目前公司在服务器端采用诸如 k8s 这种按需扩容方案,是能够大大提升整个远程API的调用效率,又比如对服务器端代码进行优化等等。 但由于本人日常工作中只接触API 在客户端的调用,所以本文探讨会在以下前提下进行探讨: 1、基于不改变API服务器代码。 2、只讨论客户端发出请求,服务器端返回数据这一段,对于客户端后续获取数据的处理不在本文探讨范围。 3、客户端远程调用 100次 API。 4、服务器端对于每次调用响应时间为 10s 。 5、目前API 服务器,我是使用自己搭建的http 服务器来测试。 传统调用模式 单线程调用模式: 单线程模式总耗时 = 100次 *10s = 1000s 约等于 17分钟。 多线程调用模式(Future模式+线程池【3个线程】): 该模式由于启用了3个线程同时进行,所以是单线程总花费时间的三分之一,单线程总耗时1000s, 则多线程模式总耗时 = 1000s /3 = 333s 约等于 6分钟。 NIO 调用模式 以上是基于公司目前所广泛使用的远程API方式,接下来会基于NIO 方式来探讨客户端对远程API调用。为了方便较系统来探讨NIO 技术,会按照以下提纲进行编排。 1、网络基础知识概览 2、NIO 核心讲述 3、基于HttpAsyncClients 实现API远程调用 4、基于Netty 4 实现API远程调用 网络基础知识概览 在常见的web 应用程序里面,都是基于不同的协议来实现,例如我们常用的邮件发送,是基于SMTP,FTP工具上传当然就是基于FTP协议,浏览器是基于HTTP协议等等。 无论是哪种协议,最终都会被抽象成Socket 来进行网络操作,如下图所示。为什么要在这里强调Socket 是因为NIO 是基于Socket 一些列操作的抽象。 Socket 常处理事件: OP_CONNECT 事件:主要是客户端连接服务端时候触发。 OP_ACCEPT 事件:主要客户端发出连接请求后,与服务器端3次握手成功后,触发事件。这个时候往往意味着客户与服务器链路已经完成连接成功,可以进行下一步通信。 OP_READ 事件:无论是客户端还是服务器端,只要Socket 检测到缓冲区有可以读写的数据就会触发。 OP_WRITE 事件:主要是检测内核缓冲区是否已经满,如果没有满则代表缓冲区还可以继续写入数据,会触发该事件,否则不会触发。 NIO核心讲述 NIO 有说是non-blocking IO 缩写,也有说是New IO 缩写,在这里不纠结这个问题,按照我的理解NIO 核心思想就是 "多路复用"。为了说清楚 多路复用 我们先看一个传统的HTTP 服务器架构实现。 从图中看出传统的HTTP服务器模型,最大的特点就是每一个客户端(Socket)连接 在服务器端都开启一个新的线程(ps:在实际中用线程池实现),这种感觉就好像餐厅为每个新客户都配置一个服务员, 实际是没有必要。因为服务员(类似线程)一般只会在客户 点菜、传菜、结账(类似Socket 事件)需要提供服务。其他大部分事件是不需要服务员。所以NIO 就是一个线程(服务员)可以同时负责成千上百个 Socket(客户)。 所谓的“多路复用”就体现在这里 多路其实指多个客户端(Socket),复用 指的是重复使用相同的线程来负责多个客户端(Socket)事件监听。如下图所示,使用NIO模型,一个线程就可以 负责成千上百个客户,相比较传统的HTTP模型一个线程负责一个Socket相比,是能极大提升服务器响应效率这也是各大电商所广泛采用的模型。 以下是非常经典简单的NIO模型核心代码实现 以上为了讲述方便,只是列举了最简单的NIO实现模型,实际上诸如比较成熟的NIO实现框架,例如后面说的HttpAsyncClients 和 Netty 4.0 模型如下图所示,由于篇幅关系在这里就不对这个模型展开讲, 后续如果有需要会补充上来。初步计划放在 《基于NIO实现公司API Http服务器》 这篇文章来说。 基于HttpAsyncClients 实现API远程调用 异步阻塞模型,这个实现原理实际上是 Future模式+NIO+线程(ps:实际上是包含了两种线程,一种是专门用来处理OP_ACCEPT 事件,一种是专门用来处理OP_READ 和 OP_WRITE 事件)具体调用代码如下: 另外这种方式耗时为341秒左右。要比传统多出10秒左右,为什么用这种方式反而更慢? 这个统一在后面回答。 基于Netty 4 实现API远程调用 其实这种方式实现原理底层也是基于NIO,与HttpAsyncClients 相比,Netty 客户端这里允许只用1个线程,而HttpAsyncClients 至少需要2个线程,因为系统后台必须默认一个专门用来处理OP_ACCEPT线程。 如果只是单纯实现客户端调用,然后远程服务器返回数据。我认为只需要1个线程就可以,所以才会提这种方式。这种方式耗时为331秒与传统的线程池模式差不多,具体实现核心代码如下: 上面就是几种远程调用API方式,从目前来看用NIO 实现远程调用 与 传统的多线程调用耗时差不多,甚至基于HttpAsyncClients 比多线程模式还要慢10秒,这是因为我为了与多线程方式调用统一,目前多线程是用 了3个线程,也就是一次可以模拟发送3个请求。为了统一NIO 这种调用方式每次也只是模拟发送3个请求。实际上我们可以根据API 服务器处理能力来设定请求数,比如API服务器每次能同事处理100个连接,NIO 就可以 把每次请求变成100,或许有人就说了,使用多线程方式调用,也可以开100个线程来达到同样的效果。基于这个就得出今天的结论,使用NIO 方式调用的优点就是 用最小的线程数实现对API服务器最大程度调用。 以上就是使用NIO对客户端调用的优化,相比较客户端优化,也许对API服务器端进行优化意义更大一些,毕竟对于整个系统而言服务器的QPS 或者说TPS 才是最重要的效率考量,对于服务器端也同样可以基于NIO 这种方式来优化,对于服务器返回数据量比较大问题,也有一系列的数据传输压缩方案,比如Google Protocol Buffer 。 基于NIO实现公司API远程调用技术探讨 标签:table 不同的 path catch custom throw 成功 ble 否则 原文地址:https://www.cnblogs.com/pangjia/p/12128499.html public static void singleThread() {
try {
Date startDate = new Date();
for(int i=0;i) {
String apiUrl = "http://127.0.0.1:6668/";
HttpClient1.sendPost(apiUrl, "");
}
Date endDate = new Date();
long interval = (endDate.getTime() - startDate.getTime())/1000;
SimpleDateFormat timeFomat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms");
System.out.println("singleThread startDate="+timeFomat.format(startDate)+" endDate="+timeFomat.format(endDate)+" diff="+interval+"秒");
}catch(Exception ex) {
ex.printStackTrace(System.out);
}
}
1 public static void multiThread() throws InterruptedException, ExecutionException {
2 Date startDate = new Date();
3 int count = 100;
4
5 List
1 SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);// 注册OP_ACCEPT事件
2 for (;;) { // 循环遍历各个Socket事件
3 selector.select();
4 Set readyKeys = selector.selectedKeys();
5 Iterator i = readyKeys.iterator();
6 long e=0;
7 while (i.hasNext()) { // 捕获到某些Socket事件
8 SelectionKey sk = (SelectionKey) i.next();
9 i.remove();
10
11 if (sk.isAcceptable()) { // 处理 OP_ACCEPT事件
12 doAccept(sk);
13 }
14 else if (sk.isValid() && sk.isReadable()) {//处理 OP_READ事件
15 if(!time_stat.containsKey(((SocketChannel)sk.channel()).socket()))
16 time_stat.put(((SocketChannel)sk.channel()).socket(),
17 System.currentTimeMillis());
18 doRead(sk);
19 }
20 else if (sk.isValid() && sk.isWritable()) {//处理 OP_WRITE事件
21 doWrite(sk);
22 e=System.currentTimeMillis();
23 long b=time_stat.remove(((SocketChannel)sk.channel()).socket());
24 System.out.println("spend:"+(e-b)+"ms");
25 }
26 }
1 public static void main(String[] args){
2 int count = 100;
3 Date startDate = new Date();
4 try {
5 RequestConfig requestConfig = RequestConfig.custom()
6 .build();
7
8 IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
9 setIoThreadCount(1)// 设定Woker 线程池数量,系统默认是CPU核数
10 .setSoKeepAlive(true)
11 .build();
12
13 //设置连接池大小
14 ConnectingIOReactor ioReactor=null;
15 try {
16 ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
17 } catch (IOReactorException e) {
18 e.printStackTrace();
19 }
20 PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
21 connManager.setMaxTotal(150);//最大连接数设置15
22 connManager.setDefaultMaxPerRoute(3);//每次默认发送3个连接,也就是一次性发送3个socket连接。
23
24 final CloseableHttpAsyncClient client = HttpAsyncClients.custom().
25 setConnectionManager(connManager)
26 .setDefaultRequestConfig(requestConfig)
27 .build();
28 client.start();
29
30 List
1 public class HttpClient {
2 public void run() throws Exception {
3 Date startDate = new Date();
4 int count = 33;// 发送33次
5 int perOfPath = 3;// 每次模拟发送3个连接
6 EventLoopGroup group = new NioEventLoopGroup(1); // 设定一个线程作为轮询Socket事件
7 try {
8 Bootstrap client = new Bootstrap();
9 client.group(group).channel(NioSocketChannel.class)
10 .handler(new ChannelInitializer
下一篇:AcWing 1381. 阶乘
文章标题:基于NIO实现公司API远程调用技术探讨
文章链接:http://soscw.com/index.php/essay/58440.html