1. 简述:什么是HSF
HSF (High-speed Service Framework),高速服务框架,是集团内部广泛使用的分布式 RPC 服务框架,和外部使用的Dubbo类似。
HSF 作为集团的基础中间件,联通不同的业务系统,解耦系统间的实现依赖。
HSF 从分布式应用的层面,统一了服务的发布/调用方式,从而帮助用户可以方便、快速的开发分布式应用,以及提供或使用公共功能模块。
(HSF是集团内部的分布式RPC服务框架,类似在外面用的Dubbo + Zookeeper)
2. HSF结构设计
HSF搭配Config Server、Diamon和Redis已经实现了微服务架构的各种基本功能。
HSF 本身是没有服务端集群,是一个纯客户端架构的 RPC 框架,所有的 HSF 服务调用都是服务消费方(Consumer)与服务提供方(Provider)点对点建立连接的。
为了实现整套分布式服务体系,HSF 还需要依赖集团的各种其他中间件:
主要的中间件有以下几个:
- Diamond:持久化配置中心:HSF 客户端在启动的过程中会向持久化配置中心订阅各种服务治理规则,如路由规则、归组规则、权重规则等,从而根据规则对调用过程的选址逻辑进行干预。
- ConfigServer:地址注册中心:提供服务发现和服务检索的能力,注册中心告知有哪些机器提供了什么什么服务,如果没有注册中心,HSF 只能完成简单的点对点调用。
- Redis(可选):元数据指 HSF 服务对应的方法列表以及参数结构等信息,对 HSF 的调用过程不会产生影响。
实战理解:
- 服务提供方绑定了 12200 端口,用于接受请求并提供服务,同时将地址信息发布到地址注册中心。
- 服务消费者通过地址注册中心订阅服务,根据订阅到的ip地址发起调用,地址注册中心不参与调用。
集成在Pandora Boot容器中的HSF服务在本地启动后
在终端输入 telnet localhost 12201 后连接到Pandora console
在console中cd hsf后ls即可看到本地运行的HSF服务提供者和消费者,如下所示:
1 | hsf>ls |
3. HSF 调用方式 fast-start
服务发布者
最简单常用的方式就是在需要对外服务的Service上提供一个一个一个@HSFProvider注解
1 | @HSFProvider(serviceInterface = HelloService.class,serviceVersion = "${hsf.provider.version}") |
在Pandora Boot容器配置文件中写入相关的HSF所属组和版本号(比通过定义HSFApiProviderBean通过api的形式配置HSF服务更方便),然后在业务相关的网关中配置访问路由即可 。
服务调用者
调用者最简单的方式即统一写一个带有@Configuration注解的Config类,对需要消费的服务上添加@HSFConsumer 注解(记得在项目中增加依赖starter),将其作为一个JavaBean交给容器进行管理。
1 | @Configuration |
然后在业务中需要使用的地方,直接@Autowired注入即可上述例子中的API配置
1 | /** |
如果嫌这种方法太简单,学不到真正的JavaEE 技术 ,也可以使用JavaBean的方式。
1 | HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean(); |
亦或是在每处需要调用到的业务代码中给出注解的定义,但是不如统一定义个Config类更方便。
1 | @HSFConsumer(serviceVersion = "1.0.0", clientTimeout = 3000, configServerCenters = "hello") |
Example:例如在业务中存在需要对二方包进行RPC调用,只需要做如下的操作:
在pom.xml里添加对二方包的依赖
1
2
3
4
5
6
7
8<!-- 运输实操域 -->
<dependency>
<groupId>com.demo</groupId>
<artifactId>demo-package</artifactId>
<version>${demo-package.version}</version>
</dependency>
....
<demo-package.version>1.0.56</demo-package.version>hsf接口配置类HsfConsumer 中注册Bean
1 | @Configuration |
- 在需要用到这个服务的地方用@Resource注解调用即可
1 | @Resource |
在业务中即可如本地方法调用一般执行HSF调用(当然这是同步调用)
1 | Result<Float> result = demoHsfService.function(param1,param2); |
如果在高并发的情景下,可能就涉及到异步调用,HSF中有两种异步调用的方法:
- Future 调用:客户端在需要获取调用的返回结果时,通过 HSFResponseFuture.getResponse(int timeout) 主动获取结果。与FutureTask有关,创建一个新的线程去执行逻辑,在get结果出阻塞等待结果(待验证)。
- Callback 调用:Callback 调用利用 HSF 内部提供的回调机制,当指定的 HSF 服务消费完毕拿到返回结果时,HSF 框架会回调用户实现的 HSFResponseCallback 接口,客户端通过回调通知的方式获取结果。
3.5 HSF内部架构
如上图所示,HSF框架进一步可细分为4块领域(框架、应用、服务和配置),共11层
- 框架提供了基础功能,负责通信、线程池、协议编解码、序列化以及连接管理相关的工作。
名称 功能 扩展点介绍 Packet 请求对象到通信对象的转换工作,能够将调用层的抽象转换成为网络层定义的抽象,也完成反向转换的工作 PacketFactory
负责实现对象转换工作,如果需要支持不同协议,就需要扩展它Serialize 完成序列化/反序列化工作 Serializer
对应一种序列化协议,比如:hessian2、fastjson等,如果要增加序列化协议,就需要扩展它ThreadPool 线程池管理服务,维护了HSF框架对于线程资源的申请、分配以及线程指标的获取 ThreadPoolManager
完成线程的创建工作,服务端线程动态的创建可以选择扩展它Frame 完成网络层协议编解码工作 Framer
负责完成不同RPC框架网络层协议的编解码工作,该扩展工作在IO线程上,因此要求其扩展不能阻塞Stream 抽象的网络链接层,使用方可以通过Stream来发起调用以及接受响应,开发者可以将Stream适配到不同的NIO框架上 StreamLifecyleListener
用于监听网络链接的建立、销毁等事件,扩展它可以知晓HSF和外部建立的链接StreamMessageListener
用于监听网络层发送以及接受的数据
应用主要面向服务框架的注册和发现过程,是HSF完成分布式调用的基础,用来支撑服务。
名称 功能 扩展点介绍 Registry 定义了注册中心客户端的抽象,完成服务注册以及服务订阅的工作,开发者可以将Registry适配到不同的注册中心实现上 AddressListener
可以监听到服务对应的地址列表AddressListenerInterceptor
完成在地址监听器收到地址前的拦截工作Protocol 定义了协议和流程,能够识别注册中心下推地址的类型,完成发布和消费服务的工作 ProtocolInterceptor
可以拦截服务在发布和订阅中的过程服务的粒度比应用小,它包含了调用链路、地址路由以及负载均衡等功能。
名称 功能 扩展点介绍 InvocationHandler 定义了调用的请求和响应,以责任链的形式实现了调用的拦截处理模式 ClientFilter
拦截客户端发起的请求和收到的响应ServerFilter
拦截服务端收到的请求和发送的响应Router 定义了路由选址过程,一次调用传入Router,Router会根据请求上下文在一组地址列表中选择一堆符合要求的地址列表 AbstractMultiTargetRouter
选址扩展,能够返回多个符合要求的RouterAbstractSingleTargetRouter
选址扩展,返回单个符合要求的RouterLoadBalance 定义了负载均衡的抽象,提供从一组地址中选择一个调用地址的能力 LoadBalancer
负载均衡策略的抽象,比如随机选择或者轮询在服务之上是配置,用户使用API来对各层进行配置,并生成调用的代理或暴露服务。
其实每一层的实现也是可替换可扩展的,替换的关键在于 每一层有自己的核心抽象 。
以注册中心Registry这一层为例,主要的抽象就是Registry以及围绕注册中心推送地址的地址监听器AddressListener,地址推送逻辑都是基于AddressListener来编写的,因此用户只要实现一个Registry接口,就完全可以替换掉这一层的实现。
HSF调用过程的简述:
调用链路从客户端发起调用开始,经历了客户端的选址和负载均衡后,将参数对象完成序列化,经过框架协议编码后,通过网络层发送出去。
服务端接受到数据后进行解码,解码获得的二进制协议派发到服务端线程完成反序列化,生成出参数对象,最终通过反射完成调用。
IO收到网络请求后,通过Frame进行协议解析获得RequestPacket,分派给ThreadPool中的业务线程处理请求,业务线程通过RequestPacket调用Serialize反序列化请求参数等信息,返回Invocation给调用链InvocationHandler发起调用,最终反射调用业务实例ServiceInstance的方法获得业务响应后,框架将其包装为RpcResult后返回给客户端。
4. 对比dubbo / SpringCloud
HSF搭配Config Server、Diamon和Redis已经实现了微服务架构的各种基本功能。
Dubbo支持使用Zoopkeeper、Nacos和Redis作为注册中心
Spring Cloud :注册 + springmvc + 一系列组件 = springcloud
- 服务调用
- HSF: RPC(也支持HTTP)
- Dubbo: RPC
- Spring Clond: Http
- 服务注册:
- HSF: Config Server
- Dubbo: Zookeeper
- 服务监控:
- HSF: HSFOPS http://hsf.alibaba.net/
- Dubbo: Dubbo admin
- 服务治理:
- HSF: 提供服务查询;服务治理;服务测试等功能
- Dubbo: 早期的Dubbo本身是一个RPC框架
5. HSF 带着问题学习
- 调用如何进行拦截和处理?
- 调用如何在一批地址列表上进行路由选择?
- 调用如何在给定的地址列表上选择一个?
- 调用如何完成序列化?
- 序列化后的二进制内容如何被发送到服务端?
- 服务端如何解码调用请求?
- 服务端如何分派线程处理请求?
- 服务端如何反序列化请求,将其还原?
- 服务端如何根据调用语义完成调用执行?
5.1 序列化对比
RPC流程中,序列化是开销很大的环节。尤其现在业务对象都很复杂,序列化开销更不容小觑。
HSF内部集成了一些序列化方式,序列化的工具有很多,一般来说序列化后的尺寸越小,网络传输的代价就越小,但是兼容性会随之降低。
目前HSF2主要用到3类序列化方式,分别是java,hessian1,hessian2
- Java原生序列化:将类信息用字符串的形式全量的写入二进制流,来粗暴的实现序列化。这是比较极端和稳妥的方式,效率低,但不会出错
- hessian:传递元数据和值数据,前者即即类全名,字段名;后者是各个字段对应值。 在hessian1协议里,每次写出Class A的实例时,都会写出Class A的元数据和值数据,就是会重复传输相同的元数据;HSF使用的hessian2协议中实现了跨请求元数据共享,这样只要发送过一次元数据,以后就再也不用发送了,进一步减少传输的数据量。
如果要增加HSF的序列化协议,就需要扩展实现 com.taobao.hsf.io.serialize 接口即可。
5.2 RPC协议效率对比
协议在HSF框架中主要体现在应用层,框架定义了一种私有的RPC协议,精心设计工作在4层的私有协议,相比于工作在7层上的协议效率更高。
Dubbo框架同样使用了工作与4层的RPC协议,但是使用相同的序列化方法,Dubbo协议的请求长度是要大于HSF协议长度
通过实验对比一下:
针对服务com.alibaba.OrderService:1.0.0调用方法queryOrder,参数是Long,使用相同Hessian2序列化方式,Dubbo协议的请求长度是188,而HSF协议长度是98
针对自定义参数对象ModifyOrderPriceParam,Dubbo协议的请求长度是365,而HSF协议长度是268,可以看到随着数据量提升,二者差距变得小了一些。
如果后续要想扩展协议,只需要继承 com.taobao.hsf.protocol.AbstractDelegateProtocolInterceptor 抽象类即可
5.3 负载均衡器
HSF通常具备的负载均衡器有4中,分别是:随机、权重、一致性哈希和轮询,其中运行效率较高的是也是默认采用随机,将用户的请求反向代理到一个具体的服务提供者上。
如果想自定义负载均衡器,只需要实现LoadBalancer接口 即可
案例 :该负载均衡器会选择ip地址以172开头的服务器
1 | public class TargetLoadBalance implements LoadBalancer { |
5.4 HSF 网络层
HSF2.2 之后的网络层中只有一个最基础的抽象Stream,它代表着一个通道,连接两点之间,使其能够相互通信,完成p2p连接的工作;由于客户端需要发起心跳(实现长连接保活),且写出数据后要得到返回,因此根据客户端和服务端的不同,在Stream的基础上,抽象出了ClientStream和ServerStream。
除了流之外,HSF还围绕网络事件的不同类型、客户端与服务端的不同特性定义了4种监听器
‒ ClientStreamLifecycleListener 客户端连接生命周期监听器 (管理客户端的连接)
‒ ClientStreamMessageListener 客户端连接消息监听器 (从客户端的连接中读数据)
‒ ServerStreamLifecycleListener 服务端连接生命周期监听器 (绑定、建立和管理服务端端连接)
‒ ServerStreamMessageListener 服务端连接消息监听器 (向连接写入数据)
如何对HSF网络层进行扩展呢?比如想要对连接进行计数操作:
只需要实现一个自定义的ClientStreamLifecycleListener,可以通过继承ClientStreamLifecycleListenerAdapter来实现
1 | public class ClientConnNumStats extends ClientStreamLifecycleListenerAdapter { |
5.5 HSF对调用进行拦截和处理
为了对RPC进行扩展,实现调用记录、流量控制、调用的路由等功能,需要对调用过程添加interceptor
如下是对调用扩展的接口定义:该接口能够将用户扩展逻辑嵌在HSF调用链中。
1 | @Scope(Scope.Option.PROTOTYPE) |
对于一次调用,发起请求阶段会经过RPCFilter.invoke方法,它能拦截住HSF的请求。当请求发送到远端完成处理之后,数据被写回,当响应到客户端后,将会调用RPCFilter.onResponse方法,因为请求Invocation在发起后会被记录,所以处理响应的线程会拿着当次调用的请求Invocation和远端的响应RPCResult调用RPCFilter.onResponse方法即可。
下面是一个对Filter进行扩展的实例:首先要明确究竟是扩展客户端还是服务端,因为这二者是不一样的子接口(ServerFilter/ClientFilter)。
1 | @Order(300) |
之后RPCFilter会整合到调用处理器(InvocationHandler)链中,这一功能是由FilterInvocationHandler调用RPCFilterBuilder来构造RPCFilter的调用链来实现,该链的结构如下所示:
1 | +--------------------------------------------------------------------------------+ |
转载无需注明来源,放弃所有权利