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. 关于bat中日期时间字符串的格式化

    在其他编程语言中,要实现日期时间字符串的格式化,包括时间计算,都是比较简单的 但在bat或者说cmd.dos中要实现这些功能.还是有一定难度的 首先,windows的cmd中可以使用%date%表示日 ...

  2. (转)HttpServletResquest对象

    HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息. 1 ...

  3. springboot实现防重复提交和防重复点击

    背景 同一条数据被用户点击了多次,导致数据冗余,需要防止弱网络等环境下的重复点击 目标 通过在指定的接口处添加注解,实现根据指定的接口参数来防重复点击 说明 这里的重复点击是指在指定的时间段内多次点击 ...

  4. java原生程序redis连接(连接池/长连接和短连接)选择问题

    最近遇到的连接问题我准备从重构的几个程序(redis和mysql)长连接和短连接,以及连接池和单连接等问题用几篇博客来总结下. 这个问题的具体发生在java原生程序和redis的交互中.这个问题对我最 ...

  5. spark textfile rdd 日记

    批量处理模板方法, 核心处理方法为内部方法 def batchProces(sc: SparkContext, locationFlag: Int, minid: Int, maxid: Int, n ...

  6. MarkDown系列教程

    编辑了一个Markdown的系列教程,前一部分是摘编自 菜鸟教程 网站 目录 第一篇 Markdown 使用教程 入门

  7. 063 01 Android 零基础入门 01 Java基础语法 08 Java方法 01 无参无返回值方法

    063 01 Android 零基础入门 01 Java基础语法 08 Java方法 01 无参无返回值方法 本文知识点:无参无返回值方法 无参无返回值方法 案例 为什么使用方法?--方便复杂问题调用 ...

  8. keepass+坚果云管理我的密码

    目录 前言 下载安装KeePass 创建一个数据库 配置坚果云 手机用坚果云 总结 前言     KeePass是一款免费.小巧.绿色且开源的密码管理工具,多年来一直深受大众的好评,它能为用户提供一个 ...

  9. for循环迭代可迭代对象

    模仿for循环迭代可迭代对象,# for i in Iterable:# iterable >>> 迭代器.iterator# 可迭代对象 iterable# 迭代器.iterato ...

  10. spring redis 配置