Java网关服务-AIO(三)

概述

前两节中,我们已经获取了body的总长度,剩下的就是读出body,处理请求

ChannelServerHandler

ChannelServerHandler即从channel中读取请求,也向channle输出结果,因此它实现了InboundHandler, OutboundHandler

/**
* 读取请求的内容,业务处理
*/
public class ChannelServerHandler implements CompletionHandler<Integer, ByteBuffer>, InboundHandler, OutboundHandler { private final static Logger LOGGER = LoggerFactory.getLogger(ChannelServerHandler.class); private AsynchronousSocketChannel channel; public ChannelServerHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
} public void completed(Integer result, ByteBuffer attachment) {
//如果条件成立,说明客户端主动终止了TCP套接字,这时服务端终止就可以了
if (result == -1) {
System.out.println("remote is close");
closeChannel();
return;
} Object resultData;
String req = (String) read(channel, attachment);
if (req == null) {
closeChannel();
return;
} try {
LOGGER.info("socket:{}", channel.getRemoteAddress()); //同步处理请求
RequestHandler requestHandler = ApplicationUtils.getBean(RequestHandler.class);
resultData = requestHandler.execute(req); } catch (Throwable t) {
resultData = Result.error("ERROR", Utils.error(t));
LOGGER.error("调用接口失败", t);
} if (resultData == null) {
resultData = Result.failure("FAILURE", "调用失败,数据为空.");
}
try {
String resultContent = resultData instanceOf String ? (String) resultData : JSON.toJSONString(resultData);
byte[] bytes = resultContent.getBytes("UTF-8");
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip(); write(channel, writeBuffer);
} catch (Exception e) {
LOGGER.error("对象转JSON失败,对象:{}", resultData, e);
} closeChannel();
} @Override
public Object read(AsynchronousSocketChannel socketChannel, ByteBuffer in) {
in.flip();
byte[] body = new byte[in.remaining()];
in.get(body); String req = null;
try {
req = new String(body, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return req;
} @Override
public Object write(AsynchronousSocketChannel socketChannel, ByteBuffer out) {
//write,write操作结束后关闭通道
channel.write(out, out, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
closeChannel();
} @Override
public void failed(Throwable exc, ByteBuffer attachment) {
closeChannel();
}
});
return null;
} public void failed(Throwable exc, ByteBuffer attachment) {
closeChannel();
} private void closeChannel() {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
} }

读取body

		in.flip();
byte[] body = new byte[in.remaining()];
in.get(body); String req = null;
try {
req = new String(body, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return req;
in.remaining()

buffer中含有的字节数

客户端、服务端由于跨语言和经验问题,没有使用复杂的跨语言序列化技术,双方约定使用UTF-8编码,通过将body转换为String,最终获得了客户端传递的字符串。

处理请求

经过自定义的请求处理逻辑,同步处理,最终将响应编码后,发送给客户端,write操作结束后,关闭连接

总结

使用AIO开发服务端时,主要涉及

  • 配置I/O事件完成的回调线程池
  • 从accept -> read 到 向client端响应 write -> close,尽量使用CompletionHanlder来异步处理,不要在处理某个事件完成的线程中,同步的调用,如future.get()
  • 如果是短连接,则需在write操作时注册write结束后的handler,在handler中关闭连接

扩展

长连接该如何处理

  • 长连接意味着client可以发多次请求,由于多次请求被server执行的顺序是不可控的,可能后发的请求先响应,因此需要在请求和响应时,加requestId,据此对应到请求的结果
  • 长连接不需要在write后关闭连接
  • 长连接需要开发定时的ping-pong心跳消息
  • 长连接在响应时比现在更复杂,也需要一个和请求类似或相同的协议来标识body长度

测试

测试用例

	/**
* mvn -Dtest=com.jd.jshop.web.sdk.test.ClientTest#pingReqSocket test
*
* @throws IOException
*/
@Test
@PerfTest(invocations = 20000, threads = 50)
public void pingReqSocket() throws IOException { byte[] content = "ping".getBytes("UTF-8");
String result = sendReq(content); //断言 是否和预期一致
Assert.assertEquals("pong", result);
} private String sendReq(byte[] content) throws IOException {
ByteBuffer writeBuffer = ByteBuffer.allocate(4 + content.length);
writeBuffer.putInt(content.length);
writeBuffer.put(content);
writeBuffer.flip(); Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 9801));
socket.getOutputStream().write(writeBuffer.array());
socket.getOutputStream().flush();
byte[] buf = new byte[1024];
int len = 0;
String result = null;
while ((len = socket.getInputStream().read(buf)) != -1) {
result = new String(buf, 0, len);
System.out.println(result);
}
return result;
}

测试的方法是,在服务器上建立socket连接,向server发送ping,server返回pong

测试服务器:centos, 2个物理核,4个逻辑核,内存16G

分析aio的实现:

在ping-pong测试中性能极高,优于并甩开netty

以下是使用Netty开发的server端的测试用例,可以和上面的图片对比一下

Measured invocations:	10,000
Thread Count: 10 Measured
(system) Required
Execution time: 1,646 ms
Throughput: 6,075 / s
Min. latency: 0 ms
Average latency: 1 ms
Median: 2 ms
90%: 2 ms
Max latency: 26 ms ============================ Started at: Oct 16, 2018 5:27:03 PM
Measured invocations: 20,000
Thread Count: 20 Measured
(system) Required
Execution time: 3,293 ms
Throughput: 6,073 / s
Min. latency: 0 ms
Average latency: 3 ms
Median: 3 ms
90%: 5 ms
Max latency: 54 ms ============================ Started at: Oct 16, 2018 5:28:24 PM
Measured invocations: 20,000
Thread Count: 10 Measured
(system) Required
Execution time: 3,051 ms
Throughput: 6,555 / s
Min. latency: 0 ms
Average latency: 1 ms
Median: 1 ms
90%: 2 ms
Max latency: 44 ms ============================ Started at: Oct 16, 2018 5:30:06 PM
Measured invocations: 20,000
Thread Count: 50 Measured
(system) Required
Execution time: 3,167 ms
Throughput: 6,315 / s
Min. latency: 0 ms
Average latency: 7 ms
Median: 7 ms
90%: 10 ms
Max latency: 64 ms

分析基于Netty的实现:

吞吐量:6000+/s

10个线程时:90%低于2ms,平均1ms

20个线程时:90%低于5ms,平均3ms

50个线程时:90%低于10ms,平均7ms

线程越多,性能越低

当前测试用例不太依赖内存

执行10000+次请求,建立10000+连接,要求服务器对单个进程fd限制打开,防止报too many open files导致测试用例执行失败

    ulimit -n 20240

Java网关服务-AIO(三)的更多相关文章

  1. Java网关服务-AIO(二)

    Java网关服务-AIO(二) 概述 AIO的特点就是用户程序注册一个事件后就可以做其他事情,当事件被内核执行并得到结果后,我们的CompletionHandler会在I/O回调线程中被自动调用,有点 ...

  2. Java网关服务-AIO(一)

    Java网关-AIO(一) aio:声明一个byteBuffer,异步读,读完了之后回调,相比于Future.get(),可以减少阻塞.减少线程等待,充分利用有限的线程 nio:声明一个byteBuf ...

  3. Spring Cloud 网关服务 zuul 三 动态路由

    zuul动态路由 网关服务是流量的唯一入口.不能随便停服务.所以动态路由就显得尤为必要. 数据库动态路由基于事件刷新机制热修改zuul的路由属性. DiscoveryClientRouteLocato ...

  4. Spring Cloud gateway 网关服务二 断言、过滤器

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  5. JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫

    JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接 ...

  6. springcloud中的API网关服务Zuul

    到目前为止,我们Spring Cloud中的内容已经介绍了很多了,Ribbon.Hystrix.Feign这些知识点大家都耳熟能详了,我们在前文也提到过微服务就是把一个大的项目拆分成很多小的独立模块, ...

  7. SpringCloud初体验:七、gateway 网关服务如何做token验证

    说说背景:假如有一个用户服在用户登录后,生成一个token给到客户端,用户每次请求时都需要这个token,于是每次都会在网关 gateway 校验,校验通过后网关从token中解析出userId,然后 ...

  8. springcloud-Api网关服务Zuul

    springcloud项目例子:链接:https://pan.baidu.com/s/1O1PKrdvrq5c8sQUb7dQ5Pg 密码:ynir 1.由来: 如果我的微服务中有很多个独立服务都要对 ...

  9. API网关服务Zuul-Spring Cloud学习第五天(非原创)

    文章大纲 一.Zuul是什么二.Zuul的基本实现三.路由配置细节四.异常处理细节五.项目源码与参考资料下载六.参考文章   一.Zuul是什么   到目前为止,我们Spring Cloud中的内容已 ...

随机推荐

  1. day56:django:csrf_token&文件上传

    目录 1.csrf介绍 2.django实现csrf_token认证 3.django实现文件上传 csrf介绍 什么是csrf? csrf:跨站请求伪造.攻击者通过HTTP请求将数据传送到服务器,从 ...

  2. python语言开发环境配置

    原作者:龙行天下-super 地址:https://www.cnblogs.com/longxingtianxia/p/10181901.html 要点:IDLE是一个轻量级python语言开发环境, ...

  3. Ubuntu16.04 Nvidia显卡驱动简明安装指南

    简单得整理了一下Ubuntu16.04 Nvidia显卡驱动的安装步骤: 查看当前系统显卡参数: sudo lspci | grep -i nvidia 删除之前的驱动: sudo apt-get - ...

  4. PHP代码审计02之filter_var()函数缺陷

    前言 根据红日安全写的文章,学习PHP代码审计审计的第二节内容,题目均来自PHP SECURITY CALENDAR 2017,讲完这个题目,会有一道CTF题目来进行巩固,外加一个实例来深入分析,想了 ...

  5. 重拾H5小游戏之入门篇(二)

    上一篇,水了近千字,很酸爽,同时表达了"重拾"一项旧本领并不容易,还有点题之效果.其实压缩起来就一句话:经过了一番记忆搜索,以及try..catch的尝试后,终于选定了Phaser ...

  6. 079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象

    079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象 本文知识点:实例化对象 说明:因为时间紧张,本人写博客过程中只是对知 ...

  7. Python单向链表的实现

    链表由一系列不必在内存中相连的结构构成,这些对象按线性顺序排序.每个结构含有表元素和指向后继元素的指针.最后一个单元的指针指向NULL.为了方便链表的删除与插入操作,可以为链表添加一个表头. 删除操作 ...

  8. 【题解】SAC E#1 - 一道难题 Tree

    Problem is here \(\text{Solution:}\) 首先,一眼看出这是最小割,只要叶子节点对汇点\(T\)连接流量为\(inf\)的边就可以一遍最大流搞定了. 剩下的问题在于,如 ...

  9. Pock 把 Touch Bar 变成系统中的 Dock 栏

    Pock 把 Touch Bar 变成系统中的 Dock 栏 Pock 是一款 macOS App,你可以通过它把 Touch Bar 变成系统中的 Dock 栏,直接用来切换和启动 App,尽享全屏 ...

  10. 更简易的机器学习-pycaret的安装和环境初始化

    1.安装 pip install pycaret 在谷歌colab中还要运行: from pycaret.utils import enable_colab enable_colab() 2.获取数据 ...