1 问题背景
问题:如下图所示是订单详情页面。餐饮商在其小程序应用中的该页面编辑商品数量和价格,需要供货商在其小程序应用中的该页面实时显示商品的数量和价格。
这个问题属于实时通信范畴,通常有三种解决方案:
- ajax轮询
- long pull
- websocket
由于ajax轮询和long pull这两种解决方案资源开销大,在客户端和服务器通信时每次都要建立HTTP连接,并且对服务器端并行处理要求高,因此本文采用websocket方案[1]。
2 Netty简介
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用[2]。
Netty有如下几个特性:
- 高并发
- 零拷贝
- 简单易用
下面首先介绍NIO模型,体现Netty的高并发特性;再讲到零拷贝以此说明Netty如何减少内存拷贝,突出其资源消耗少、快速的特点;最后讲解reactor主从多线程模型结构。
2.1 NIO简介
- BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
- NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
- AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作[3]。
2.2 零拷贝
“零拷贝”是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式[4]。
2.3 reactor主从多线程模型结构
主从Reactor线程模型的特点是:服务端用于接收客户端连接的不再是个1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到IO线程池(sub reactor线程池)的某个IO线程上,由它负责SocketChannel的读写和编解码工作。Acceptor线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端subReactor线程池的IO线程上,由IO线程负责后续的IO操作[5]。
3 业务架构
3.1 服务器端初始化流程图
3.2 业务流程介绍
3.2.1 创建websocket连接
客户端发送建立websocket连接请求,服务器端接收请求并处理握手动作。
3.2.2 客户端发送心跳
客户端每隔20秒发一次心跳请求。
3.2.3 断开websocket连接
客户端发送断开websocket连接请求,服务器端接收请求并断开连接,移除用户信息。
3.2.4 客户端发送修改订单项数量的消息
用户在订单详情页面编辑订单数量时,给服务器发送订单修改信息。服务器接收该消息,查询该订单对应的供货商是否在线,如果在线,转发该消息;如果不在线,给供货商发送微信消息。
3.2.5 客户端发送修改订单项价格的消息
用户在订单详情页面编辑订单价格时,给服务器发送订单修改信息。服务器接收该消息,查询该订单对应的供货商是否在线,如果在线,转发该消息;如果不在线,给供货商发送微信消息。
3.2.6 客户端发送修改订单项备注的消息
用户在订单详情页面编辑订单备注时,给服务器发送订单修改信息。服务器接收该消息,查询该订单对应的供货商是否在线,如果在线,转发该消息;如果不在线,给供货商发送微信消息。
3.2.7 客户端发送确认订单的消息
用户在订单详情页确认收货时,给服务器发送订单确认消息。服务器接收该消息,查询该订单对应的供货商是否在线,如果在线,转发该消息;如果不在线,给供货商发送微信消息。
3.3 实战开发
3.3.1 服务器端开发
- pom文件引入netty jar包。
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
- 编写websocket server,使用主从Reactor线程模型。
@Component
public class WSServer {
private static class SingletionWSServer {
static final WSServer instance = new WSServer();
}
public static WSServer getInstance() {
return SingletionWSServer.instance;
}
private EventLoopGroup mainGroup;
private EventLoopGroup subGroup;
private ServerBootstrap server;
private ChannelFuture future;
public WSServer() {
mainGroup = new NioEventLoopGroup();
subGroup = new NioEventLoopGroup();
server = new ServerBootstrap();
server.group(mainGroup, subGroup).channel(NioServerSocketChannel.class).childHandler(new WSServerInitialzer());
}
public void start() {
this.future = server.bind(8088);
LoggerManager.info("netty websocket server 启动完毕...");
}
}
-
编写WSServerInitialzer,用于定义websocket使用的http协议;心跳支持;业务处理的注册
-
编写心跳连接的HeartBeatHandler,判断是读空闲、写空闲还是channel关闭
-
编写业务处理的OrderHandler。参考3.3.2小节。
-
编写启动netty的NettyBooter,在服务器端启动时启动netty。
3.3.2 OrderHandler
- 定义消息数据结构。
- 连接消息。用于绑定用户、订单sn与channel。
- 修改订单消息。接收到消息,保存数据库;查找在线的(供货商,订单),存在则发送消息;不存在发送微信消息。
- 心跳消息
3.3.3 小程序端开发
- 定义与后端同样的数据结构
- 创建一个websocket连接
- 微信监听websocket连接事件
- 向服务器端发生该用户以及订单在线的消息
- 心跳连接
- 向服务器端发生修改订单的消息
- 接收服务器端的消息(订单变化内容)
- 如果websocket没有建立连接,采用http请求发送订单修改信息。(未采用此方式,原因是后端代码改动量较大。现在的消息只是回写到前端实时展示)
3.4 技术细节
3.4.1 集成wss
- 什么是WS/WSS
WS是Web Socket的缩写
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:
WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。
WSS是Web Socket Secure的缩写即WebSocket加密版本。 - 为何使用WS/WSS
随着互联网的蓬勃发展,各种类型的WEB应用层出不穷,很多应用要求服务端有能力进行实时推送能力(比如直播间聊天室),以往很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
WebSocket实现了浏览器与服务器全双工(full-duplex)通信—允许服务器主动发送信息给客户端。
WebSocket协议的交互过程如下:
-
如何在阿里云负载均衡SLB上启用WS/WSS支持
无需配置,当选用HTTP监听时,默认支持无加密版本WebSocket协议;当选择HTTPS监听时,默认支持加密版本的WebSocket协议,即WSS;
-
限制于约束
负载均衡与ECS后端服务的连接采用HTTP/1.1,建议后端服务器采用支持HTTP/1.1的WebServer
若负载均衡与后端服务超过60秒无消息交互,会主动断开连接,如需要维持连接一直不中断,需要主动实现保活机制,每60秒内进行一次报文交互 -
wss 服务器端采用https,因此直接配置小程序端为wss协议即可。以上资料参考[6][7]。
- netty服务端并发量测试-未完待续