HSF-base-info

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
hsf>ls
Current Unit: CENTER

As Provider side:
--------------------------------------------------------------------------------------------------
| SERVICE_NAME | GROUP | PUB |SERIALIZE |WRITE_MODE |
--------------------------------------------------------------------------------------------------
| com.demo.hsf.HelloService:1.0.0.DAILY | HSF_DEMO | Y | hessian2 | |
--------------------------------------------------------------------------------------------------


As Consumer side:
-----------------------------------------------------------------------------------------------
| SERVICE_NAME | GROUP | ADDR_NUM |UN_ADDR_NUM|TIMEOUT|
-----------------------------------------------------------------------------------------------
| com.demo.hsf.HelloService:1.0.0.DAILY | HSF_DEMO | 1 | 1 | 2000 |
-----------------------------------------------------------------------------------------------

3. HSF 调用方式 fast-start

服务发布者

最简单常用的方式就是在需要对外服务的Service上提供一个一个一个@HSFProvider注解

1
2
3
4
5
6
7
@HSFProvider(serviceInterface = HelloService.class,serviceVersion = "${hsf.provider.version}")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
}

在Pandora Boot容器配置文件中写入相关的HSF所属组和版本号(比通过定义HSFApiProviderBean通过api的形式配置HSF服务更方便),然后在业务相关的网关中配置访问路由即可 。

服务调用者

调用者最简单的方式即统一写一个带有@Configuration注解的Config类,对需要消费的服务上添加@HSFConsumer 注解(记得在项目中增加依赖starter),将其作为一个JavaBean交给容器进行管理。

1
2
3
4
5
@Configuration
public class HsfConfig {
@HSFConsumer
private HelloService helloService;
}

然后在业务中需要使用的地方,直接@Autowired注入即可上述例子中的API配置

1
2
3
4
5
6
7
8
9
10
11
/**
* 通过HSFConsumer注入hsf服务,详情见 https://gitlab.alibaba-inc.com/middleware-container/pandora-boot/wikis/spring-boot-hsf
*/
@Controller
@RequestMapping(value = "invoke")
public class HsfController {

@Autowired
@Qualifier("helloService")
private HelloService helloService;
}

如果嫌这种方法太简单,学不到真正的JavaEE 技术 ,也可以使用JavaBean的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();

hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.HelloWorldService");
// [设置] 服务的版本
hsfApiConsumerBean.setVersion("1.0.0");
// [设置] 服务的组别
hsfApiConsumerBean.setGroup("HSF");
// [订阅] HSF 服务,同步等待地址推送,默认 false (异步),同步默认超时时间 3000 毫秒
hsfApiConsumerBean.init(true);

HelloService helloService = (HelloService) hsfApiConsumerBean.getObject();
String hello = HelloService.sayHello("hello");

亦或是在每处需要调用到的业务代码中给出注解的定义,但是不如统一定义个Config类更方便。

1
2
@HSFConsumer(serviceVersion = "1.0.0", clientTimeout = 3000, configServerCenters = "hello")
private HelloService helloService;

Example:例如在业务中存在需要对二方包进行RPC调用,只需要做如下的操作:

  1. 在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>
  2. hsf接口配置类HsfConsumer 中注册Bean

1
2
3
4
5
6
7
8
@Configuration
public class HsfConsumer {
.....
@HSFConsumer
private DemoHsfService demoHsfService;
.....

}
  1. 在需要用到这个服务的地方用@Resource注解调用即可
1
2
3
@Resource
private DemoHsfService demoHsfService;

在业务中即可如本地方法调用一般执行HSF调用(当然这是同步调用)

1
2
Result<Float> result = demoHsfService.function(param1,param2);

如果在高并发的情景下,可能就涉及到异步调用,HSF中有两种异步调用的方法:

  1. Future 调用:客户端在需要获取调用的返回结果时,通过 HSFResponseFuture.getResponse(int timeout) 主动获取结果。与FutureTask有关,创建一个新的线程去执行逻辑,在get结果出阻塞等待结果(待验证)。
  2. 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选址扩展,能够返回多个符合要求的Router
    AbstractSingleTargetRouter选址扩展,返回单个符合要求的Router
    LoadBalance 定义了负载均衡的抽象,提供从一组地址中选择一个调用地址的能力 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

  1. 服务调用
  • HSF: RPC(也支持HTTP)
  • Dubbo: RPC
  • Spring Clond: Http
  1. 服务注册:
  • HSF: Config Server
  • Dubbo: Zookeeper
  1. 服务监控:
  1. 服务治理:
  • HSF: 提供服务查询;服务治理;服务测试等功能
  • Dubbo: 早期的Dubbo本身是一个RPC框架

5. HSF 带着问题学习

  • 调用如何进行拦截和处理?
  • 调用如何在一批地址列表上进行路由选择?
  • 调用如何在给定的地址列表上选择一个?
  • 调用如何完成序列化?
  • 序列化后的二进制内容如何被发送到服务端?
  • 服务端如何解码调用请求?
  • 服务端如何分派线程处理请求?
  • 服务端如何反序列化请求,将其还原?
  • 服务端如何根据调用语义完成调用执行?

5.1 序列化对比

RPC流程中,序列化是开销很大的环节。尤其现在业务对象都很复杂,序列化开销更不容小觑。
HSF内部集成了一些序列化方式,序列化的工具有很多,一般来说序列化后的尺寸越小,网络传输的代价就越小,但是兼容性会随之降低。
目前HSF2主要用到3类序列化方式,分别是java,hessian1,hessian2

  1. Java原生序列化:将类信息用字符串的形式全量的写入二进制流,来粗暴的实现序列化。这是比较极端和稳妥的方式,效率低,但不会出错
  2. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TargetLoadBalance implements LoadBalancer {

@Override
public ServiceURL select(List<ServiceURL> addresses, Invocation invocation) {
for (ServiceURL serviceURL : addresses) {
if (serviceURL.getHost().startsWith("172")) {
return serviceURL;
}
}

return null;
}
@Override
public boolean accept(Invocation invocation) {
return true;
}
}

5.4 HSF 网络层

HSF2.2 之后的网络层中只有一个最基础的抽象Stream,它代表着一个通道,连接两点之间,使其能够相互通信,完成p2p连接的工作;由于客户端需要发起心跳(实现长连接保活),且写出数据后要得到返回,因此根据客户端和服务端的不同,在Stream的基础上,抽象出了ClientStream和ServerStream。
除了流之外,HSF还围绕网络事件的不同类型、客户端与服务端的不同特性定义了4种监听器
‒ ClientStreamLifecycleListener 客户端连接生命周期监听器 (管理客户端的连接)
‒ ClientStreamMessageListener 客户端连接消息监听器 (从客户端的连接中读数据)
‒ ServerStreamLifecycleListener 服务端连接生命周期监听器 (绑定、建立和管理服务端端连接)
‒ ServerStreamMessageListener 服务端连接消息监听器 (向连接写入数据)
如何对HSF网络层进行扩展呢?比如想要对连接进行计数操作:
只需要实现一个自定义的ClientStreamLifecycleListener,可以通过继承ClientStreamLifecycleListenerAdapter来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ClientConnNumStats extends ClientStreamLifecycleListenerAdapter {
@Override
public void connectSuccess(Client client, ClientStream stream) {
// 连接计数器加一
RemotingRuntimeInfoHolder.getInstance().increaseCountConnectionsAsClient();
}

@Override
public void close(Client client, ClientStream stream) {
//减一
RemotingRuntimeInfoHolder.getInstance().decreaseCountConnectionsAsClient();
}
}

5.5 HSF对调用进行拦截和处理

为了对RPC进行扩展,实现调用记录、流量控制、调用的路由等功能,需要对调用过程添加interceptor
如下是对调用扩展的接口定义:该接口能够将用户扩展逻辑嵌在HSF调用链中。

1
2
3
4
5
@Scope(Scope.Option.PROTOTYPE)
public interface RPCFilter {
ListenableFuture<RPCResult> invoke(InvocationHandler nextHandler, Invocation invocation) throws Throwable;
void onResponse(Invocation invocation, RPCResult rpcResult);
}

对于一次调用,发起请求阶段会经过RPCFilter.invoke方法,它能拦截住HSF的请求。当请求发送到远端完成处理之后,数据被写回,当响应到客户端后,将会调用RPCFilter.onResponse方法,因为请求Invocation在发起后会被记录,所以处理响应的线程会拿着当次调用的请求Invocation和远端的响应RPCResult调用RPCFilter.onResponse方法即可。

下面是一个对Filter进行扩展的实例:首先要明确究竟是扩展客户端还是服务端,因为这二者是不一样的子接口(ServerFilter/ClientFilter)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Order(300)
public class HSFServerFilter implements ServerFilter {
public ListenableFuture<RPCResult> invoke(InvocationHandler invocationHandler,
Invocation invocation) throws Throwable {
//process args
String[] sigs = invocation.getMethodArgSigs();
Object[] args = invocation.getMethodArgs();
System.out.println("#### intercept request");
return invocationHandler.invoke(invocation);
}

public void onResponse(Invocation invocation, RPCResult rpcResult) {
System.out.println("#### intercept response");
Object resp = rpcResult.getAppResponse();
System.out.println(resp);
}
}

之后RPCFilter会整合到调用处理器(InvocationHandler)链中,这一功能是由FilterInvocationHandler调用RPCFilterBuilder来构造RPCFilter的调用链来实现,该链的结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+--------------------------------------------------------------------------------+
| com.taobao.hsf.invocation.filter.RPCFilterBuilder$HeadNode |
+--------------------------------------------------------------------------------+
| ^
v |
+--------------------------------------------------------------------------------+
| com.taobao.hsf.rpc.server.ServiceAbsenceFilter |
+--------------------------------------------------------------------------------+
| ^
v |
+--------------------------------------------------------------------------------+
| com.alibaba.middleware.hsf.guide.serverfilter.HSFServerFilter |
+--------------------------------------------------------------------------------+
| ^
v |
+--------------------------------------------------------------------------------+
| com.taobao.hsf2dubbo.context.DubboRPCContextServerFilter |
+--------------------------------------------------------------------------------+
| ^
v |
+--------------------------------------------------------------------------------+
| com.taobao.hsf.invocation.filter.RPCFilterBuilder$TailNode |
+--------------------------------------------------------------------------------+

转载无需注明来源,放弃所有权利