高速服务框架 HSF 的基本原理
SOA解决方案——HSF(High-speed Service Framework)是阿里系主要采用的服务框架,其目的是作为桥梁联通不同的业务系统,解耦系统之间的实现依赖。
▐ 背景
-
服务的方便检索 ,查询服务,包括服务的提供者与消费者信息 -
服务的快捷测试 ,能够简单、高效的进行服务测试 -
服务的路由 ,根据调用的服务名等运行时信息,服务消费方能够路由到对应的服务提供方指定的机器上 -
服务的归组 ,能够在统一机器资源维度上,让服务提供方具备服务自动归组的能力
▐ 是什么
▐ 特性
-
高性能的服务调用
低侵入,HSF基于Java接口完成透明的RPC调用,用户对服务是否在本地不做感知,不侵入用户代码。
高性能,HSF提供基于非阻塞I/O上的高性能调用。
多语言,多语言支持完善,提供了C++以及Node.js客户端,支持HTTP REST调用。
-
大流量的场景应对
客户端负载均衡,HSF在客户端基于服务地址列表做负载均衡,不需要借助其他负载均衡设备,高效完成负载均衡工作。
多种选址策略,HSF客户端在调用时提供了多种选址策略。
上下线策略,HSF提供了优雅上下线的能力,保证服务在重启时对客户端的影响面减到最小,客户端调用在服务端重启时表现平滑。
-
全方位的服务治理
服务管理功能,HSF运维平台提供了服务查询、测试和Mock功能,支持用户通过服务名(一般是接口名+版本号)查询服务的提供者,或者通过输入参数对已有的服务进行调用测试。
规则管理功能,HSF运维平台支持使用归组、路由以及同机房等规则对客户端发起的调用进行干预,使客户端调用变得更加智能。
▐ 基本结构
功能结构图:
HSF功能结构上分为6个部分,分别是:「服务消费方」、「服务提供方」、「地址注册中心」、「持久化配置中心」、「元数据存储中心」和「HSF运维平台」(HSF 控制台),它们组合在一起可以提供全功能的分布式服务,其中服务消费方、服务提供方和地址注册中心是必需的,上述功能结构的介绍如下表:
▐ 调用过程
作为服务消费方,客户端线程首先会将用户的参数也就是请求对象进行序列化,将序列化之后的内容放置到请求通信对象中,请求通信对象对应的是HSF协议,它包含诸如请求Id等多个与请求对象无关的内容。请求通信对象会提交给I/O线程,在I/O线程中完成编码,最终发送到服务提供方,此时客户端线程会等待结果返回,处于等待状态。
服务提供方的I/O线程接收到二进制内容,解码后生成通信请求对象并将其递交给HSF服务端线程,在HSF服务端线程完成反序列化还原成请求对象,然后发起反射调用,得到结果,也就是响应对象。响应对象会在HSF服务端线程中完成序列化,并放置到通信响应对象中。HSF服务端线程会将通信响应对象提交给I/O线程,在I/O线程中完成编码,最终发送回服务消费方。
服务消费方收到二进制内容,在I/O线程中完成解码,生成响应通信对象,并唤醒客户端线程,客户端线程会根据响应通信对象中的内容完成反序列化,最终拿到响应对象,一次远程调用结束。、
▐ 服务接口定义
在接口定义模块中定义接口,将其打为jar包,发布到Maven仓库中。
public interface HelloWorldService {
/**
* 根据参数中指定的名字,生成问候语
*
* @param name 被问候的姓名
* @return 问候语
*/
String sayHi(String name);
}
▐ 业务代码实现
<!-- 依赖接口定义模块 -->
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>hsf-guide-api</artifactId>
</dependency>
// 实现接口
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的方式
<!-- 依赖业务模块 -->
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>hsf-guide-biz</artifactId>
</dependency>
<!-- 依赖HSF -->
<dependency>
<groupId>com.taobao.hsf</groupId>
<artifactId>hsf-all</artifactId>
</dependency>
// [定义] 服务的实现
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();
-
注解的方式
<!-- 依赖starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
<version>2023-04-release</version>
</dependency>
将@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<String> asyncCallMethods = new ArrayList<String>();
// [格式] 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();
}
}
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.HelloWorldService");
hsfApiConsumerBean.setVersion("1.0.0");
hsfApiConsumerBean.setGroup("HSF");
// [设置] 异步callback调用
List<String> asyncCallMethods = new ArrayList<String>();
// [格式] 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类型。
▐ 调用上下文
com.taobao.hsf.util.RequestCtxUtil
类中提供了基于ThreadLocal设置和获取调用上下文内容的静态方法,每次调用getXXX方法,在获取到XXX属性的值后会将该属性从ThreadLocal中remove掉,保证该属性值仅作用于当前线程的单次调用。 ▐ 序列化方式
为了在网络中传输数据,需要通过序列化将java对象转为byte数组,反序列化则相反。HSF支持的序列化方式有java
、hessian
、hessian2
、json
和kyro
,默认使用的是hessian2
。java
的兼容性最好,kyro
性能最强,hessian2
和json
比较均衡。
▐ 超时配置
客户端和服务端都可以设置超时时间,客户端的优先级比服务端的高,默认的超时时间是3000ms。在设置超时时间时不仅要考虑业务执行时间,还需要加上序列化和网络通讯的时间。推荐根据业务需要为每个服务配置合适的超时时间。
不同方式设置超时时间的优先级、范围、作用域信息如下表。
▐ 服务端线程池
可以通过JVM启动参数和代码的方式进行配置。
▐ 路由规则
一个简单的接口路由示例如下图所示:
▐ 归组规则
▐ 同机房规则
同机房规则保存在Diamond中。作用在消费者发起HSF服务调用的选址阶段,根据机房网段信息优先选择同一个机房的服务提供方发起调用,从而减少跨机房流量的产生。
规则以服务名.RULES作为DataId、服务的组别作为GroupId,采用XML格式编写具体的规则内容。
注意:
-
同机房规则默认是关闭的
-
同机房规则是根据网段作为虚拟机房进行地址选取的
-
同机房规则与路由规则使用了相同的Diamond配置,如果已经配置了路由规则,在原有路由规则的基础上append即可
同机房规则配置如下图所示:
-
单一职责原则 :应该有且仅有一个原因引起类的变更 -
里氏替换原则: 所有引用父类的地方必须能透明地使用其子类的对象 -
依赖倒置原则: 面向接口编程,依赖抽象而非细节 -
接口隔离原则:接口尽量细化,接口中的方法尽可能的少 -
迪米特法则: 一个对象应该对其他对象有最少的了解 -
开闭原则:对拓展开放,对修改关闭
▐ 责任链模式
介绍:
ProtocolInterceptor
,它的继承树如下图所示。 ProtocolInterceptor
实现了 Protocol
接口,里面只增加了一个方法 void setProtocol(Protocol protocol);
,其目的就是形成一个 Protocol
链条,这样就能将 ProtocolInterceptor
的扩展点拼装到流程链条上。 AbstractDelegateProtocolInterceptor
,可以通过继承该抽象类,重写 List<ServiceURL> export();
等方法,轻松的实现流程的扩展。 Protocol protocol = HSFServiceContainer.getInstance(Protocol.class);
List<ProtocolInterceptor> 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<ServiceURL> 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<ServiceURL> 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源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
全国首例“AI 外挂”案一审公开宣判
5月6日下午,江西省鹰潭市余江区人民法院公开宣判全国首例“AI外挂”案,对被告人王某合以提供侵入、非法控制计算机信息系统程序、工具罪判处有期徒刑三年,缓刑五年,并处罚金;已退缴的违法所得及扣押的作案工具予以没收,上缴国库;剩余未退缴的个人违法所得继续追缴。宣判后,被告人王某合表示服从判决,不上诉。 经审理查明:被告人王某合为通过制作出售“AI外挂”的方式牟利,2022年,其先后联系万某至、张某(另案处理)等人编写“AI外挂”程序。程序制作完成后,王某合利用网络平台招聘陈某勇、张某文(另案处理)等人作为代理销售程序,并通过出售“AI外挂”点卡密码等获利。至案发,王某合非法获利共计629万余元,其间支付万某至制作费用84万余元,支付张某制作费用42万余元。 经鉴定,案涉“AI外挂”中“cvc”等程序对多款游戏中游戏画面数据进行了未授权获取,对游戏中处理的鼠标数据指令进行了未授权的修改,增加了游戏中“自动瞄准”和“自动开枪”的功能,干扰了游戏的正常运行环境,属于破坏性程序。盒子程序源代码具有接收计算机USB端口传输的鼠标数据指令,并对指令进行计算解析,再将计算结果发送至计算机USB端口,从而...
- 下一篇
淘宝 (taobao.com) 重启网页版优化工作
5 月 6 日,淘宝网发布署名空无(赵昆)的公开信,宣布 taobao.com 优化工作已进行两个月,目前上线了 60 多个功能,集中解决了用户反馈最多的意见和问题,包括频繁登录、PC 大屏优化、购物车展示券后价、店铺 PC 端装修后无法查看商品等。 同时,为贴近 PC 用户使用习惯,淘宝网即日重启淘江湖论坛 (https://bbs.taobao.com/),并将于近期推出移动版,且将在论坛招募「淘宝网智囊团」成为淘宝网站的核心意见贡献者。
相关文章
文章评论
共有0条评论来说两句吧...