基于Netty的通讯构件设计与实现

 

基于Netty的通讯架构设计与实现

 

 

The design and implementation of communication architecture based on Netty

 

摘要

本文将以Netty框架为基础搭建一套通讯架构体系,在该通讯架构体系中集成Socket客户端和服务端、HTTP、HTTPS客户端和服务端、webservice客户端和服务端,该通讯体系将能够满足企业项目应用中对不同通讯协议的需求,同时实现了不同应用服务间的高性能通讯。

基于Netty开发的服务端通讯架构,主要由接收器、处理器、配置中心等模块组成。采用工厂模式,实现对Socket、Http、Webservice等多种服务端对象集合进行管理,通过通讯上下文初始化、通讯渠道初始化、线程池初始化、编解码处理器配置等操作,实现服务端通讯功能。

基于Netty开发的客户端通讯架构,主要由客户端工厂、发送器、处理器、配置中心等模块组成,实现对Socket、Http、Webservice等多种客户端对象集合进行管理,实现所有客户端配置服务加载和启动,通过通讯上下文初始化、线程池初始化、客户端发送器配置等操作,实现客户端通讯功能。

 

ABSTRACT

The server side communication architecture based on Netty is mainly composed of receiver, processor, configuration center and other modules. Using the factory mode, we can manage a variety of server end objects such as Socket, Http, Webservice and so on. Through the initialization of communication context, the initialization of communication channels, the initialization of the thread pool, the configuration of the codec processor and so on, the communication function of the server is realized.

The client communication architecture based on Netty is mainly composed of client factory, transmitter, processor, configuration center and other modules. It can manage a variety of client objects such as Socket, Http, Webservice and so on. All client configuration services are loaded and started, and the communication context initializes and thread pools are initialized. Initialization, client transmitter configuration and other operations to achieve client communication function.

 

目录

摘要... 3

ABSTRACT. 4

第一章 绪论... 6

1.1     研究背景... 7

1.2     研究现状... 7

1.3     研究内容... 8

1.4     论文的组织结构... 9

第二章 相关技术介绍... 10

2.1     JAVA BIO/NIO及问题... 10

2.2     RPC通讯... 11

2.3     Netty框架简介... 11

2.3.1     Reactor线程模型... 11

2.3.2     Netty线程模型... 13

2.4     WEBSERVICE服务实现... 14

2.4.1     WEBSERVICE服务调用原理... 14

2.5     Soap报文... 15

2.6     SOP报文规范... 15

第三章 通讯模块组件... 18

3.1     通讯模块总体架构... 18

3.1.1     服务端架构... 18

3.1.2     客户端架构... 19

3.2     SOCKET协议及相关实现... 20

3.2.1     SOCKET服务端... 20

3.2.2     SOCKET客户端... 27

3.2.3     SOP报文处理... 30

3.3     HTTP协议及相关实现... 33

3.3.1     HTTP服务端... 33

3.3.2     HTTP客户端... 39

3.3.3     HTTPS协议实现... 40

1.1.1     https证书制作... 44

3.4     WEBSERVICE协议及相关实现... 44

3.4.1     WEBSERVICE服务端... 45

3.4.2     WEBSERVICE客户端... 47

3.4.3     SPEED4J支持我行ESB对接开发... 48

3.4.4     SOAP报文... 52

4.5     异常情况处理... 55

4.5.1     异常信息设计... 55

4.5.2     异常情况处理... 56

第四章 性能结果分析... 56

5.1     推广情况... 57

5.2     性能测试样例... 57

4.2.1     测试背景... 57

4.2.2     性能需求指标... 57

4.2.3     生产环境拓扑图... 58

4.2.4     负载测试... 58

4.2.5     容量测试... 60

4.2.6     健壮性测试... 63

4.2.7     稳定性测试... 68

4.2.8     测试结论... 70

第五章 总结和展望... 72

6.1     总结... 72

6.2     展望... 72

参考文献... 72

致谢... 72

 

 

第一章 绪论

    1. 研究背景

在网络传输方式问题上,传统的RPC框架或者基于RMI等方式的远程服务(过程)调用采用了同步阻塞IO,当客户端的并发压力或者网络时延增大之后,同步阻塞IO会由于频繁的wait导致IO线程经常性的阻塞,由于线程无法高效的工作,IO处理能力自然下降。

在序列化方式问题上,Java序列化存在如下几个典型问题:

1) Java序列化机制是Java内部的一种对象编解码技术,无法跨语言使用;例如对于异构系统之间的对接,Java序列化后的码流需要能够通过其它语言反序列化成原始对象(副本),目前很难支持;

2) 相比于其它开源的序列化框架,Java序列化后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用;

3) 序列化性能差(CPU资源占用高)。

线程模型问题:由于采用同步阻塞IO,这会导致每个TCP连接都占用1个线程,由于线程资源是JVM虚拟机非常宝贵的资源,当IO读写阻塞导致线程无法及时释放时,会导致系统性能急剧下降,严重的甚至会导致虚拟机无法创建新的线程。

针对以上情况,我们需要研究解决传输、协议和线程问题,即,用什么样的通道将数据发送给对方,BIO、NIO或者AIO,IO模型在很大程度上决定了框架的性能;采用什么样的通信协议是HTTP或者内部私有协议,协议的选择不同,性能模型也不同;数据报如何读取,读取之后的编解码在哪个线程进行,编解码后的消息如何派发等问题,Reactor线程模型的不同,对性能的影响非常大。

    1. 研究现状

RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,RPC框架针对网络协议、网络I/O模型的封装是透明的,对于调用的客户端而言,它就认为自己在调用本地的一个对象。

目前典型的RPC实现框架有:Thrift(facebook开源)、Dubbo(alibaba开源)等。衡量一个RPC框架性能的好坏与否,RPC的网络I/O模型的选择及RPC框架的传输协议非常重要,目前RPC的网络I/O模型包括:支持阻塞式同步IO、非阻塞式同步IO、多路复用IO模型和异步IO模型,RPC开源实现框架的传输协议包括:TCP协议、HTTP协议及UDP协议。目前大多数RPC开源实现框架采用基于TCP和HTTP协议开发实现的。

实际应用为了提高单个节点的通信吞吐量,提高通信性能,我们一般首选的是NIO框架(No-block IO)。Java的NIO开发一个后端的TCP/HTTP服务器,附带考虑TCP粘包、网络通信异常、消息链接处理等网络通信细节,掌握起来学习成本较高,要求相当的技术功底和技术积累,因而采用业界主流的NIO框架进行服务器后端开发,是一个明智的选择。

主流的NIO框架主要有Netty、Mina,它们主要都是基于TCP通信,非阻塞的IO、灵活的IO线程池而设计的,可以应对高并发请求。

Mina(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。
   Netty是一款异步的事件驱动的网络应用框架和工具,是一个NIO客户端/服务器框架,支持快速、简单地开发网络应用,如协议服务器和客户端,它极大简化了网络编程。

BIO服务端通信模型,该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比,由于线程是JAVA虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能急剧下降,随着并发量的继续增加,可能会发生句柄溢出、线程堆栈溢出等问题,并导致服务器最终宕机。

与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式正好相反。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择同步阻塞IO以降低编程复杂度。但是对于高负载、高并发的网络应用,需要使用NIO的非阻塞模式进行开发。

 

    1. 研究内容

Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。

作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。

本文将以Netty框架为基础搭建一套通讯架构体系,在该通讯架构体系中会集成Socket客户端和服务端、HTTP及HTTPS客户端和服务端、webservice客户端和服务端,该通讯体系将能够满足企业应用中对不同通讯协议的需求,同时实现了各个应用服务间的高性能通讯。

    1. 论文的组织结构

全文共分为七章,主要从研究背景、研究内容、相关技术、系统需求分析、功能设计、系统架构设计与实现、服务部署与性能分析几个方面对论文进行组织。

第一章介绍了课题的研究背景和研究内容,同时也对和本课题相关的国内外现状进行了分析和介绍,最后对论文的整体结构进行了阐述。

第二章对通讯构件所涉及到的相关技术进行了介绍,包括BIO/NIO相关问题、Reactor线程模型、SOP报文、Soap报文等。

第三章对基于Netty的通讯架构设计与实现进行了总体介绍,首先对通讯总体架构设计进行了分析,接着对Netty框架集成SOCKET通讯协议及其相关实现进行了分析,然后对Netty框架集成HTTP和WEBSERVICE通讯协议及其相关实现进行了分析,最后通讯模块异常情况处理进行了分析。

第四章主要对Docker环境中部署的微服务架构系统性能进行测试分析,设计了一套性能测试架构,实现系统负载、容量、健壮性和稳定性测试分析。

第五章为总结和展望。在这一章中总结了基于Netty通讯架构的设计与实现,同时对高性能通讯框架发展方向给出了期望。

 

第二章 相关技术介绍

    1. JAVA BIO/伪异步IO/NIO模型简介
  1. BIO通信模型

BIO服务端通信模型,通常由一个独立的Acceptor线程负责监听客户端的连接,接收到客户端连接之后会为客户端创建一个新的线程来处理具体的业务逻辑,处理完成之后,返回应答消息给客户端,然后销毁线程,BIO通信模型是典型的一请求一应答模型。

  1. 伪异步IO通讯模型

伪异步IO模型采用线程池和任务队列来实现。当有客户端接入时,客户端的SOCKET被封装成为一个任务(该任务实现了Runnable接口),然后将该任务投递到后端的线程池中进行处理,JDK的线程池会维护一个队列和多个线程,实现对队列中的任务进行处理。线程池可以设置消息队列的大小和最大线程数,因而资源占用是可以控制的。

  1. NIO异步非阻塞通信模型

NIO异步非阻塞通讯采用IO多路复用器技术,将多个IO的阻塞复用到同一个select的阻塞上,仅需要一个线程负责多路复用器Selector轮询操作,就可以接入成千上万的客户端。

与传统的多线程/多进程模型比,I/O多路复用技术的优势是系统开销小,不需要创建新的额外进程或者线程,同时也降低了系统的维护工作量,节省了系统资源。

    1. RPC通讯

 

    1. Netty框架简介
      1. Reactor线程模型

常用的Reactor线程模型有三种,分别为:Reactor单线程模型、Reactor多线程模型、主从Reactor多线程模型。

  1. Reactor单线程模型

Reactor单线程模型,指的是所有的IO操作都在同一个NIO线程上面完成。

Reactor单线程模型示意图如下所示:

https://res.infoq.com/articles/netty-high-performance/zh/resources/0808020.png

图2-3 Reactor单线程模型

Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会导致阻塞,一个NIO线程通过Acceptor接收客户端的TCP连接请求消息,链路建立成功之后,通过Dispatch将对应的ByteBuffer派发到指定的Handler上进行消息解码,用户Handler可以通过NIO线程将消息发送给客户端。

  1. Rector多线程模型

Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理IO操作。

https://res.infoq.com/articles/netty-high-performance/zh/resources/08080211.png

图2-4 Reactor多线程模型

Reactor多线程模型的特点:

1) 有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP连接请求;

2) 网络IO操作-读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;

3) 1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。

  1. 主从Reactor线程模型

主从Reactor线程模型的服务端用于接收客户端连接的是一个独立的NIO线程池。Acceptor接收到客户端TCP连接请求处理完成后,将新创建的SocketChannel注册到IO线程池(sub reactor线程池)的某个IO线程上,由它负责SocketChannel的读写和编解码工作。Acceptor线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,便将链路注册到后端subReactor线程池的IO线程上,由IO线程负责后续的IO操作。

它的线程模型如下图所示:

图2-5 Reactor主从多线程模型

利用主从NIO线程模型,可以解决1个服务端监听线程无法有效处理所有客户端连接的性能不足问题。因此,在Netty的官方demo中,推荐使用该线程模型。

      1. Netty线程模型
  1. Netty线程模型 

Netty的线程模型并非固定不变,通过在启动辅助类中创建不同的EventLoopGroup实例并通过适当的参数配置,就可以支持Reactor单线程模型、多线程模型、主从Reactor线程模型。

Netty架构按照Reactor模式设计和实现,Netty的IO线程NioEventLoop聚合了多路复用器Selector,可以同时并发处理成百上千个客户端Channel连接,并且读写操作都是非阻塞的,避免了频繁IO阻塞导致的线程挂起。

                                                                                     图2-6  Netty线程模型

  1. 无锁化串行设计

为了尽可能提升性能,Netty采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降。通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优,而且避免了多线程操作导致的锁的竞争,从性能角度看是最优的。

  1. 零拷贝

Netty的“零拷贝”主要体现在如下三个方面:

  1. Netty采用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。
  2. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer,提升了内存使用效率。
  3. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
  1. 内存池

对于缓冲区Buffer,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty提供了基于内存池的缓冲区重用机制。Netty提供了多种内存管理策略,通过在启动辅助类中配置相关参数,实现差异化的定制。

    1. WEBSERVICE服务实现
      1. WEBSERVICE服务调用原理

http://images.cnblogs.com/cnblogs_com/lingfengtian/1206283249.jpg

实现一个完整的Web服务包括以下步骤:
◆ Web服务提供者设计实现Web服务,并将调试正确后的Web服务通过Web服务中介者发布,并在UDDI注册中心注册; (发布)
◆ Web服务请求者向Web服务中介者请求特定的服务,中介者根据请求查询UDDI注册中心,为请求者寻找满足请求的服务; (发现)
◆ Web服务中介者向Web服务请求者返回满足条件的Web服务描述信息,该描述信息用WSDL写成,各种支持Web服务的机器都能阅读;(发现)
◆ 利用从Web服务中介者返回的描述信息生成相应的SOAP消息,发送给Web服务提供者,以实现Web服务的调用;(绑定)
◆ Web服务提供者按SOAP消息执行相应的Web服务,并将服务结果返回给Web服务请求者。(绑定)

    1. Soap报文

SOAP简单对象访问协议是“Simple Object Access Protocol”的简称,。SOAP可以运行在任何其他传输协议上,在传输层之间的头是不同的,但XML有效负载保持相同。Web Service通常会使用HTTP或HTTPS与SOAP绑定。   

SOAP在安全方面是通过使用XML-Security和XML-Signature两个规范组成了WS-Security来实现安全控制的。报文编码推荐采用UTF-8或GBK,编码需要在XML头的encoding中定义。

    1. SOP报文规范

如图3-2 所示为SOP报文通讯架构,SOP报文的通讯方式采用Socket短连接的方式进行处理,报文编码是GBK。

                 图3-9 SOP报文通讯架构

  1. Sop报文结构

                     图3-10 sop报文结构

 如图3-10所示,sop报文结构具体包含内容如下:

  1. 通讯数据包由公共信息部分、交易数据部分。
  2. 公共信息部分包括系统信息头和交易公共信息头。
  3. 交易数据部分包括交易数据头(可选)、业务数据和系统控制命令。
  4. 业务数据部分又包括数据单元、表格和对象。
  5. 业务数据部分可以插入系统控制命令。

整个通讯数据包的组成示意图如下:

公共信息

交易数据

 

交易头

业务数据

系统信息头

交易公共信息头

交易数据头

数据单元

数据单元

数据单元

表格

单元

对象

单元

。。。

控制命令码可以穿插在业务数据之间,如单元与单元之间,表格内部数据项之间,以及对象内数据项之间

                 
  1. 数据单元

数据单元TRANFLD是COP和SOP中代表特定内容的基本数据项,一般对应一个应用数据定义,在SOP平台中采用可自解包的通讯格式。数据单元通讯格式如下:

可选

机构名

可选

金额

可选

266字节数据

。。。

属性

0X5

“A网点”

属性

0X6

“123.45”

属性

0xFF

250字节

0X10

16字节

 

 

                         

每一个数据单元在通讯格式中用两个部分表示——长度+内容。内容部分都以字符串方式传输,截掉前导和后续的空格,以减少冗余数据的传输。长度以一个字节的16进制数表示,可表示的最大长度为250(0XFA)字节,若数据单元长度超过250字节,则采用分解传送的方式,以0XFF表示数据单元超长,如上图中266字节数据单元。

  1. 表格单元

表格单元FORM是指COP和SOP平台中由格式相同的多条记录组成的复合数据单元,其中每一条记录的数据又由多个数据单元组成。表格在通讯格式中以表格名+记录条数+多条记录数据组成,每一条记录又由多个数据单元(表示方法同数据单元)表示。

表格单元的通讯格式如下:

表格名

记录条数

栏位个数

可选项

记录01

记录02

0X3

“F01”

0X2

0X3

打印属性或其它

0X3

“ABC”

0X2

0x7

“1234.99”

0X4

“李明”

0X2

0x5

“20.00”

表格名长度

表格名称

 

 

见下表

姓名项

借贷标志项

金额项

姓名项

借贷标志项

金额项

                                 

属性是可选项,在一般数据单元的定义中不使用。

在本系统中,记录条数不得大于250个。如果记录条数可能大于250条,必须采用文件传输的方式解决这一问题。

  1. 对象单元

在COP/SOP系统中,对象的类型可以根据需求进行扩展和定义,目前支持的对象类型包括窗口对象、打印对象等。

对象一般由对象名+数据单元(可选项)+表格单元(可选项)组成。一个包含数据单元和表格单元的窗口对象的通讯格式如下:

对象名

对象内容

0X5

“OBJ01”

数据单元

数据单元

。。。

表格单元01

 

数据单元

。。。

               

窗口对象的对象内容中不包含数据单元和表格单元的可选属性,如行、列、模式等信息。

打印对象由对象名+[打印属性+数据单元](可选项)+[表格名称+记录条数+栏位数+打印属性+格数据](可选项)+打印控制命令码(可选项)组成。打印控制命令码见打印控制命令码介绍。

  1. MapFile的文件格式

通讯打包数据的配置,包括对象配置文件和 G R I D 配置文件,其格式为 ;第一行指明该对象的输出方向 ,WINDOW代表窗口,PRINTER代表打印机, 从第二行起格式如下可以有任意行:

  { F L D | G R D } : n a m e  l e n  t y p e  s c a l e  a l i g n  f i l l c h a r  t u r n m o d e

s t r i n g  e n c r y p t (对于GRID只能有FIELD,配置文件名就是GRID的名字 ,具体字段的含义由应用层定(都是由后台接口文件自动生成)。

其意义分别是 :

F L D | G R D

字段或表格标识,当为G R D 时,后面只有name项

n a m e

f i e l d字段名称或表格或G r i d 名 称

l e n

字段长度,为实际的存储长度,如整型为 4

t y p e

字段类型 ,n - s h o r t ,N - i n t e g e r,L - l o n g,D - d e c i m a l ,S - c h a r,Q - d a t e ,T - t i m e,H - h e x,B - C H N 。

s c a l e

小数点后位数

a l i g n

对齐方式,0 - 左对齐1-右对齐 2 - 中对齐  3 - 无对齐

f i l l c h a r

填充字符

t u r n m o d e

转换模式:1-将传回的数据转为汉字金额2-转换为列表 中 的内容3-将数据转换为日期大写4-为空不打印 5-不管为不为空都不打印

s t r i n g

如果要转换成列表内容,则指明列表名,否则为空(填 NULL)。

e n c r y p t

加密标志,1-加密, 0-不加密

Mapfile文件内容如下:

W I N D O W

F L D : G U I Y D H   8   S   0   0   0   0   N U L L   0

F L D : G U I Y X M   2 2  S   0   0   0   0   N U L L   0

G R D : F 9 3 1 3 0 1

 

 

 

第三章 通讯模块组件

    1. 通讯模块总体架构
      1. 服务端架构

如图3.1是基于Netty开发的服务端通讯架构,该架构由接收器、处理器、配置中心等模块组成。下面对各个模块进行详细介绍:

  • ServerFactory:服务端工厂,主要实现对Socket、Http、Webservice等多种服务端对象集合进行管理。
  • SocketServer/HttpServer/WebserviceServer:主要实现3个功能,1初始化通讯上下文,2初始化通讯管道,3设置线程池,监听客户端服务请求。
  • Channel:通讯渠道,主要用于建立线程池、绑定端口,添加不同handler处理类。
  • Handler:处理器,用于处理数据读写和编解码工作。
  • ContextConfig:上下文配置器,主要用于加载服务端配置信息。

                                                         图3.1服务端架构图

      1. 客户端架构

如图3.2是基于Netty开发的客户端通讯架构图,该架构由客户端工厂、发送器、处理器、配置中心等模块组成。下面对各个模块进行详细介绍:

  • ClientFactory:客户端工厂,主要实现对Socket、Http、Webservice等多种客户端对象集合进行管理,实现所有客户端配置服务加载和启动。
  • SocketClient/HttpClient/WebserviceClient:主要功能为:1初始化通讯上下文;2初始化拦截器;3创建发送器线程对象,设置线程池参数。
  • ThreadPool:线程池,创建任务处理线程池,对Socket、Http、Webservice等发送器线程进行管理。
  • ClientSender:客户端发送器,提供Socket、Http、Webservice等客户端数据发送功能。
  • ContextConfig:上下文配置器,主要用于加载客户端配置信息。

                                                    图3.2客户端架构图

 

 

    1. SOCKET协议及相关实现
      1. SOCKET服务端

如图3-3所示为Netty框架实现SOCKET服务端通讯架构的设计类图,在类图中描述了各个类之间的关联、继承、实现和依赖关系。

                       图3-3 Socket服务端类图

  1. SOCKET服务端加载过程

类名称

描述

ServerConf

该类是服务端配置信息父类,属性包括服务端配置ID、监听端口、交易码解析器Bean名称、自定义过滤器Bean名称列表、服务器端错误返回格式转换ID、服务器错误返回BeanID、交易列表。

SocketServerConf

该类是服务端配置信息类,属性包括:连接线程池大小、工作线程池大小、数据流入处理器Bean名称列表、数据流出处理器Bean名称列表、超时时间。

ServerCtx

该类是服务端运行时上下文父类,属性包括:自定义过滤器列表、交易码解析器、交易map、错误信息。

SocketServerCtx

该类是socket服务端运行时上下文类,属性包括:数据流入处理器Bean名称列表、数据流出处理器Bean名称列表。

CommTran

该类是交易定义类,属性包括:交易码、交易描述.。

ServerTran

该类是服务端交易类,属性包括:是否选择格式转换、是否选择流程、数据流入格式转换ID、数据流出格式转换ID、调用交易引擎流程ID。

                           图3-4 Socket服务端加载流程图

如图3-4为Socket服务端加载流程图,该流程图展示了Netty集成Socket协议栈,并实现初始化配置的过程,Socket服务端加载逻辑如下所述:

首先配置Socket服务端基础信息,这些基础信息配置过程是由Eclipse插件所开发的工具实现的,具体配置信息包括监听端口、交易码解析器Bean名称、自定义过滤器Bean名称列表、连接线程池大小、工作线程池大小等。

其次调用CommServerFactory工厂类完成所有Socket服务端配置初始化加载,在加载过程中使用ResourceUtil工具类对相关配置文件进行循环遍历,逐个加载,具体加载内容包括:读取指定文件夹下所有Socket配置文件、定义server类型为Socket服务端类型配置、创建一个SocketServer对象。

然后执行SocketServer对象的InitServer()初始化方法,将配置文件信息加载到SocketServerCtx服务器上下文中,至此Socket服务端配置文件信息便全部加载到SocketServerCtx对象中。

接下来完成Netty与Socket协议栈的集成过程。

首先创建一个SocketChannelInitializer()对象,完成通讯通道初始化操作,使用SocketChannel类创建ChannelPipeline对象,在ChannelPipeline对象中加入相关流入和流出拦截器,并创建SocketServerHandler业务处理类,添加到channelPipeline中,用于执行具体的业务逻辑。

然后调用线程的startServer()方法,启动Socket服务端线程,在线程启动过程中,设置监听线程池和工作线程池,绑定监听端口,实现对客户端请求进行监听,当监听器接收到客户端的连接请求后便开始和客户端进行通信,至此socket服务端加载完成。

SocketServerHandler业务处理类继承ChannelInboundHandlerAdapter类,重新父类方法channelRead(final ChannelHandlerContext ctx, Object msg),其中对象msg就是具体的报文数据信息。

for (IDatagramInterceptor interceptor : serverCtx.datagramInterceptors) {

                interceptor.decodeInterceptor(inputByte);

            }

介绍报文后执行拦截器处理,拦截器接口为IDatagramInterceptor接口类,

 

  1. SOCKET服务端工作流程

                   图3-5 Socket服务端工作流程图

图3-5 为Socket服务端工作流程图,Socket服务端处理逻辑如下所述:

Socket服务接收数据信息;

  1. 调用socketServerHandler对象进行逻辑处理;
  2. 接收报文,执行自定义过滤器datagramInterceptors,对报文内容进行过滤处理;
  3. 调用交易码解析器,对报文内容进行解码处理;
  4. 执行平台格式转换或自定义格式转换;
  5. 调用交易引擎对报文内容按照交易逻辑进行处理;
  6. 判断交易逻辑处理过程是否正确;
  7. 正确时执行数据格式转换,错误时执行异常处理,同时对错误信息进行格式转换;
  8. 执行datagramInterceptors拦截器处理;
  9. 返回客户端应答信息,Socket请求结束。

 

接口datagramInterceptors拦截器定义了解码拦截器和编码拦截器。

public interface IDatagramInterceptor {

    void decodeInterceptor(byte[] datagram);

 

    void encodeInterceptor(byte[] datagram, String trancode);

   

    byte[] encodeInterceptor(byte[] datagram, String trancode, Object args);

}

 

  1. SOCKET服务端编解码处理

Netty架构支持多种不同的编解码框架,例如google的protobuf编解码框架、facebook的thrift编解码框架等,在Socket服务通讯中按照不同的业务需求定制化了不同的编解码器,socket服务端接收报文数据过程中,netty框架以责任链模式分别调用解码器、业务处理handler和编码器,具体相关编解码器列表如下:

解码器

编码器

长度字符串解码器

new LengthFieldBasedFrameDecoder()

长度字符串打包编码器

new LengthFieldPrependerSetter()

定长解码器

new FixedLengthFrameDecoder()

长度字符串切割编码器

new LengthFieldCutter()

分隔符解码器

new DelimiterBasedFrameDecoder()

自定义编码器

new  ChannelOutboundHandlerAdapter()

回车符解码器

new LineBasedFrameDecoder

 

自定义解码器

new ChannelInboundHandlerAdapter()

 

在工程下的src/main/java目录下新建服务端对应的交易类,为了演示方便只做简单的打印,代码如下:

package com.spdb.speed4j.tran.demo;

import java.util.HashMap;

import java.util.Map;

import org.springframework.stereotype.Component;

import com.spdb.speed4j.comm.tran.service.ITranFlowConvertType;

@Component

public class TranCore implements ITranFlowConvertType {

   @Override

   public Map tranFlowConvert(Map tranFlowData) {

      System.out.println("Process......");

      Map<String, Object> map = new HashMap<String, Object>();

      return tranFlowData;

   }

}

函数中的输入参数和输出结果都为Map类型,所以我们定义了两个JAVAMAP接口,实现交易数据的解包处理以及对客户端返回数据进行打包处理。

新建Socket服务渠道,配置通讯Handler,如果通讯过程中发生粘包和拆包时,则需配置流入、流出处理Handler列表,展示页面如图3-6所示。

数据流入处理器Bean名称对应着不同的解包处理方式,包括:长度字符串解包、定长解包、分隔符解包、自定义解包及回车符解包等。数据流出处理器Bean名称对应着不同的打包处理方式,包括:长度字符串打包、长度字符串切割及自定义bean打包处理等。

图3-6通讯Handler配置图

  1. SOCKET服务端XML/MAP格式转换

使用SOCKET通讯方式,服务端业务逻辑处理handler获取解码后的字节数组,该字节数组转换为字符串后是一个标准XML字符串,而接口处理函数的参数是MAP集合,因此需要完成XML到MAP集合的转换操作。

目前操作xml文件的开源项目已经非常成熟,比较流行有JiBX、XStream、JDOM、dom4j等,本论文支持自定义转换和平台转换两种方式,平台转换采用XStream技术实现XML到POJO对象转换。

图3-7 Socket服务端基础配置图

  1. SOCKET服务端初始化执行代码

初始化过程中首先创建ChannelPipeline对象,然后添加LoggingHandler执行类。按照Inbound数据流入处理器Outbound数据流出处理器顺序调用分别按顺序调用编码及解码器。

Inbound顺序

按照Inbound顺序,判断数据流入处理器bean名称:

    1. 可实例化为LengthFieldBasedFrameDecoderConf对象的,则进行长度字符串解包处理,如果是字符类型解析,则添加LengthFieldBasedFrameDecoderSetter类对象作为解包handler处理类,如果是数值类型解析,则添加LengthFieldBasedFrameDecoder类对象作为解包handler处理类,这两个类继承自netty的LengthFieldBasedFrameDecoder类对象。
    2. 可实例化为FixedLengthFrameDecoderConfig类对象的,则进行定长解包,添加netty的FixedLengthFrameDecoder类作为解包handler处理类。
    3. 可实例化为DelimiterBasedFrameDecoderConfig类对象的,则进行分隔符解包,创建ByteFul对象,循环遍历string[],使用netty的Unpooled类的CopiedBuffer()方法将内容的byte字节码添加到ByteBuf对象中,然后添加DelimiterBasedFrameDecoder类作为解包handler处理类。
    4. 可实例化为CustomHandlerConfig类对象的,则进行自定义解包,通过CustomHandlerConfig类中的bean名称,寻找对应的ChannelInboundHandlerAdapter接口具体实现类对象作为解包handler处理类。
    5. 可实例化为LineBasedFrameConfig类对象的,则进行回车符解包,添加netty的LineBasedFrameDecoder类作为解包handler处理类。

 

Outbound顺序

按照Outbound顺序,判断数据流出处理器bean名称:

    1. 可实例化为LengthFieldPrependerSetterConfig对象的,则进行长度字符串打包处理,添加LengthFieldPrependerSetter类对象作为打包handler处理类,该类继承自netty的LengthFieldPrepender类对象。
    2. 可实例化为LengthFieldCutterConfig对象的,则进行长度字符串切割打包处理,添加LengthFieldCutter类对象作为打包handler处理类,该类继承自netty的MessageToByteEncoder<ByteBuf>类对象。
    3. 可实例化为CustomHandlerConfig类对象的,则进行自定义bean打包,通过CustomHandlerConfig类中的bean名称,寻找对应的ChannelOutboundHandlerAdapter接口具体实现类对象作为解包handler处理类。

最后再添加SocketServerHandler业务逻辑处理handler对象。

      1. SOCKET客户端

如图3-6所示为Netty框架实现SOCKET客户端通讯架构的设计类图,在类图中描述了各个类之间的关联、继承、实现和依赖关系。

                                                           图3-6 Socket客户端类图

  1. SOCKET客户端加载过程

类名称

属性描述

ClientConf

该类是客户端配置信息类,属性包括服务端配置ID、监听端口、交易码解析器Bean名称、自定义过滤器Bean名称列表、服务器端错误返回格式转换ID、服务器错误返回BeanID、交易列表。

SocketClientConf

该类是Socket客户户端配置信息类,属性包括:数据流入处理器Bean名称列表、数据流出处理器Bean名称列表。

ClientCtx

该类是客户端运行时上下文父类,属性包括:自定义过滤器列表、交易码解析器、交易map、错误信息。

SocketClientCtx

该类是socket客户端运行时上下文类,属性包括:数据流入处理器Bean名称列表、数据流出处理器Bean名称列表。

CommTran

该类是交易类,属性包括:交易码、交易描述.。

ClientTran

该类是客户端交易类,属性包括:是否选择格式转换、数据流入格式转换ID、数据流出格式转换ID。

                       图3-7 Socket客户端加载流程图

图3-7 为Socket客户端加载流程图,Socket客户端加载逻辑如下所述:

  1. 使用工具配置Socket客户端基础信息,包括监听端口、交易码解析器Bean名称、自定义过滤器Bean名称列表等;
  2. 系统使用CommClientFactory工厂类实现对所有Socket服务端进行初始化加载配置。
  3. 使用ResourceUtil工具类读取指定文件夹下所有Socket配置文件信息,并对各个配置文件进行循环遍历,逐个加载;
  4. 定义Client类型为SOCKET客户端;
  5. 创建SocketClient对象;
  6. 执行SocketClient对象的InitServer()初始化方法;
  7. 将配置文件信息加载到SocketClientCtx客户端上下文中;
  8. 初始化Client线程池,配置核心线程数、最大线程数、队列容量等;
  9. 将新建立的SocketClient对象加入到ClientMap缓存中;
  10. 客户端正常启动,配置结束。
  1. SOCKET客户端工作流程

                         图3-8 Socket客户端工作流程图

Socket客户端处理逻辑如下所述:

  1. 执行doRequest请求;
  2. 获取需要加载的Socketclient缓存对象列表信息;
  3. Socket客户端请求开始;
  4. 创建一个同步线程SocketClientSender对象,用于执行客户端请求;
  5. 设置线程池,将SocketClientSender对象提交给线程池执行;
  6. 创建一个SocketClientChannelInitializer对象;
  7. 建立连接使用的Socketchannel对象;
  8. 建立SocketClientHandler对象;
  9. 执行平台格式转换或自定义格式转换;
  10. 执行过滤器datagramInterceptors处理;
  11. 将待发送数据加入到发送缓存中;
  12. 将SocketClientHandler对象加入到channelPipeline中;
  13. 触发发送数据;
  14. 接收到响应数据;
  15. 执行datagramInterceptor过滤器处理;
  16. 执行resultDecoder进行结果数据解码;
  17. 判断解码处理过程是否正确;
  18. 正确时执行数据格式转换,错误时执行异常处理;
  19. Socket客户端请求结束。

 

  1. SOCKET服务端初始化执行代码

初始化过程中首先创建ChannelPipeline对象,然后添加LoggingHandler执行类。按照Inbound数据流入处理器Outbound数据流出处理器顺序调用分别按顺序调用编码及解码器。

Inbound顺序

按照Inbound顺序,判断数据流入处理器bean名称:

    1. 可实例化为LengthFieldBasedFrameDecoderConf对象的,则进行长度字符串解包处理,如果是字符类型解析,则添加LengthFieldBasedFrameDecoderSetter类对象作为解包handler处理类,如果是数值类型解析,则添加LengthFieldBasedFrameDecoder类对象作为解包handler处理类,这两个类继承自netty的LengthFieldBasedFrameDecoder类对象。
    2. 可实例化为FixedLengthFrameDecoderConfig类对象的,则进行定长解包,添加netty的FixedLengthFrameDecoder类作为解包handler处理类。
    3. 可实例化为DelimiterBasedFrameDecoderConfig类对象的,则进行分隔符解包,创建ByteFul对象,循环遍历string[],使用netty的Unpooled类的CopiedBuffer()方法将内容的byte字节码添加到ByteBuf对象中,然后添加DelimiterBasedFrameDecoder类作为解包handler处理类。
    4. 可实例化为CustomHandlerConfig类对象的,则进行自定义解包,通过CustomHandlerConfig类中的bean名称,寻找对应的ChannelInboundHandlerAdapter接口具体实现类对象作为解包handler处理类。
    5. 可实例化为LineBasedFrameConfig类对象的,则进行回车符解包,添加netty的LineBasedFrameDecoder类作为解包handler处理类。

Outbound顺序

按照Outbound顺序,判断数据流出处理器bean名称:

    1. 可实例化为LengthFieldPrependerSetterConfig对象的,则进行长度字符串打包处理,添加LengthFieldPrependerSetter类对象作为打包handler处理类,该类继承自netty的LengthFieldPrepender类对象。
    2. 可实例化为LengthFieldCutterConfig对象的,则进行长度字符串切割打包处理,添加LengthFieldCutter类对象作为打包handler处理类,该类继承自netty的MessageToByteEncoder<ByteBuf>类对象。
    3. 可实例化为CustomHandlerConfig类对象的,则进行自定义bean打包,通过CustomHandlerConfig类中的bean名称,寻找对应的ChannelOutboundHandlerAdapter接口具体实现类对象作为解包handler处理类。

最后再添加SocketClientHandler业务逻辑处理handler对象。

 

      1. SOP报文处理

如图3-2 所示为SOP报文通讯架构,SOP报文的通讯方式采用Socket短连接的方式进行处理,报文编码是GBK。

                 图3-9 SOP报文通讯架构

 

  1. Sop报文结构

                     图3-10 sop报文结构

 如图3-10所示,sop报文结构具体包含内容如下:

通讯数据包由公共信息部分、交易数据部分;公共信息部分包括系统信息头和交易公共信息头;交易数据部分包括交易数据头(可选)、业务数据和系统控制命令;业务数据部分又包括数据单元、表格和对象;业务数据部分可以插入系统控制命令。

控制命令码可以穿插在业务数据之间,如单元与单元之间,表格内部数据项之间,以及对象内数据项之间

整个通讯数据包的组成示意图如下:

公共信息

交易数据

 

交易头

业务数据

系统信息头

交易公共信息头

交易数据头

数据单元

数据单元

数据单元

表格

单元

对象

单元

。。。

                 
  1. 数据单元

数据单元TRANFLD是COP和SOP中代表特定内容的基本数据项,一般对应一个应用数据定义,在SOP平台中采用可自解包的通讯格式。数据单元通讯格式如下:

可选

机构名

可选

金额

可选

266字节数据

。。。

属性

0X5

“A网点”

属性

0X6

“123.45”

属性

0xFF

250字节

0X10

16字节

 

 

                         

每一个数据单元在通讯格式中用两个部分表示——长度+内容。内容部分都以字符串方式传输,截掉前导和后续的空格,以减少冗余数据的传输。长度以一个字节的16进制数表示,可表示的最大长度为250(0XFA)字节,若数据单元长度超过250字节,则采用分解传送的方式,以0XFF表示数据单元超长,如上图中266字节数据单元。

  1. 表格单元

表格单元FORM是指SOP平台中由格式相同的多条记录组成的复合数据单元,其中每一条记录的数据又由多个数据单元组成。表格在通讯格式中以表格名+记录条数+多条记录数据组成,每一条记录又由多个数据单元(表示方法同数据单元)表示。

表格单元的通讯格式如下:

表格名

记录条数

栏位个数

可选项

记录01

记录02

0X3

“F01”

0X2

0X3

打印属性或其它

0X3

“ABC”

0X2

0x7

“1234.99”

0X4

“李明”

0X2

0x5

“20.00”

表格名长度

表格名称

 

 

见下表

姓名项

借贷标志项

金额项

姓名项

借贷标志项

金额项

                                 

在本系统中,记录条数不得大于250个。如果记录条数可能大于250条,必须采用文件传输的方式解决这一问题。

  1. 对象单元

在SOP系统中,对象的类型可以根据需求进行扩展和定义,目前支持的对象类型包括窗口对象、打印对象等。

对象一般由对象名+数据单元(可选项)+表格单元(可选项)组成。一个包含数据单元和表格单元的窗口对象的通讯格式如下:

对象名

对象内容

0X5

“OBJ01”

数据单元

数据单元

。。。

表格单元01

 

数据单元

。。。

               
  1. MapFile的文件格式

通讯打包数据的配置,包括对象配置文件和 G R I D 配置文件,其格式为 ;第一行指明该对象的输出方向 ,如:WINDOW代表窗口,PRINTER代表打印机, 从第二行起格式如下可以有任意行:

  { F L D | G R D } : n a m e  l e n  t y p e  s c a l e  a l i g n  f i l l c h a r  t u r n m o d e

s t r i n g  e n c r y p t

其具体含义如表所示 :

F L D | G R D

字段或表格标识,当为G R D 时,后面只有name项

n a m e

f i e l d字段名称或表格或G r i d 名 称

l e n

字段长度,为实际的存储长度,如整型为 4

t y p e

字段类型 ,n - s h o r t ,N - i n t e g e r,L - l o n g,D - d e c i m a l ,S - c h a r,Q - d a t e ,T - t i m e,H - h e x,B - C H N 。

s c a l e

小数点后位数

a l i g n

对齐方式,0 - 左对齐1-右对齐 2 - 中对齐  3 - 无对齐

f i l l c h a r

填充字符

t u r n m o d e

转换模式:1-将传回的数据转为汉字金额2-转换为列表 中 的内容3-将数据转换为日期大写4-为空不打印 5-不管为不为空都不打印

s t r i n g

如果要转换成列表内容,则指明列表名,否则为空(填 NULL)。

e n c r y p t

加密标志,1-加密, 0-不加密

Mapfile文件内容如下:

W I N D O W

F L D : G U I Y D H   8   S   0   0   0   0   N U L L   0

F L D : G U I Y X M   2 2  S   0   0   0   0   N U L L   0

G R D : F 9 3 1 3 0 1

 

    1. HTTP协议及相关实现
      1. HTTP服务端

 

 

类名称

属性描述

CommServerFactory

 

HttpServerChannelInitializer

 

HttpServer

 

HttpServerConf

 

SSLManagerFactoryUtil

 

 

 

 

 

 

以下工厂类对象启动时会加载所有的XML文件,完成服务端启动注册的过程。XML配置文件均使用xstream技术进行解析。服务端通讯启动过程中需要先完成初始化过程,然后启动异步线程,交由netty进行管理。

/**

     * 加载Server配置文件

     */

    private void loadServerFromXML(String server){

     Resource[] resources = null;

     LogManager.COMM_LOGGER.info("开始加载"+server+"服务端配置");

         resources = ResourceUtil.getResourceList(serverloadMap.get(server));

         if (resources == null) {

            LogManager.COMM_LOGGER.error("加载"+server+"服务端配置列表失败");

         } else {

             for (int i = 0; i < resources.length; i++) {

                 XStream xstream = new XStream(new DomDriver());

                 String uri = null;

                 try {

                     uri = resources[i].getURI().toString();

                     LogManager.COMM_LOGGER.info("开始加载"+server+"服务端配置:" + uri);

                     ServerConf conf = (ServerConf) xstream.fromXML(resources[i].getInputStream());

                     checkServerIDDup(conf.id);

                     IServer iServer = (IServer) ctx.getBean(server);

                     iServer.setServerConf(conf);

                     iServer.initServer();

                     if (iServer.isInited()) {

                         serverList.add(iServer);

                         iServer.startServer();

                     }

                     }

                 } catch (Exception e) {

                    LogManager.COMM_LOGGER.error("加载"+server+"服务端配置:" + uri + "失败", e);

                 }

             }

         }

    }

以下为异步线程初始化过程的核心代码,主要包括建立通讯上下文、完成交易码解析、遍历添加交易列表及初始化通讯通道,其中初始化通讯渠道主要用于创建ChannelPipeline添加并设置各种ChannelHandler,包括各种编解码器及自定义handler服务。

@Override

    public void initServer() {

        // 建立通讯上下文

        serverCtx = new HttpServerCtx();

        // 交易码解析

        try {

            serverCtx.httpTrancodeDecoder = (IHttpTrancodeDecoder) springCtx.getBean(httpServerConf.trancodeDecoderBean);

        } catch (Exception e) {

            serverCtx.errorMsg = "Http服务端:" + httpServerConf.id + " - 初始化交易码解析器失败";

            logManager.COMM_LOGGER.error(serverCtx.errorMsg, e);

            return;

        }

        // 交易列表

        Map<String, ServerTran> map = new HashMap<String, ServerTran>();

        for (ServerTran tran : httpServerConf.tranList) {

            map.put(tran.tranCode, tran);

        }

        serverCtx.tranMap = map;

        // 初始化通讯通道

        try {

           httpServerChannelInitializer = new HttpServerChannelInitializer(httpServerConf, serverCtx, springCtx);

       } catch (Exception e) {

           serverCtx.errorMsg = "Http服务端:" + httpServerConf.id + " - 初始化通讯通道失败";

            logManager.COMM_LOGGER.error(serverCtx.errorMsg, e);

            return;

       }

        isInited = true;

        logManager.COMM_LOGGER.info("Http服务端:" + httpServerConf.id + " - 初始化成功");

    }

 

初始化渠道通讯过程中,主要完成创建ChannelPipeline添加并设置各种ChannelHandler,包括各种编解码器及自定义handler服务。

在HTTP通讯过程中,ChannelPipeline中添加了SslHandler用于安全认证, HttpRequestDecoder解码器、HttpObjectAggregator对象聚合解码器、 HttpResponseEncoder消息应答编码器及httpServerHandler业务逻辑处理器。

@Override

    protected void initChannel(NioSocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        //添加日志信息

        if(httpServerConf.isLogger){

        pipeline.addLast(new LoggingHandler());  

        }

        //SSL加密设置配置

        if(httpServerConf.isEncryption){

        //SSL加密设置配置

            sslHandler = sSLManagerFactoryUtil.configureSSLOnDemand(httpServerConf.sslManagerConf, true);

        if (sslHandler != null) {

                pipeline.addLast("ssl", sslHandler);

            }

        }

        pipeline.addLast(new ReadTimeoutHandler(httpServerConf.timeout, TimeUnit.MILLISECONDS));

        pipeline.addLast(new HttpRequestDecoder());

        pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));

        pipeline.addLast(new HttpResponseEncoder());

        // 最终处理返回流程

        HttpServerHandler httpServerHandler = new HttpServerHandler(httpServerConf, serverCtx, springCtx);

        pipeline.addLast(httpServerHandler);

    }

 

 

以下为异步线程启动过程,在该方法中定义了一个当前自身的runnable异步线程,并执行start()线程启动方法,调用自身的run()方法。

@Override

    public void startServer() throws CommException {

        status = ServerStatus.STARTING;

        if (!isInited) {

            status = ServerStatus.STOPPED;

            throw new CommException(CommException.INIT_ERROR,serverCtx.errorMsg);

        }

        // 新建线程启动

        Thread serverThread = new Thread(this);

        serverThread.start();

    }

 

 

 

以下是runnable异步线程启动时的具体逻辑代码,

主要完成netty的reactor线程池配置工作,设置监听线程池组和工作线程池组,并创建服务端启动类ServerBootStrap实例,同时设置服务端启动相关参数;

设置并绑定服务端channel,通过channel方法指定服务端的channel类型,利用反射创建NioServerSocketChannel对象;

然后使用ServerBootStrap对象绑定并监听端口,在绑定之前系统会做一系列的初始化和检测工作,完成之后会启动监听端口,并将ServerSocetChannel注册到Selector上监听客户端连接;

Rector监听线程池中的NioEventLoop线程负责调度和执行Selector轮询操作,选择那些准备就绪的Channel集合。

@Override

    public void run() {

    // 设置线程池

        EventLoopGroup bossGroup = new NioEventLoopGroup(httpServerConf.bossThreadNum,new ThreadFactory() {

            @Override

            public Thread newThread(Runnable r) {

                Thread thread = new Thread(r);

                thread.setName(httpServerConf.id+"Acceptor");

                return thread;

            }

        });

        EventLoopGroup workerGroup = new NioEventLoopGroup(httpServerConf.workerThreadNum,new ThreadFactory(){

            @Override

            public Thread newThread(Runnable r) {

                Thread thread = new Thread(r);

                thread.setName(httpServerConf.id+"Worker");

                return thread;

            }

        });

        // 主控服务

        ServerBootstrap serverBootStrap = new ServerBootstrap();

        serverBootStrap.option(ChannelOption.ALLOW_HALF_CLOSURE, true);

        //1:1排队       serverBootStrap.option(ChannelOption.SO_BACKLOG,httpServerConf.workerThreadNum);

        serverBootStrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(httpServerChannelInitializer);

        try {

            // 启动监听

            channel = serverBootStrap.bind(httpServerConf.port).sync().channel();

            status = ServerStatus.STARTED;

            logManager.COMM_LOGGER.info("Http服务端:" + httpServerConf.id + " - 启动成功");

        } catch (Exception e) {

            status = ServerStatus.STOPPED;

            logManager.COMM_LOGGER.error("Http服务端:" + httpServerConf.id + " - 启动失败", e);

            return;

        }

        try {

            // 监听服务停止

            channel.closeFuture().sync();

            status = ServerStatus.STOPPED;

            logManager.COMM_LOGGER.info("Http服务端:" + httpServerConf.id + " - 停止成功");

        } catch (Exception e) {

            logManager.COMM_LOGGER.error("Http服务端:" + httpServerConf.id + " - 停止失败", e);

        } finally {

            // 释放资源

            bossGroup.shutdownGracefully();

            workerGroup.shutdownGracefully();

        }

    }

 

      1. HTTP客户端

 

 

类名称

属性描述

CommClientFactory

 

HttpClientChannelInitializer

 

HttpClient

 

HttpClientCtx

 

HttpClientConf

 

HttpClientSender

 

 

 

 

 

 

      1. HTTPS协议实现
        1. SSLManagerConf类描述

SSL证书管理过程中包含以下属性:协议类型、证书类型、证书密码、证书存放路径、信任证书类型、信任证书密码、信任证书存放路径、证书库秘钥类型、信任证书库秘钥类型、是否开启服务端验证、是否开启客户端验证、客户端是否配置自定义证书、包含选项、排除选项。这里的信任证书就是跟证书。

服务端证书相关配置项内容:

协议类型

SSL

证书类型

pkcs12

证书秘钥库类型

sunx509

证书密码

111111

证书存放路径

D:/servercrt/server.pfx

 

 

根证书类型

jks

根证书秘钥类型

sunx509

根证书密码

111111

根证书存放路径

D:/certspxf/cacrt/ca-trust.jks

 

 

 

客户端证书相关配置项内容:

协议类型

TLSv1.2

证书类型

pkcs12

证书秘钥库类型

sunx509

证书密码

111111

证书存放路径

D:/clientrcrt/client.pfx

 

 

根证书类型

jks

根证书秘钥类型

sunx509

根证书密码

111111

根证书存放路径

D:/certspxf/cacrt/ca-trust.jks

        1. X509Certificate信任证书

X509TrustManager信任证书管理,

        1. 信任证书库管理

加载TrustManageFactory 信任证书库管理,需要ssl的配置信息。首先要创建信任证书的KeyStore对象,需要确认信任的根证书的keystore类型(有jks和pkcs12等),其次以文件流的形式读取证书文件,然后使用keystore对象load加载证书,同时输入keystore的密码,最后实例化TrustManageFactory对象,使用加载完证书的keystore对象完成初始化。

        1. 加载KeyManagerFactory证书库管理 

加载KeyManageFactory证书库管理,需要ssl的配置信息。首先要创建信任证书的KeyStore对象,需要确认服务端或客户端证书的keystore类型(pkcs12),其次以文件流的形式读取证书文件,然后使用keystore对象load加载证书,同时输入keystore的密码,最后实例化KeyManageFactory对象,使用加载的证书keystore对象完成初始化。

        1. 服务端证书验证

服务端证书验证时需要考虑是单项认证还是双向认证以及是否要验证客户端证书等。

对于双向认证需要创建KeyManagerFactory和TrustManageFactory类的实例对象,使用SSLContextUtils创建SSLContext对象,创建过程中需要使用根证书和服务端证书,然后使用SSLContext创建SSLEngine对象,设置该对象相关属性,例如设置使用服务端模式,设置需要验证客户端,设置需要客户端授权。使用SSLEngine对象生成SslHandler对象,SslHandler对象是netty框架中的处理对象,用于解码获取数据后执行的处理类。

对于单向认证仅仅需要创建KeyManagerFactory对象,使用SSLContextUtils创建SSLContext对象,创建SSLEngine对象,设置该对象相关属性,设置使用服务器端模式且不验证客户端。

        1. 客户端证书验证

客户端证书验证时需要考虑是单项认证还是双向认证以及是否要验证服务端证书等。

对于双向认证需要创建KeyManagerFactory和TrustManageFactory类的实例对象,使用SSLContextUtils创建SSLContext对象,创建过程中需要使用根证书和客户端证书,然后使用SSLContext创建SSLEngine对象,设置该对象相关属性,例如设置使用客户端模式,设置需要验证客户端,设置需要客户端授权。使用SSLEngine对象构造SslHandler对象,SslHandler对象是netty框架中的处理对象,用于解码获取数据后执行的处理类。

对于单向认证需要区分是否自定义证书,仅仅需要创建KeyManagerFactory对象,使用SSLContextUtils创建SSLContext对象,创建SSLEngine对象,设置该对象相关属性,设置使用服务器端模式且不验证客户端。

        1. TLS协议处理方法

TLSv1.0、TLSv1.1、TLSv1.2均是SSLv3.0的升级协议,但是HTTPS的握手协议方式不变,

双向认证首先需要需要创建KeyManagerFactory和TrustManageFactory类的实例对象,其次需要创建TLSServerParemeters对象,设置安全套阶层协议、秘钥管理、信任证书管理,设置包含和不包含的协议,然后创建ClientAuthentication客户端认证对象,将tlsserverfacry对象设置客户端认证。

然后使用SSLContext创建SSLEngine对象,设置参数为tlsserverfacry对象。使用SSLEngine对象生成SslHandler对象,SslHandler对象是netty框架中的处理对象,用于解码获取数据后执行的处理类。

单向认证不需要创建TrustManageFactory类,仅仅需要创建KeyManagerFactory类完成证书认证即可。

        1. Netty添加HTTPS处理

首先sSLManagerFactoryUtil.configureSSLOnDemandWS()方法创建sslhandler对象,使用pipeline.addLast("ssl", sslHandler)将sslhandler对象添加到pipeline中,然后再添加编解码处理和执行逻辑处理。

 

protected void initChannel(NioSocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        //添加日志信息

        if(httpServerConf.isLogger){

        pipeline.addLast(new LoggingHandler());  

        }

        //SSL加密设置配置

        if(httpServerConf.isEncryption){

        sslHandler = sSLManagerFactoryUtil.configureSSLOnDemandWS(httpServerConf.sslManagerConf, true, tlsServerParameters);

        if (sslHandler != null) {

                pipeline.addLast("ssl", sslHandler);

            }

        }

        pipeline.addLast(new HttpRequestDecoder());

        pipeline.addLast(new HttpObjectAggregator(httpServerConf.maxChunkContentSize));

        pipeline.addLast(new HttpResponseEncoder());

        pipeline.addLast("deflater", new HttpContentCompressor());

        // Set up the idle handler

        pipeline.addLast("idle", new IdleStateHandler(httpServerConf.readIdleTime,

                httpServerConf.writeIdleTime, 0));

        pipeline.addLast("handler", this.getServletHandler());

    }

 

 

      1. https证书制作
  1. 创建根证书证书
  1. 生成根证书的私钥
  2. 利用私钥生成一个根证书的申请,一般证书的申请格式都是csr。所以私钥和csr一般需要保存好
  3. 自签名的方式签发我们之前的申请的证书,生成的证书为ca.crt。
  4. 为我们的证书建立第一个序列号,一般都是用4个字符,这个不影响之后的证书颁发等操作
  5. 建立ca的证书库,不影响后面的操作,默认配置文件里也有存储的地方。
  6. 建立证书回收列表保存失效的证书

---已上就完成了根证书的相关操作,下一步可以颁发证书了。

  1. 创建服务端验证证书
  1. 建立服务器验证证书的私钥
  2. 生成证书申请文件
  3. 利用根证书签发服务器身份验证证书(CA签署server证书)
  4. 至此,服务器端身份认证证书已经完成,可以利用证书和私钥生成pfx格式的证书给微软使用,命令如下:
  1. 创建客户端验证证书

签发客户端身份认证证书

  1. 生成私钥
  2. 生成证书请求文件
  3. 签发证书
  4. 生成pfx格式

客户端证书完成,注意如果在web服务器上使用客户端证书,需要在web服务器上使用根证书对客户端进行验证,切记!

 

    1. WEBSERVICE协议及相关实现

Web Service是一个平台独立、松耦合、自包含、可编程的web应用服务,用于支持网络间不同应用系统的互动操作。Web Service的三个核心技术是:SOAP、WSDL和UDDI。Web Service简称为“WS”。

WSDL是用机器能阅读的方式提供的一个正式的基于XML语言的描述文档,用于描述Web Service的调用协议和通讯协议。WSDL是人与机器均可读取,通常用来辅助生成服务器和客户端代码。

SOAP可以运行在任何其他传输协议上,在传输层之间的头是不同的,但XML有效负载保持相同。Web Service通常会使用HTTP或HTTPS与SOAP绑定。

      1. WEBSERVICE服务端

Webservice服务端主要用于提供各项目组以Webservice通讯协议方式在ESB企业总线发布相关服务,供其他应用服务请求调用,该服务端具体设计类图如下。

                图 3-11 Webservice服务端类图

类名称

描述

WebServiceServer

该类是WebService的Server服务类,定义的属性包括:初始化标志、Http服务端通讯上下文、CXF 注册路径、通讯通道、服务端运行状态、WebService服务端配置SSL/TLS参数、WebService配置参数、pipeline初始化属性,重写了startServer()和stopServer()方法。

定义线程run()方法。

ServerConf

该类是服务端配置信息类,定义了服务端配置ID、监听端口、交易码解析器Bean名称、自定义过滤器Bean名称列表、服务器端错误返回格式转换ID、服务器错误返回BeanID、交易列表等属性。

WebServiceServerConf

该类继承ServerConf类,是WebService服务端配置信息类,属性包括:服务主机地址、连接后读取超时时间、写返回超时时间、最大报文长度、连接线程池大小、工作线程池大小、密钥管理属性、选取的方法列表、拦截器列表等。

WebServiceServerChannelInitializer

该类是Channel pipeline初始化类,定义了Http服务端通讯上下文、Http服务端配置、SSL加密设置配置属性及线程启动方法。

ServerCtx

该类是服务端运行时上下文,属性包括:自定义过滤器列表、交易码解析器、交易map、错误信息等属性。

HttpServerCtx

该类继承ServerCtx类,同时定义Http交易码解析器属性。

WebServiceHttpDestination

该类继承AbstractHTTPDestination类,

定义属性包括:WebService的Server、WebService Server工厂、ServletContext、ClassLoader类加载器、URL等。

方法:1定义doservice()方法;2检索webservice引擎;3通过端点信息获取URL方法。

WebServiceHttpContextHandler

该类主要用于保存WebService Handler的Map。

属性包括:ServletContext类、WebServiceHttpHandler对象的List集合等。

方法:1 addNettyHttpHandler()方法,添加WebServiceHttpHandler对象到list集合中;2在list集合中查询WebServiceHttpHandler对象;3删除list集合中查询WebServiceHttpHandler对象;4 handle()处理方法,遍历WebServiceHttpHandler对象,执行对象的handle方法;

WebServiceHttpHandler

该类属性包括: urlname、WebServiceHttpDestination类对象、ServletContext类对象等。

方法:定义了handle()方法,实际调用的是WebServiceHttpDestination类对象的doservice()方法。

WebServiceServletHandler

继承ChannelInboundHandlerAdapter类,定义Netty拦截器列表、WebServiceServerChannelInitializer类、WebServiceServerConf配置文件、ServTranS服务接口类等属性。

重写了channelRead()方法、channelReadComplete()方法和exceptionCaught()方法。

WebServiceServerFactory

该类是WebService Server的工厂类,实现CXF的BusLifeCycleListener类。属性包括:WebServiceServer的Map集合、CXF的bus总线、总线生命周期管理器、线程参数的Map集合、TLS server的Map集合。

方法包括:1在Map集合中检索WebServiceServer引擎;2.在已经存在的端口集合列表中删除该port端口对应的WebServiceServer引擎;3.WebServiceServer引擎管理,添加WebServiceServer服务到集合列表中;4.在CXF退出时,远程关闭WebService服务。

      1. WEBSERVICE客户端

Webservice客户端主要用于提供各项目组以Webservice通讯协议方法连接ESB企业总线,调用ESB所提供的相关服务,该客户端具体设计类图如下。

                     图3-12 Webservice客户端类图

类名称

属性描述

ServerConf

该类是服务端配置信息父类,属性包括:服务端配置ID、监听端口、交易码解析器Bean名称、自定义过滤器Bean名称列表、服务器端错误返回格式转换ID、服务器错误返回BeanID、交易列表。

webserviceClientConf

该类是Socket服务端配置信息类,属性包括:1请求URI;2 WebServiceClientTran交易列表;3进入服务的拦截器;4离开服务的拦截器。

ServerCtx

该类是服务端运行时上下文父类,属性包括:自定义过滤器列表、交易码解析器、交易map、错误信息。

webserviceClientCtx

该类是socket服务端运行时上下文配置类,属性包括:自定义过滤器列表、交易map(存储服务端交易配置)、请求结果Map及错误信息等属性。

ClientTran

该类是服务端交易类,属性包括:是否选择格式转换、数据流入格式转换ID、数据流出格式转换ID。

WebServiceClientTran

属性:1调用入口类名称;2调用方法名称;3服务名;4服务方法。

CommClientFactory

该类是客户端工厂类,该类从xml文件中加载所有clients配置,包括http、socket和webservice的客户端配置信息。

      1. SPEED4J支持我行ESB对接开发
  1. 什么是ESB系统

ESB系统作为浦发银行IT架构的核心枢纽主要提供银行内部实时的调用服务。ESB功能定位于“服务集成”,企业服务总线是银行的基础设施系统,它的作用是为服务集成、服务和产品创新等应用提供基础架构的支撑,通过企业服务总线平台,实现基于SOA架构的松耦合架构体系,全面解决系统之间的异构性问题,降低技术集成的复杂度,实现各个产品系统功能的服务化封装,奠定全行服务化的应用架构基础,全面提高全行IT架构灵活度和支持业务创新的能力。

  1. ESB系统信息交换模式

ESB支持的信息交换模式主要包括有如下几种:

简单请求响应模式:ESB系统支持简单请求响应模式,包括同步调用方式、异步调用方式和无响应调用方式。

文件传输模式:ESB系统支持联机文件传输模式。文件发送方将文件存到指定的位置,由文件接收方通过文件传输模块获取文件进行处理。文件传输模式也支持同步调用方式和异步调用方式。

订阅发布模式:订阅发布一般用于消息的广播和批量推送,推送过程采用异步推送的处理方式。

  1. ESB报文规范

浦发银行采用SOAP报文作为银行系统间信息交互的标准报文格式。新建服务调用方系统需要通过SOAP报文调用ESB系统上的服务;新建服务提供方系统也需要通过SOAP报文来对外发布服务供ESB系统调用。

考虑到存量系统的实际情况,ESB系统会兼容银行现有存量系统的报文格式SOP,存量系统可以通过SOP报文实现服务的调用和发布。

  1. 对接ESB的开发方式

ESB系统作为浦发银行IT架构的核心枢纽主要提供银行内部实时的调用服务。SPEED4J平台支持WEBSERVICE调用ESB等配置化开发,通过工具配置用户信息查询交易接口和用户信息查询客户端来实现对接ESB的调用。

首先以客户信息ECIF服务为例,讲解项目开发中如何与ESB对接。连接ESB,我们首先需要配置一个系统ID和系统编号,系统ID和系统CODE需要项目组自己根据实际情况向ESB处申请的具体值填写。如下图

图3-21 系统参数配置图

  1. 接口配置

用户信息查询交易接口配置:

用户信息查询交易通过与ESB进行WebService通讯完成,通讯框架通过使用Apache CXF组件支持使用特定WSDL地址来生成相关的WebService接口,系统的相关接口配置存放于项目通用组件工程中,具体配置过程如下:

展开项目下的接口管理节点,右击WebService资源管理节点,选择新增WebService服务,弹出向导,输入服务中文名:客户信息查询,WSDL地址:http://10.112.20.145:8080/Publish/WSDLfilePath/S120030018.wsdl,该地址由ESB提供,自动反显服务名称,由于该交易是外部请求,接口对象方式选择客户端,点击完成。稍等片刻后将在Console中Speed4J Console子视图中看到生成加载成功消息,至此ECIF用户信息查询交易接口就创建完成了,可以在WebService资源管理节点下看到交易的相关接口对象。

图3-22 接口配置图

 

  1. 客户端配置

用户信息查询客户端配置:

为了方便系统与外部系统进行交互,该通讯开发框架提供了渠道通讯管理工具,便于项目组统一配置和管理系统的各类外部通讯。目前系统向外部请求的外部通讯管理支持Http通讯、MQ通讯、Socket通讯以及WebService通讯,系统作为服务渠道对外提供服务的服务渠管理同样也支持Http服务、MQ服务、Socket服务以及WebService通讯。

相关通讯配置存放于项目通用组件工程中,具体的WebService外部通讯配置如下:

展开项目下的渠道通讯管理节点,展开外部通讯管理节点,右击WebService外部通讯节点,选择新建WebService外部通讯,弹出配置向导。

输入基本配置页面具体配置,包括全局唯一ID,调用主机地址,调用主机端口,请求URI,线程池初始大小,线程池最大大小,线程池队列大小,线程池保持连接时间,服务超时时间,是否开启监控等。

图3-23 外部通讯配置图

 

自定义过滤器配置页面用于配置报文进入系统后的过滤器,针对浦发ESB交易,通讯开发框架提供了对交易头进行处理的过滤器。如果项目不想用通讯框架提供的服务组件报文头,可以不设置此项,项目自己按照ESB提供的报文头规范进行组装即可。

交易列表配置页面用于配置交易,点击添加,输入交易码:RtlBscInfoQryClntNo,点击交易类旁的选择按钮,选择前面添加的S120030018接口,选择接口方法rtlBscInfoQryClntNo,交易描述:客户签约信息查询,点击确定,完成交易添加。

图3-24 交易列表配置图

点击Finish完成外部通讯配置过程,可在WebService外部通讯节点下查看配置项。

图3-25 客户端生成配置图

 

 

      1. SOAP报文

“Simple Object Access Protocol”的简称,简单对象访问协议。SOAP可以运行在任何其他传输协议上,在传输层之间的头是不同的,但XML有效负载保持相同。Web Service通常会使用HTTP或HTTPS与SOAP绑定。   

SOAP在安全方面是通过使用XML-Security和XML-Signature两个规范组成了WS-Security来实现安全控制的。

报文编码推荐采用UTF-8或GBK,编码需要在XML头的encoding中定义。

  1. 请求报文样例

<?xml version="1.0" encoding="UTF-8"?>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:s="http://esb.spdbbiz.com/services/S100100001"

xmlns:d="http://esb.spdbbiz.com/metadata">

         <soap:Header>

                  <s:ReqHeader>

                          <d:Mac>00000000000000000000</d:Mac>

                          <d:MacOrgId>888888</d:MacOrgId>

                          <d:SourceSysId>0001</d:SourceSysId>

                          <d:MsgId>0002201307070000000000000001</d:MsgId>

                          <d:ConsumerId>0002</d:ConsumerId>

                          <d:ServiceAdr>http://esb.spdbbiz.com:7701/services/S100100001

http://esb.spdbbiz.com/services/S100100001</d:ServiceAdr>

                          <d:ServiceAction>urn:/QueryCust01</d:ServiceAction>

                  </s:ReqHeader>

         </soap:Header>

         <soap:Body>

                  <s:ReqQueryCust01>

                          <s:ReqSvcHeader>

                                   <s:BranchId>0001</d:BranchId>

                                   <s:TranTellerNo>800800</d:TranTellerNo>

                                   <s:TranSeqNo>0002201307070000000000000001</d:TranSeqNo>

                                   <s:TranDate>20130707</d:TranDate>

                                   <s:TranTime>121200000</d:TranTime>

                                   <s:SourceSysId>0001</d:SourceSysId>

                                   <s:ConsumerId>0002</d:ConsumerId>

                                   <s:GlobalSeqNo>0001201307070000000000000001</d:GlobalSeqNo>

                          </s:ReqSvcHeader>

                          <s:SvcBody>

                                   <s:CustId>8888888888</d:CustId>

                                   <s:CustType>01</d:CustType>

                          </s:SvcBody>

                  </s:ReqQueryCust01>

         </soap:Body>

</soap:Envelope>

 

 

  1. WSDL定义样例

S100100001.wsdl的样例:

<?xml version="1.0" encoding="UTF-8"?>

<wsdl:definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://esb.spdbbiz.com/services/S100100001/wsdl" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:s="http://esb.spdbbiz.com/services/S100100001" targetNamespace="http://esb.spdbbiz.com/services/S100100001/wsdl">

         <wsdl:types>

                  <xsd:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://esb.spdbbiz.com/services/S100100001/wsdl">

                          <xsd:import namespace="http://esb.spdbbiz.com/services/S100100001" schemaLocation="S100100001.xsd"/>

                  </xsd:schema>

         </wsdl:types>

         <wsdl:message name="ReqHeader">

                  <wsdl:part name="ReqHeader" element="s:ReqHeader"/>

         </wsdl:message>

         <wsdl:message name="ReqQueryCust01">

                  <wsdl:part name="ReqQueryCust01" element="s:ReqQueryCust01"/>

         </wsdl:message>

         <wsdl:message name="ReqQueryCust02">

                  <wsdl:part name="ReqQueryCust02" element="s:ReqQueryCust02"/>

         </wsdl:message>

         <wsdl:message name="RspHeader">

                  <wsdl:part name="RspHeader" element="s:RspHeader"/>

         </wsdl:message>

         <wsdl:message name="RspQueryCust01">

                  <wsdl:part name="RspQueryCust01" element="s:RspQueryCust01"/>

         </wsdl:message>

         <wsdl:message name="RspQueryCust02">

                  <wsdl:part name="RspQueryCust02" element="s:RspQueryCust02"/>

         </wsdl:message>

         <wsdl:portType name="ESBServerPortType">

                  <wsdl:operation name="QueryCust01">

                          <wsdl:input message="tns:ReqQueryCust01"/>

                          <wsdl:output message="tns:RspQueryCust01"/>

                  </wsdl:operation>

                  <wsdl:operation name="QueryCust02">

                          <wsdl:input message="tns:ReqQueryCust02"/>

                          <wsdl:output message="tns:RspQueryCust02"/>

                  </wsdl:operation>

         </wsdl:portType>

         <wsdl:binding name="ESBServerSoapBinding" type="tns:ESBServerPortType">

                  <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>

                  <wsdl:operation name="QueryCust01">

                          <soap:operation soapAction="urn:/QueryCust01/v10"/>

                          <wsdl:input>

                                   <soap:header message="tns:ReqHeader" part="ReqHeader" use="literal"/>

                                   <soap:body use="literal"/>

                          </wsdl:input>

                          <wsdl:output>

                                   <soap:header message="tns:RspHeader" part="RspHeader" use="literal"/>

                                   <soap:body use="literal"/>

                          </wsdl:output>

                  </wsdl:operation>

                  <wsdl:operation name="QueryCust02">

                          <soap:operation soapAction="urn:/QueryCust02/v10"/>

                          <wsdl:input>

                                   <soap:header message="tns:ReqHeader" part="ReqHeader" use="literal"/>

                                   <soap:body use="literal"/>

                          </wsdl:input>

                          <wsdl:output>

                                   <soap:header message="tns:RspHeader" part="RspHeader" use="literal"/>

                                   <soap:body use="literal"/>

                          </wsdl:output>

                  </wsdl:operation>

         </wsdl:binding>

         <wsdl:service name="S100100001">

                  <wsdl:port name="ESBServerSoapEndpoint" binding="tns:ESBServerSoapBinding">

                          <soap:address location="http://esb.spdbbiz.com:7701/services/S100100001"/>

                  </wsdl:port>

         </wsdl:service>

</wsdl:definitions>

<wsdl:service name=””>定义了具体接口服务名称。

<soap:address location=””>定义了发布webservice具体服务地址。

<wsdl:operation name="QueryCust02">对应着具体接口内的方法名称QueryCust02。

<wsdl:input message="tns:ReqQueryCust02"/>指对应方法的输入,在方法名前添加了Req特定字段。

<wsdl:output message="tns:RspQueryCust02"/>指对应方法的输出,在方法名前添加了Rsp特定字段。

    1. 异常情况处理
      1. 异常信息设计

在异常情况处理逻辑设计上,我们定义了CommException(通讯异常)类,该类的属性包括:错误编码、原始报文和流水号,其中错误编码类型包括:未分类的系统错误、超时异常、IO异常、服务端/客户端初始化失败、解码异常、流控拒绝等。

在Speed4j平台中,通讯异常控制被定义在业务逻辑的handler处理类中,以SOCKET服务端通讯为例,业务逻辑处理类SocketServerHandler 继承netty框架中ChannelInboundHandlerAdapter类,重写了exceptionCaught()方法,在该方法中,我们定义了不同异常情况下的处理方案,具体异常处理代码如下所述:

  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

        if (cause instanceof ReadTimeoutException) {

            clientCtx.isSuccess = false;

            clientCtx.errorMsg = "Socket客户端:" + socketClientConf.id + " - 接收数据超时,自动断开";

       logManager.COMM_LOGGER.error(clientCtx.errorMsg, new Exception(cause));

        } else {

            clientCtx.isSuccess = false;

            clientCtx.errorMsg = "Socket客户端:" + socketClientConf.id + " - 通讯异常";

            logManager.COMM_LOGGER.error(clientCtx.errorMsg, new Exception(cause));

        }

        ctx.channel().close();

    }

 

      1. 异常情况处理

异常情况处理方案包括:冲正、补发、人工处理、对账等。

作为客户端请求外部通讯时,调用doRequest方法,如果期间发生任何通讯错误将向调用者抛出CommException异常;

作为服务端对外提供通讯服务时,所有异常在运行框架内处理,通过LogManager记录框架错误日志。

 

 

 

 

 

 

 

 

 

 

 

 

第四章 性能结果分析

    1. 推广情况

SPEED4J平台目前提供Http、Socket、MQ、WebService这四种通信方式,其中Socket和WebService这两种方式使用频率较高。

具体推广情况如下所述:

  1. 财务估值系统、使用了平台WebService通讯的客户端进行快速配置化开发,实现了财务估值系统与ESB平台对接的渠道通讯交易调用;
  2. 企业在线融资平台以及电商清结算平台使用了WebService通讯的服务端以及客户端进行工具的配置化开发,作为服务端将相关交易发布到ESB平台并提供给行内其他系统调用。
  3. 重庆分行使用WebService通讯的HTTPS加密方式实现与ESB平台交易通讯,工具满足了分行以加密方式对接ESB实现交易调用过程,实用性相对较广;
  4. 昆明分行使用了socket通讯,即sop报文方式对接ESB交易服务调用,快捷高效的完成了系统功能开发等。
    1. 性能测试样例
      1. 测试背景

我行电子支付链路端到端的交易处理能力已无法满足“双十一”等具有互联网业务特点的高并发交易场景的井喷式增长。对标同业2016年“双十一”前支付宝的压力测试结果,招商银行的交易能力约为5,000TPS,建设银行的交易能力约为10,000TPS,而我行的处理能力仅为600TPS。面对我行电子商务支付链路处理能力已经严重不能满足用户需要的情况,行领导指示:对标同业先进水平,提升我行电子支付交易的处理能力,提升互联网业务大并发、高可用的应对能力。为此信息科技部启动并全力推进电子商务支付链路性能优化项目建设。

企业级客户信息管理系统(ECIF)是基于Speed4j平台进行开发的,该系统通信框架正是本篇文章所介绍的基于netty架构所开发的通讯系统。系统开发完成后,行内测试中心对ECIF系统实施了一次标准化性能测试,目的是为了验证该企业级客户信息管理系统是否可以支持预期TPS指标。

      1. 性能需求指标
  • 系统资源利用率:ECIF ES44交易应用和redis各服务器的CPU、内存平均使用率均不高于80%。
  • 交易成功率:各交易成功率不低于99.99%。
  • 交易响应时间:

交易名称

交易代码

90%响应时间(秒)

根据介质获取客户信息

ES44

0.1

  • 系统处理能力(TPS):不低于4000笔/秒。
  • 健壮性要求:模拟一个应用容器进程被异常终止时,系统性能仍需要满足指标需求。
      1. 生产环境拓扑图

电子商务支付链路性能优化项目ECIF系统性能测试拓扑图,如图5-1所示。环境配置:开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库。目的:验证预期6个ECIF容器是否满足支付链路性能优化项目需求。

                     图4-1所示电子商务支付链路性能测试拓扑图

      1. 负载测试

在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,采用LR发压,35并发用户数,满足4000笔/秒的指标要求下,进行ES44负载测试,结果如下

用户数

交易名称

平均响应时间()

系统处理能力(/)

TPS(/)

通过事务数

失败事务数

交易成功率

35

根据介质获取客户信息_redis

0.007

4530.607

4671.089

8159619

0

100.00%

根据介质获取客户信息_DB

0.006

140.482

253005

0

100.00%

资源使用率;

用户数

35

监控项

服务器

资源使用情况

CPU(单位:%)

应用服务器10.112.34.11

6.379

应用服务器10.112.34.12

7.091

zuul容器10.112.34.11

42.635

zuul容器10.112.34.12

42.455

Docker服务器10.112.34.11(其中一个)

14.536

Docker服务器10.112.34.12(其中一个)

14.276

内存(单位:%)

应用服务器10.112.34.11

3.828

应用服务器10.112.34.12

3.905

应用服务器10.112.34.11_swap内存

0.031

应用服务器10.112.34.12_swap内存

0.032

zuul容器10.112.34.11

35.84

zuul容器10.112.34.12

36.48

Docker服务器10.112.34.11(其中一个)

31.507

Docker服务器10.112.34.12(其中一个)

30.797

应用服务器10.112.34.12

41.612

由上可知:

  1. 各交易在35并发用户下平均响应时间均满足指标需求。
  2. 交易响应时间为0.007秒,满足不超过0.1秒的指标要求。
  3. TPS为4671.089笔/秒,满足高峰期4000笔/秒的指标要求。
  4. 宿主服务器CPU使用率不超过10%,应用Docker的CPU使用率为14%左右,不超过80%的指标要求;Zuul的CPU使用率为42.635%,不超过70%的指标要求;
  5. 宿主机内存使用率为4%左右,应用Docker的内存使用率为31%左右,不超过80%的指标要求;Zuul的内存使用率为42.635%,不超过80%的指标要求。
      1. 容量测试

在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,联机交易容量测试分别采用50、55、60、65、70并发用户执行容量测试,具体结果如下:

用户数

交易名称

平均响应时间()

系统处理能力(/)

TPS(/)

通过事务数

失败事务数

交易成功率

50

根据介质获取客户信息_redis

0.007

5864.61

6047.417

10561947

0

100.00%

根据介质获取客户信息_DB

0.007

182.807

329238

0

100.00%

55

根据介质获取客户信息_redis

0.008

6375.011

6568.472

11475020

0

100.00%

根据介质获取客户信息_DB

0.007

197.367

355260

0

100.00%

60

根据介质获取客户信息_redis

0.008

6748.6

6950.348

12147290

0

100.00%

根据介质获取客户信息_DB

0.007

205.547

369975

0

100.00%

65

根据介质获取客户信息_redis

0.008

7215.42

7435.708

12987757

0

100.00%

根据介质获取客户信息_DB

0.007

224.51

404118

0

100.00%

70

根据介质获取客户信息_redis

0.008

7486.779

7981.872

13476203

0

100.00%

根据介质获取客户信息_DB

0.008

246.953

417691

0

100.00%

TPS趋势图:

响应时间趋势图:

由上可知:

  1. 各交易响应时间随用户的增加逐渐增高,TPS随用户的增加逐渐增加。
  2. 各交易成功率为100%,满足指标99.99%要求。

资源使用率:

用户数

50

55

60

65

70

监控项

服务器

资源使用情况

CPU(单位:%)

应用服务器10.112.34.11

8.173

8.876

9.331

9.95

10.357

应用服务器10.112.34.12

8.814

9.487

9.967

10.58

11.018

zuul容器10.112.34.11

55.443

59.899

63.167

69.053

71.403

zuul容器10.112.34.12

54.097

59.456

63.695

68.171

71.011

Docker服务器10.112.34.11(其中一个)

19.608

21.33

22.294

23.469

24.571

Docker服务器10.112.34.12(其中一个)

18.935

20.564

21.652

23.359

24.591

内存(单位:%)

应用服务器10.112.34.11

4.304

4.54

4.359

5.055

4.259

应用服务器10.112.34.12

4.379

4.719

4.421

5.239

4.328

应用服务器10.112.34.11_swap内存

0.029

0.042

0.046

0.043

0.039

应用服务器10.112.34.12_swap内存

0.034

0.036

0.035

0.036

0.033

zuul容器10.112.34.11

35.84

35.84

35.84

35.84

35.84

zuul容器10.112.34.12

36.48

36.48

36.48

36.48

40.984

Docker服务器10.112.34.11(其中一个)

39.68

42.88

41.299

42.88

38.91

Docker服务器10.112.34.12(其中一个)

42.24

43.938

43.516

43.28

36.48

资源使用率—CPU使用率图:

资源使用率—内存使用率图:

由上可知:

  1. 宿主服务器、应用容器、zuul容器CPU资源使用率随用户增加而增加,70用户数时,Zuul的CPU超过70%指标要求,为71%,因此65并发用户数时,系统处理能力达到最大,为7435.708笔/秒,各项指标要求均满足预期。
      1. 健壮性测试

在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,执行健壮性测试。

1.主redis实例数据库异常的健壮性测试

步骤一:执行测试15分钟。

步骤二:手动停止主redis数据库实例,保持执行,持续15分钟。

步骤三:重启主redis数据库实例,保持执行,持续15分钟。

系统处理能力:

用户数

交易名称

平均响应时间(秒)

系统处理能力(笔/秒)

总TPS(笔/秒)

通过事务数

失败事务数

交易成功率

35

根据介质获取客户信息_redis

0.007

4265.555

4636.8

11521354

1175

99.99%

根据介质获取客户信息_DB

0.006

130.962

353732

0

100.00%

资源使用率:

用户数

35

监控项

服务器

资源使用情况

CPU(单位:%)

应用服务器10.112.34.11

6.05

应用服务器10.112.34.12

6.682

zuul容器10.112.34.11

40.431

zuul容器10.112.34.12

39.568

Docker服务器10.112.34.11(其中一个)

13.887

Docker服务器10.112.34.12(其中一个)

13.634

内存(单位:%)

应用服务器10.112.34.11

4.01

应用服务器10.112.34.12

4.368

应用服务器10.112.34.11_swap内存

0.042

应用服务器10.112.34.12_swap内存

0.036

zuul容器10.112.34.11

35.84

zuul容器10.112.34.12

36.48

Docker服务器10.112.34.11(其中一个)

31.16

Docker服务器10.112.34.12(其中一个)

38.229

TPS趋势图:

响应时间图:

资源使用率—CPU使用率图:

资源使用率—内存使用率图:

  1. 交易响应时间为0.007秒,满足不超过0.1秒的指标要求。
  2. TPS为4636.8笔/秒,满足高峰期4000笔/秒的指标要求,在redis停止、重启过程中,TPS无明显变化。
  3. redis数据库主实例被手工停止时,瞬间出现1175笔报错,之后未出现。项目组解释:已经发到主实例的请求无法转到备实例,实例停掉后这部分请求将会报错,符合预期。

2.Docker容器实例异常健壮性测试测试

步骤一:执行测试15分钟。

步骤二:宿主机kill一个docker容器进程,保持执行,持续15分钟

系统处理能力:

时间段

交易名称

平均响应时间(秒)

系统处理能力(笔/秒)

TPS(笔/秒)

成功事务数

失败事务数

成功率

15分钟

根据介质获取客户信息_01

0.007

4619.041

4761.95

4715808

0

100%

根据介质获取客户信息_02

0.006

142.909

145903

0

100%

15~31分钟

根据介质获取客户信息_01

0.007

4613.229

4756.12

4156562

8

99.99%

根据介质获取客户信息_02

0.006

142.891

128746

0

100%

资源使用率:

时间段

17分钟

15~33分钟

监控项

服务器

资源使用情况

CPU(单位:%)

应用服务器10.112.34.11

6.577

6.575

应用服务器10.112.34.12

7.409

7.472

zuul容器10.112.34.11

44.049

44.364

zuul容器10.112.34.12

43.579

43.868

Docker服务器10.112.34.11(其中一个)

14.804

15.05

Docker服务器10.112.34.12(停止的)

15.21

-

Docker服务器10.112.34.12(启动的)

-

15.231

内存(单位:%)

应用服务器10.112.34.11

4.038

4.333

应用服务器10.112.34.12

4.105

4.196

应用服务器10.112.34.11_swap内存

0

0

应用服务器10.112.34.12_swap内存

0

0

zuul容器10.112.34.11

35.454

35.739

zuul容器10.112.34.12

35.84

35.84

Docker服务器10.112.34.11(其中一个)

31.426

33.197

Docker服务器10.112.34.12(停止的)

29.44

-

Docker服务器10.112.34.12(启动的)

-

29.267

Docker服务器10.112.34.12(停止的)

15597960

-

Docker服务器10.112.34.12(启动的)

-

15656732

应用服务器10.112.34.12

17159.662

17168.35

总TPS图:

响应时间图:

资源使用率—CPU使用率图:

资源使用率—内存使用率图:

由上可知:

  1. 交易响应时间为0.007秒,满足不超过0.1秒的指标要求。
  2. 手动停止一个容器以后,会自动启动一个新的应用容器,在容器管理平台查看自动启动的容器信息,发现自动的容器服务名称与停止的容器服务名称一样,且IP地址信息一样;
  3. 在手动停止容器的瞬间,报错33笔,交易成功率为99.99%,满足不低于99.99%的指标要求,后续未再出现报错。。
  4. 各项资源使用率满足指标。
      1. 稳定性测试

在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,执行健壮性测试。

时间段

交易名称

平均响应时间(秒)

系统处理能力(笔/秒)

TPS(笔/秒)

成功事务数

失败事务数

成功率

11小时

根据介质获取客户信息_01

0.007

2642.128

2723.803

104630863

0

100%

根据介质获取客户信息_02

0.006

81.675

3234406

0

100%

中间2小时

根据介质获取客户信息_01

0.007

4931.978

5084.041

18130641

0

100%

根据介质获取客户信息_02

0.006

152.063

559981

0

100%

11小时

根据介质获取客户信息_01

0.006

2750.353

2835.25

108826553

0

100%

根据介质获取客户信息_02

0.006

84.897

3358588

0

100%

TPS趋势图:

响应时间图:

由上可知:

  1. 三阶段响应时间均满足不超过0.1s的性能需求指标。
  2. 成功率满足性能需求指标。
  3. 稳定性测试响应时间各时间段相差不大,未出现较大波动。
  4. 前11小时日常系统处理能力为2723.803笔/秒,满足日常2000笔/秒的指标需求;中间2小时高峰期系统处理能力为5084.041笔/秒,满足高峰4000笔/秒的指标需求;后11小时日常系统处理能力为2835.25笔/秒,满足日常2000笔/秒的指标需求
  5. 稳定性测试系统处理能力各时间段相差不大,未出现较大波动

资源使用率:

时间段

11小时

中间2小时

11小时

监控项

服务器

资源使用情况

CPU(单位:%)

应用服务器10.112.34.11

3.912

6.895

4.038

应用服务器10.112.34.12

4.671

7.606

4.822

zuul容器10.112.34.11

24.921

46.409

25.807

zuul容器10.112.34.12

24.728

46.484

25.897

Docker服务器10.112.34.11(其中一个)

8.146

15.662

8.504

Docker服务器10.112.34.12(其中一个)

7.989

15.37

8.325

内存(单位:%)

应用服务器10.112.34.11

3.351

4.119

4.232

应用服务器10.112.34.12

3.338

4.075

4.106

应用服务器10.112.34.11_swap内存

0

0

0.071

应用服务器10.112.34.12_swap内存

0

0

0.025

zuul容器10.112.34.11

36.48

36.48

36.48

zuul容器10.112.34.12

36.475

36.48

36.48

Docker服务器10.112.34.11(其中一个)

20.526

35.388

34.283

Docker服务器10.112.34.12(其中一个)

20.833

34.058

35.436

应用服务器10.112.34.12

12245.484

22730.958

12746.274

资源使用率—CPU使用率图:

资源使用率—内存使用率图:

由上可知:

  1. 宿主机CPU资源使用率在日常时为4%左右;高峰期CPU资源使用率在7%左右,满足性能需求指标。
  2. zuul容器CPU资源使用率在日常时为25%左右;高峰期CPU资源使用率在48%左右,满足性能需求指标。
  3. Docker容器(其中一个)CPU资源使用率在日常时为8%左右;高峰期CPU资源使用率在15%左右,满足性能需求指标。
  4. 在高峰期应用占用容器内存增加,由20%左右上升到40%左右,降低用户后应用占用内存有所下降,降为35%左右。
      1. 测试结论

通过本次性能测试和上述测试数据分析,ECIF系统在既有的软硬件配置下,经过负载测试、容量测试、健壮性测试和稳定性测试,各项测试结果如下:

  1. 负载测试
  1. 在测试环境容器+UAT环境redis+性能测试环境DB2数据库下,16并发用户下,系统处理能力可达到1174.4笔/秒,其他指标满足需求。
  2. 在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,35并发用户下,系统处理能力为4671.089笔/秒,满足高峰期4000笔/秒的指标要求,其他指标满足需求。
  3. 在开发环境6台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库并且与BIP和信用卡前置代授权同时压测下,120用户下,系统处理能力可达到4811.307笔/秒。测试中,ECIF受到信用卡前置系统影响程度较大,初步怀疑为F5层面引起,经和F5项目组沟通,该现象待三个系统单体测试全部结束后继续跟进。
  1. 容量测试

    在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,采用50、55、60、65、70并发用户执行容量测试,50、55、60、65并发用户下TPS随着用户数的增加保持上升趋势,70用户数时,Zuul的CPU超过70%指标要求,为71%,因此65并发用户数时,系统处理能力达到最大,为7435.708笔/秒,各项指标要求均满足预期。

  1. 健壮性测试

在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,redis数据库实例健壮性,满足预期指标;Docker应用容器健壮性在正常执行15分钟后后台kill -9 杀掉一应用容器进程两种情况下,瞬时报错8笔交易,后恢复正常,满足预期指标。

  1. 稳定性测试

在开发环境2台28C512G容器宿主机+UAT环境redis+性能测试环境DB2数据库下,采用“11+2+11”方式(20并发用户—40并发用户—20并发用户,即前后11小时采用双十一指标压力的50%,中间2小时采用双十一指标压力)执行稳定性测试,运行24小时,各交易平均响应时间,系统处理能力以及各服务器CPU使用率均呈规律性波动,响应时间均满足不超过0.1s的性能需求指标,前11小时日常系统处理能力为2723.803笔/秒,满足日常2000笔/秒的指标需求;中间2小时高峰期系统处理能力为5084.041笔/秒,满足高峰4000笔/秒的指标需求;后11小时日常系统处理能力为2835.25笔/秒,满足日常2000笔/秒的指标需求。

 

 

第五章 总结和展望

    1. 总结

 

    1. 展望

最后,本文通过Netty这个NIO框架,实现了一个很简单的“高性能”的RPC服务器,但是还是有一些值得改进的地方,比如:

  1、对象序列化传输可以支持目前主流的序列化框架:protobuf、JBoss Marshalling、Avro等等。

  2、Netty的线程模型可以根据业务需求,进行定制。因为,并不是每笔业务都需要这么强大的并发处理性能。

  3、目前RPC计算只支持一个RPC服务接口映射绑定一个对应的实现,后续要支持一对多的情况。

  4、业务线程池的启动参数、线程池并发阻塞容器模型等等,可以配置化管理。

  5、Netty的Handler处理部分,对于复杂的业务逻辑,现在是统一分派到特定的线程池进行后台异步处理。当然你还可以考虑JMS(消息队列)方式进行解耦,统一分派给消息队列的订阅者,统一处理。目前实现JMS的开源框架也有很多,ActiveMQ、RocketMQ等等,都可以考虑。

猜你喜欢

转载自blog.csdn.net/u010882691/article/details/82257338