![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-cf0d92129138e2c138e143696168013a.png)
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-15f764297337962ab399233c2812468b.gif)
SOA解决方案——HSF(High-speed Service Framework)是阿里系主要采用的服务框架,其目的是作为桥梁联通不同的业务系统,解耦系统之间的实现依赖。
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-7402f5636372972aa121accf57bb54b0.png)
▐ 背景
-
服务的方便检索
,查询服务,包括服务的提供者与消费者信息
-
服务的快捷测试
,能够简单、高效的进行服务测试
-
服务的路由
,根据调用的服务名等运行时信息,服务消费方能够路由到对应的服务提供方指定的机器上
-
服务的归组
,能够在统一机器资源维度上,让服务提供方具备服务自动归组的能力
▐ 是什么
▐ 特性
-
高性能的服务调用
低侵入,HSF基于Java接口完成透明的RPC调用,用户对服务是否在本地不做感知,不侵入用户代码。
高性能,HSF提供基于非阻塞I/O上的高性能调用。
多语言,多语言支持完善,提供了C++以及Node.js客户端,支持HTTP REST调用。
-
大流量的场景应对
客户端负载均衡,HSF在客户端基于服务地址列表做负载均衡,不需要借助其他负载均衡设备,高效完成负载均衡工作。
多种选址策略,HSF客户端在调用时提供了多种选址策略。
上下线策略,HSF提供了优雅上下线的能力,保证服务在重启时对客户端的影响面减到最小,客户端调用在服务端重启时表现平滑。
-
全方位的服务治理
服务管理功能,HSF运维平台提供了服务查询、测试和Mock功能,支持用户通过服务名(一般是接口名+版本号)查询服务的提供者,或者通过输入参数对已有的服务进行调用测试。
规则管理功能,HSF运维平台支持使用归组、路由以及同机房等规则对客户端发起的调用进行干预,使客户端调用变得更加智能。
▐ 基本结构
功能结构图:
HSF功能结构上分为6个部分,分别是:「服务消费方」、「服务提供方」、「地址注册中心」、「持久化配置中心」、「元数据存储中心」和「HSF运维平台」(HSF 控制台),它们组合在一起可以提供全功能的分布式服务,其中服务消费方、服务提供方和地址注册中心是必需的,上述功能结构的介绍如下表:
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-d21251cb9a98522f1c29f704eb1b6db0.png)
▐ 调用过程
作为服务消费方,客户端线程首先会将用户的参数也就是请求对象进行序列化,将序列化之后的内容放置到请求通信对象中,请求通信对象对应的是HSF协议,它包含诸如请求Id等多个与请求对象无关的内容。请求通信对象会提交给I/O线程,在I/O线程中完成编码,最终发送到服务提供方,此时客户端线程会等待结果返回,处于等待状态。
服务提供方的I/O线程接收到二进制内容,解码后生成通信请求对象并将其递交给HSF服务端线程,在HSF服务端线程完成反序列化还原成请求对象,然后发起反射调用,得到结果,也就是响应对象。响应对象会在HSF服务端线程中完成序列化,并放置到通信响应对象中。HSF服务端线程会将通信响应对象提交给I/O线程,在I/O线程中完成编码,最终发送回服务消费方。
服务消费方收到二进制内容,在I/O线程中完成解码,生成响应通信对象,并唤醒客户端线程,客户端线程会根据响应通信对象中的内容完成反序列化,最终拿到响应对象,一次远程调用结束。、
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-b6d0985df670edc91ac3cd91c119859f.png)
▐ 服务接口定义
在接口定义模块中定义接口,将其打为jar包,发布到Maven仓库中。
public interface HelloWorldService {
/**
* 根据参数中指定的名字,生成问候语
*
* @param name 被问候的姓名
* @return 问候语
*/
String sayHi(String name);
}
▐ 业务代码实现
com.alibaba.middleware
hsf-guide-api
// 实现接口
public class HelloWorldServiceImpl implements HelloWorldService {
@Override
public String sayHi(String name) {
// 编写业务代码
if (name == null || name.length() == 0) {
return null;
}
return "Hi, " + name + "! Welcome to the HSF world.";
}
}
▐ 服务发布
-
API的方式
com.alibaba.middleware
hsf-guide-biz
com.taobao.hsf
hsf-all
// [定义] 服务的实现
Object target = new HelloWorldServiceImpl();
// [设置] HSF服务发布逻辑
HSFApiProviderBean hsfApiProviderBean = new HSFApiProviderBean();
// [设置] 发布服务的接口
hsfApiProviderBean.setServiceInterface("com.alibaba.middleware.hsf.guide.api.service.HelloWorldService");
// [设置] 服务的实现对象
hsfApiProviderBean.setTarget(target);
// [设置] 服务的版本
hsfApiProviderBean.setServiceVersion("1.0.0");
// [设置] 服务的归组
hsfApiProviderBean.setServiceGroup("HSF");
// [设置] 服务的响应时间
hsfApiProviderBean.setClientTimeout(3000);
// [设置] 服务传输业务对象时的序列化类型
hsfApiProviderBean.setSerializeType("hessian2");
// [发布] HSF服务
hsfApiProviderBean.init();
-
注解的方式
com.alibaba.boot
pandora-hsf-spring-boot-starter
2023-04-release
将@HSFProvider
配置到业务模块的实现类上。
@HSFProvider(serviceInterface = HelloWorldService.class, serviceGroup = "HSF", serviceVersion = "1.0.0", clientTimeout = 3000, serializeType = "hessian2")
public class HelloWorldServiceImpl implements HelloWorldService {
@Override
public String sayHi(String name) {
if (name == null || name.length() == 0) {
return null;
}
return "Hi, " + name + "! Welcome to the HSF world.";
}
}
▐ 服务调用
-
API的方式
在Main方法中调用服务端业务代码。
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
// [设置] 订阅服务的接口
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.HelloWorldService");
// [设置] 服务的版本
hsfApiConsumerBean.setVersion("1.0.0");
// [设置] 服务的组别
hsfApiConsumerBean.setGroup("HSF");
// [订阅] HSF服务,同步等待地址推送,默认false(异步),同步默认超时时间为3000ms
hsfApiConsumerBean.init(true);
// [代理] 获取HSF代理
HelloWorldService helloWorldService = (HelloWorldService) hsfApiConsumerBean.getObject();
// [调用] 像调用本地接口一样,发起HSF调用
String hi = helloWorldService.sayHi("松张");
System.out.println(hi);
-
注解的方式
建配置类,用
@HSFConsumer
标记要调用的接口。
@Configuration
public class HsfConfig {
@HSFConsumer(serviceVersion = "1.0.0", serviceGroup = "HSF")
HelloWorldService helloWorldService;
}
通过注入的方式使用。
@Autowired
HelloWorldService helloWorldService;
调用方式
同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。
HSF的IO操作都是异步的,客户端同步调用的本质是做future.get(timeout)
操作,等待服务端的结果返回,这里的timeout就是客户端生效的超时时间(默认3000ms)。
HSF默认的同步调用时序图:
对于客户端来说,并不是所有的HSF服务都是需要同步等待服务端返回结果的,对于这些服务,HSF提供异步调用的形式,让客户端不必同步阻塞在HSF操作上。异步调用在发起调用时,HSF服务的调用结果都是返回类型的默认值,如返回类型是int,则会返回0,返回类型是Object,则会返回null。而真正的结果,是在HSFResponseFuture或者回调函数(callback)中获得的。
▐ Future异步调用
HSF发起调用后,用户可以在上下文中获取跟返回结果关联的HSFFuture对象,然后用户可以在任意时刻调用HSFFuture.getResponse(timeout)
获取服务端的返回结果。Future异步调用时序图:
API配置客户端Future异步调用。
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.HelloWorldService");
hsfApiConsumerBean.setVersion("1.0.0");
hsfApiConsumerBean.setGroup("HSF");
// [设置] 异步future调用
List asyncCallMethods = new ArrayList();
// [格式] name:{methodName};type:future
asyncCallMethods.add("name:sayHi;type:future");
hsfApiConsumerBean.setAsyncallMethods(asyncCallMethods);
hsfApiConsumerBean.init(true);
HelloWorldService helloWorldService = (HelloWorldService) hsfApiConsumerBean.getObject();
String hi = helloWorldService.sayHi("松张");
// 运行后控制台打印null
System.out.println(hi);
// 及时在当前调用上下文中获取future对象;因为该对象是放在ThreadLocal中的,同一线程中后续调用会覆盖future对象,所以要及时取出
HSFFuture hsfFuture = HSFResponseFuture.getFuture();
// do something else
try {
// 这里才是真正地获取结果,如果调用还未完成,将阻塞等待结果,3000ms是等待结果的最大时间
System.out.println(hsfFuture.getResponse(3000));
} catch (Throwable e) {
e.printStackTrace();
}
▐ Callback异步调用
HSFResponseCallback
接口的Listener,结果返回之后,HSF会调用
HSFResponseCallback
中的
onAppResponse
方法。
// 实现了HSFResponseCallback接口的Listener
public class MyCallbackHandler implements HSFResponseCallback {
@Override
public void onAppException(Throwable t) {
t.printStackTrace();
}
@Override
public void onAppResponse(Object o) {
// 取callback调用时设置的上下文
Object context = CallbackInvocationContext.getContext();
// 打印远程调用结果 + callback调用时设置的上下文
System.out.println(o.toString() + context);
}
@Override
public void onHSFException(HSFException e) {
e.printStackTrace();
}
}
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-7e7373efa680be8072df7328425a8fe5.png)
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.HelloWorldService");
hsfApiConsumerBean.setVersion("1.0.0");
hsfApiConsumerBean.setGroup("HSF");
// [设置] 异步callback调用
List asyncCallMethods = new ArrayList();
// [格式] name:{methodName};type:callback;listener:{listenerFullyQualifiedName}
asyncCallMethods.add("name:sayHi;type:callback;listener:com.alibaba.middleware.hsf.guide.client.handler.CallbackHandler");
hsfApiConsumerBean.setAsyncallMethods(asyncCallMethods);
hsfApiConsumerBean.init(true);
HelloWorldService helloWorldService = (HelloWorldService) hsfApiConsumerBean.getObject();
// 可选步骤,设置上下文。CallbackHandler中通过api可以获取到
CallbackInvocationContext.setContext(" in callback");
String hi = helloWorldService.sayHi("松张");
// 运行后控制台打印null
System.out.println(hi);
// 清理上下文
CallbackInvocationContext.setContext(null);
// do something else
▐ 泛化调用
GenericService
接口,传入需要调用的方法名、方法签名和参数值进行调用服务。泛化调用适用于一些网关应用(没办法依赖所有服务的二方包),其中HSF-OPS服务测试也是依赖泛化调用功能的。
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.HelloWorldService");
hsfApiConsumerBean.setVersion("1.0.0");
hsfApiConsumerBean.setGroup("HSF");
// [设置] 泛化配置
hsfApiConsumerBean.setGeneric("true");
hsfApiConsumerBean.init(true);
// 使用泛化接口获取代理
GenericService genericHelloWorldService = (GenericService)hsfApiConsumerBean.getObject();
// [调用] 发起HSF泛化调用,返回指定类型的result
String helloWorldStr = (String)genericHelloWorldService.$invoke("sayHi",
// 方法入参类型数组(xxx.getClass().getName())
new String[] {String.class.getName()},
// 参数,如果是pojo,则需要转成Map
new Object[] {"松张"});
System.out.println(helloWorldStr);
HSFConsumerBean
设置generic
为true,标识HSF客户端忽略加载不到接口的异常。
GenericService
提供的$invoke
方法包含了真实调用的方法名、入参类型和参数值,以便服务端找到该方法。由于没有依赖服务端的API jar包,传入的参数如果是自定义的DTO,需要转成客户端可以序列化的Map类型。
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-b87b12146284d5d636215347c29add8c.png)
▐ 调用上下文
com.taobao.hsf.util.RequestCtxUtil
类中提供了基于ThreadLocal设置和获取调用上下文内容的静态方法,每次调用getXXX方法,在获取到XXX属性的值后会将该属性从ThreadLocal中remove掉,保证该属性值仅作用于当前线程的单次调用。
▐ 序列化方式
为了在网络中传输数据,需要通过序列化将java对象转为byte数组,反序列化则相反。HSF支持的序列化方式有java
、hessian
、hessian2
、json
和kyro
,默认使用的是hessian2
。java
的兼容性最好,kyro
性能最强,hessian2
和json
比较均衡。
▐ 超时配置
客户端和服务端都可以设置超时时间,客户端的优先级比服务端的高,默认的超时时间是3000ms。在设置超时时间时不仅要考虑业务执行时间,还需要加上序列化和网络通讯的时间。推荐根据业务需要为每个服务配置合适的超时时间。
不同方式设置超时时间的优先级、范围、作用域信息如下表。
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-77e97c4ed9f6a662e3f25005a28e3753.png)
▐ 服务端线程池
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-297d0745a05adc88e152a71abe122279.png)
可以通过JVM启动参数和代码的方式进行配置。
▐ 路由规则
一个简单的接口路由示例如下图所示:
▐ 归组规则
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-2b755c023830897e24846a45243a91c0.png)
▐ 同机房规则
同机房规则保存在Diamond中。作用在消费者发起HSF服务调用的选址阶段,根据机房网段信息优先选择同一个机房的服务提供方发起调用,从而减少跨机房流量的产生。
规则以服务名.RULES作为DataId、服务的组别作为GroupId,采用XML格式编写具体的规则内容。
注意:
-
同机房规则默认是关闭的
-
同机房规则是根据网段作为虚拟机房进行地址选取的
-
同机房规则与路由规则使用了相同的Diamond配置,如果已经配置了路由规则,在原有路由规则的基础上append即可
同机房规则配置如下图所示:
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-f557d69aaf3f24c040924c796ba8d722.png)
-
单一职责原则
:应该有且仅有一个原因引起类的变更
-
里氏替换原则:
所有引用父类的地方必须能透明地使用其子类的对象
-
依赖倒置原则:
面向接口编程,依赖抽象而非细节
-
接口隔离原则:接口尽量细化,接口中的方法尽可能的少
-
迪米特法则:
一个对象应该对其他对象有最少的了解
-
开闭原则:对拓展开放,对修改关闭
▐ 责任链模式
介绍:
ProtocolInterceptor
,它的继承树如下图所示。
![](http://www.e-1.cn/wp-content/uploads/2024/05/frc-4c9f163ff53f4588eac515109ef6dadf.png)
ProtocolInterceptor
实现了
Protocol
接口,里面只增加了一个方法
void setProtocol(Protocol protocol);
,其目的就是形成一个
Protocol
链条,这样就能将
ProtocolInterceptor
的扩展点拼装到流程链条上。
AbstractDelegateProtocolInterceptor
,可以通过继承该抽象类,重写
List export();
等方法,轻松的实现流程的扩展。
Protocol protocol = HSFServiceContainer.getInstance(Protocol.class);
List handlers = HSFServiceContainer.getInstances(ProtocolInterceptor.class);
//init
Protocol last = protocol;
for (int i = handlers.size() - 1; i >= 0; i--) {
handlers.get(i).setProtocol(last);
last = handlers.get(i);
}
return last;
HSFServiceContainer.getInstances(ProtocolInterceptor.class);
会返回优先级从高到低的
xxxProtocolInterceptor
(通过
@Order(int)
注解排序,值越小优先级越高),最后返回的是优先级最高的
ProtocolInterceptor
节点。
export
服务导出这个场景下的执行流程如下。
public abstract class AbstractDelegateProtocolInterceptor implements ProtocolInterceptor {
protected Protocol protocol;
@Override
public List export(ServiceMetadata serviceMetadata, InvocationHandler invocationHandler) {
return protocol.export(serviceMetadata,invocationHandler);
}
}
@Order(250)
public class EagleEyeProtocolInterceptor extends AbstractDelegateProtocolInterceptor {
/**
* container信息(edas)
*/
private ContainerInfo containerInfo = HSFServiceContainer.getInstance(ContainerInfo.class);
@Override
public List export(ServiceMetadata serviceMetadata, InvocationHandler invocationHandler) {
if (containerInfo.isSupportContainer()) {
serviceMetadata.addProperty(HSFConstants.CONTAINER_ID_KEY, containerInfo.getContainerId());
}
return protocol.export(serviceMetadata, invocationHandler);
}
}
此处具体的实现类以「EagleEye的启动阶段拦截」为例,需要在调用protocol.export(serviceMetadata, invocationHandler);
之前编写业务代码。
▐ 代理模式
定义:
为其他对象提供一种代理以控制对这个对象的访问。
介绍:
服务消费方使用代理模式调用服务提供方的方法,获取返回结果。
服务消费方通过代理类与服务提供方建立TCP连接,进行网络通信,将方法和入参传输给服务提供方后,服务提供方通过反射调用指定方法,得到结果,再通过网络将结果传给服务消费方。
创建代理对象:
Object proxy = proxyFactory.getProxy(metadata, decorateInterfaces);
public Object getProxy(ServiceMetadata metadata, Class>... interfacesArray) {
try {
JdkProxyInvocationHandler jdkProxyInvocationHandler = new JdkProxyInvocationHandler(metadata);
Object instance = Proxy.newProxyInstance(metadata.getIfClazz().getClassLoader(), interfacesArray, jdkProxyInvocationHandler);
jdkProxyInvocationHandler.init(instance);
return instance;
} catch (Throwable t) {
throw new HSFException("failed to generate jdk proxy",t);
}
}
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
ApplicationModelFactory.setCurrentApplication(serviceMetadata.getApplicationModel());
ConsumerMethodModel methodModel = serviceMetadata.getConsumerServiceModel().getMethodModel(method);
return InvocationUtil.invoke(methodModel, args);
}
▐ 观察者模式
定义:
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
介绍:
以服务消费方监听路由规则为例,服务消费方通过RegistryProtocolInterceptor
与注册中心进行交互时,会根据当前路由规则构建相应的监听器,监听路由规则的变化,保证调用服务提供方的方法时使用的是最新的路由规则。
注册监听器:
public void registerListener(Object listener) {
synchronized (eventBus) {
if (lastRule != null) {
eventBusHelp.register(listener);
eventBusHelp.post(lastRule);
eventBusHelp.unregister(listener);
}
eventBus.register(listener);
}
}
▐ 装饰模式
定义:
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
介绍:
服务消费方生成调用方法的代理对象的时候,会指定代理对象实现了哪些接口,这些接口都是经过装饰的。
装饰接口:
/**
* 使用该接口对客户端元数据进行处理,返回需要装饰的接口
*/
@Shared
@Scope(Scope.Option.SINGLETON)
public interface ProxyDecorator {
/**
* 用来装饰当前的调用接口
*
* @param serviceMetadata 客户端元数据
* @return 装饰接口,如果不进行装饰返回null
*/
Class> decorate(ServiceMetadata serviceMetadata);
}
// 生成调用远程HSF服务的代理
ProxyDecoratorGenerator proxyDecoratorGenerator = HSFServiceContainer.getInstance(
ProxyDecoratorGenerator.class);
// 获取装饰后的接口
Class>[] decorateInterfaces = proxyDecoratorGenerator.getDecorateInterfaces(metadata);
ProxyFactory proxyFactory = HSFServiceContainer.getInstance(ProxyFactory.class, metadata.getProxyStyle());
Object proxy = proxyFactory.getProxy(metadata, decorateInterfaces);
Method[] methods = proxyFactory.getMethods(proxy);
团队介绍
拓展阅读
¤
终端技术 |
音视频技术
服务端技术 | 技术质量 | 数据算法
本文分享自微信公众号 – 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。