1. HSF的基本概念
HSF全称为High-Speed Service Framework,旨在为淘系的应用提供一个分布式的服务框架,HSF从分布式应用层面以及统一的发布/调用方式层面为大家提供支持,从而可以很容易的开发分布式的应用以及提供或使用公用功能模块,而不用考虑分布式领域中的各种细节技术,例如远程通讯、性能损耗、调用的透明化、同步/异步调用方式的实现等等问题。
2. 知识准备
2.1 对象的序列化
对象的序列化过程在RPC过程中是很重要的一个环节,序列化对于远程调用的响应速度、吞吐量、网络带宽消耗等同样也起着至关重要的作用。
在HSF1.0时只支持两种序列化方式:java 和 hessian,在HSF2.0之后就支持了五种序列化方式:java、hessian、hessian2、json、kyro。
但是目前版本中常用的序列化方式还是java 和 hessian两种。 如果想细致的了解也可以多做了解。
2.2 动态代理
对于消费方来说,所存在的只有一个接口,虽然底层的实现原理我们知道,但是为了在使用时的高度透明,在JAVA语言层面上的表现形式则是通过动态代理的方式实现的,很多的逻辑都在InvocationHandler 中处理的。关于如何实现动态代理,还动态代理的一些使用的细节也可以稍作了解。
2.3 网络通信NIO
如果在网络传输过程中,采取普通的BIO,会有很多的问题存在,例如如果调用端有多个请求过来,那么就得需要多个线程去处理,每个线程都使用独立的连接,在远端的提供者端有对应的多个线程来执行相应的服务。这种方式会使得调用者和提供者之间建立大量的连接,而且是阻塞的方式,连接并不能得到充分的利用(摘自《大型网站系统与JAVA中间件》)。采用NIO则就可以避免这样的损耗,但是HSF在使用时并不是采用直接的NIO编程,而是通过第三方的框架Netty。
3. HSF 实现原理
3.1 提供服务的大致流程如下:
- server启动时候向configserver注册
- client启动时候向configserver请求list
- client缓存list,发现不可用的server,从缓存中remove
- configserver通过心跳包维护可用server的list
- list有更新的时候,configserver通过带version的报文通知client更新
从以上几个问题出发,看下HSF的实现方式。
3.2 HSF的整体实现方式
从图中可以看出,HSF的实现方式可以理解为是C/S的架构,但是和传统的C/S架构相比还是有很大的不同,HSF没有真正的服务器,每个应用都可以成为服务的调用方和提供方。具体工作方式如下:
ConfigServer:远程调用对端的地址就是由ConfigServer 来推送的,这样用户只需要配置自己的服务端或者消费端,不需要对自己的地址进行管理。
Diamond:持久化的配置中心,用于配置服务调用的规则。
服务:服务是调用方和提供方交流的依凭,一般是一个接口,表示一个业务行为以及相关的数据含义。通过使用HSFApiProviderBean能够暴露一个服务,将机器的地址注册到configserver,并且能够通过12200端口进行服务提供,通过HSFApiConsumerBean能够包装出一个客户端,它是服务接口的一个代理,并且它从configserver上订阅了服务的地址列表,能够在这个列表上完成随机调用,做到负载均衡与HA((High Available,高可用性群集)。
网络通信:HSF的底层网络通信是使用netty框架实现的,是基于epoll的NIO的网络通讯框架,HSF在此使用的是长连接,通过合理的服务部署及负债均衡,基本不存在I/O方面的限制。
4. HSF设计架构
关于HSF的架构基本可以理解为C/S结构设计方式。(虽然HSF没有自己的服务器)
Server端除了configServer外还有一个diamond用来保存一些持久化的配置信息,这里不进行过多的介绍。
Client是HSF的重点,下面是各模块的功能介绍:
Proxy:这一层主要负责接口的代理。基本上所有的RPC框架都会用到代理模式,相信大家不陌生。需要注意的是HSF的代理层还进行了软负载和单元化的处理。
Remoting:这一层是HSF的应用层协议,定义了报文格式,各个字段的含义等信息,内容比较多,之后单独写一篇文章来介绍。
Processer:这一层主要是处理HSF自身的业务逻辑,包括埋点、限流、鉴权等。
Netty:上面三层会将一次服务调用或者服务返回包装成一个报文,然后通过这层传输。
HSF调用流程
上图是HSF整个的调用过程,从左向右看:
第一条线路相当于consumer进行服务调用的过程,首先经过proxy层,将请求经过代理类包装出去;然后是Remoting层进行协议的包装,(处理HSF自身的业务逻辑,包括埋点、限流、鉴权等)最后io层发送出去。
第二条线路相当于provider将结果返回后解析的过程,与上一流程刚好相反。
右边的provider两条调用流程相信大家都能按照上面的过程理解,就不一一讲解了。
5. HSF处理请求流程
5.1 HSF提供端初始化
5.2 HSF消费端初始化
5.3 消费方请求到提供方,响应一次调用
5.4 实现细节
心跳检测:
1、客户端主动关闭连接:客户端第一次与服务端建立链接后,就会周期性(27s)发送心跳包的callback调用,如果连续三次收不到服务端的心跳包回应,客户端主动关闭链接
2、服务端主动关闭连接:当连接59s没有调用(对方网络不可用,或者full gc太久),相当于两次(2*27s)收不到心跳包的时间
从上图可以看出RPC要解决以下几个问题:
如何解决网络通信问题,主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
如何解决寻址问题,客户端如何找到制定的服务端,也就是保证准确有效的完成一次服务调用的前提。
参数的传递及服务端在收到客户端请求后如何实现其具体功能并返回,由于网络传输协议是二进制的,内存中的参数值必须要解决序列化,反序列化,以及对半包,粘包的处理。
6. HSF的优点:
6.1 服务的自动注册、发现
通过注册中心,实现服务的注册/注销与服务的发现。当服务启动后,会调用publish来将服务发布到中心,而服务的消费者,通过调用订阅接口传入的监听器来更新服务提供者列表。HSF提供了三种注册中心实现,分别是ConfigServer,Zookeper,和配置文件模式。
这里仅对Zookeper进行分析,ZookeeperMetadataAddressService 实现了上述接口,在初始化中实例化了一个ZookeeperRegistry来进行管理,其使用了一个封装了zookeperclient的实例-ZkclientZookeeperClient,在注册服务的时候根据url参数中的Constants.DYNAMIC_KEY来确定创建Persistent节点还是Ephemeral节点,Ephemeral节点生命周期与本机连接绑定,这样就可以实现本机离线后的服务自动注销的功能。
6.2 服务提供者与消费者之间长连接
HSF采用长连接方式进行通信,相比短连接,长连接更具性能优势,避免连接重复创建与销毁带来的缓冲区申请与释放。HSF抽象了连接AbstractClient(Client),并采用了netty框架作为底层实现。netty是一个性能非常优秀的通信框架,基于Reactor模式,内部采用了管线模式来解耦不同层次的逻辑之间的耦合问题。HSF为了强化TCP连接的可用性,增加HeartBeat功能,使用了一个Netty提供的 HashedWheelTimer 的定时任务调度器来执行心跳包的发送(补充:此HashedWheelTimer原理采用轮片式的桶结构,避免每次操作对全部任务的迭代操作,只对将要到期的桶进行操作,此原理也可用于缓存系统设计,在需要进行垃圾回收的情况下只需要按照桶为单位进行内存回收)。
6.3 非侵入性
HSF最大优点是非侵入性,它使用了JAVA的Proxy机制来实现这一特点,在通过xml配置文件配置Consumer的时候,实际上是调用了 HSFApiConsumerBean ,在它的初始化方法中,读取了配置的实现接口,并在ProcessComponent中用一个封装了Proxy注册功能,并实现了InvocationHandler接口的类HSFServiceProxy去管理。使用者在自己的代码中无需做任何特殊处理,就像使用本地方法一样去调用其方法。
6.4 版本管理
这一特性可以很灵活的帮助上线运营的服务在升级过程中避免服务不可用的情况。
6.5 服务治理
可以通过网页可视化查看、管理、测试服务的可用。
6.6 扩展灵活
可以接入自动服务降级功能(熔断) - 根据配置或服务的执行结果,在调用级控制服务是否调用执行,避免服务整体瘫痪,提升服务的可用性。
参考:
https://blog.csdn.net/qq_16681169/article/details/72512819