用户指南
HSF(High-speed Service Framework),高速服务框架,是阿里系主要采用的服务框架,其目的是作为桥梁联通不同的业务系统,解耦系统之间的实现依赖。其高速体现在底层的非阻塞I/O以及优秀的序列化机制上,实现了同步和异步调用方式,并且有一套软负载体系,实现分布式应用。
这个手册会从快速跑HSF程序开始入手,然后介绍HSF的相关组件、HSF的服务端和客户端的配置、HSF服务的调用方式和HSF服务的发布方式、软负载体系的介绍和规则配置的讲解,最后介绍了如何通过日志和FAQ文档去自主排查问题。
希望这个小册子可以帮助你从0到1掌握HSF的使用和基本概念和具备自主排查问题的能力,有疑问的地方可以在下方评论,并且欢迎您进行文档的建设。
背景
一般意义上,一个公司的业务系统发展脉络基本都是类似,从单体应用到多应用,从本地调用到远程调用,随着发展需要对远程服务进行高效的资源管理,这个过程是系统应对变化和复杂的应对之道。
每个应用都是解决不同的问题,应用数量的增加会导致复杂性的上升,当复杂性越高,整个系统接收变化的程度就越低,也代表开发成本、维护成本的攀升。如何在应用规模增大的同时,保证响应变化的迅捷,应用之间的沟通或调用方式是关键,下图描述了不同调用方式之间随着应用数量的增加和复杂性之间的关系。
可以看到ORM框架(仅做数据层共享)在应用数很少时,复杂度最低,但是当应用数一旦过千,复杂度程指数级攀升。SOA在应用数很少时,由于其技术复杂性导致其初始复杂度较高,但是当应用数快速攀升过万时,复杂度并不会显著提升,仍处于可控状态,同时复杂度表现又明显的优于单纯的RPC方案。
单体应用的问题
单体应用的主要问题是不同的业务相互纠缠在一起,面对快速发展的业务,这种开发模型和架构不利于业务发展,主要体现在以下方面:
架构分化,分工不同的业务开发团队对于开发细节和实现方式在一段时间后一定有差别
开发效率,团队业务发展快慢区别导致发布的频度会不一样,团队之间需要相互配合和知会,导致效率低下
可用性低,一个团队的严重问题导致单体应用挂掉,将影响到另一个团队,稳定性难以提升
分拆应用的好处
将不同的业务分拆到多个应用中,让不同的应用分别承担不同的功能,例如:商品应用承担商品信息的管理,会员应用承担会员核心业务的实现。分拆出来的功能分布到不同的系统后,就可以做到技术实现的多样性以及适合性,带来发布的自由度以及系统的稳定性会极大地提升。
经过演化,一个单体应用变成了一组复杂的分布式系统,而在一个应用时,相互调用直接在本地完成,而变为多个系统时,相互之间进行通信的方式就不能依赖本地,而必须走远程,因此一个高效、稳定的RPC框架就变得非常重要。
服务即资源
随着业务不断的发展,可以想象承担不同业务的应用雨后春笋般的出现,每个应用都有很多服务。如果把这些服务都理解为资源的话,对于资源的管理就变得愈发重要,在RPC框架刚开始使用的时候,可能只有几个应用,几十个服务,如果规模扩充到上万应用,几十万个服务,RPC调用反而不是重头戏,而重要的是如何能高效的组织这些服务。
一般来说,需要(或者最好能够具备)服务治理的能力:
服务的方便检索,查询服务,包括服务的提供者与消费者信息
服务的快捷测试,提升分布式场景验证服务的便捷性
服务的路由,根据调用的服务名等运行时信息,服务消费方能够路由到对应的服务提供方指定的机器上
这些特性都已经超越了一个普通RPC框架的范畴,而提供这些能力的RPC框架才能被称之为SOA框架。
阿里SOA解决方案
阿里SOA解决方案–HSF(High-speed Service Framework),高速服务框架。该框架是阿里系主要采用的服务框架,其目的是作为桥梁联通不同的业务系统,解耦系统之间的实现依赖。其高速体现在底层的非阻塞I/O以及优秀的序列化机制上,实现了同步和异步调用方式,并且有一套软负载体系,实现分布式应用。HSF超越了普通的SOA解决方案,在以下几个方面有更加优秀的特性:
高性能的服务调用
低侵入,HSF基于Java接口完成透明的RPC调用,用户对服务是否在本地不做感知,不侵入用户代码。
高性能,HSF提供基于非阻塞I/O上的高性能调用,相同场景下与gRPC做了性能对比测试,超过gRPC约30%(达到41K的TPS)。
多语言,多语言支持完善,提供了C++以及nodejs客户端,支持HTTP REST调用。
大流量的场景应对
客户端负载均衡,HSF在客户端基于服务地址列表做负载均衡,不需要借助其他负载均衡设备,高效完成负载均衡工作。
多种选址策略,HSF客户端在调用时提供了多种选址策略,以服务端重启这个场景为例,HSF提供了基于可用地址的选址策略,当发现地址的链接不可用时,会暂时将该地址移出地址列表并尝试恢复链接,这样既保证调用的平滑,又能够使服务端机器在重启完成后对应的地址被加回地址列表。
上下线策略,HSF提供了优雅上下线的能力,保证服务在重启时对客户端的影响面减到最小,客户端调用在服务端重启时表现平滑。
全方位的服务治理
服务管理功能,HSF运维平台提供了服务查询、测试和Mock功能,支持用户通过服务名(一般是接口名)查询服务的提供者,或者通过输入参数对已有的服务进行调用测试。
规则管理功能,HSF运维平台支持使用归组、路由以及同机房等规则对客户端发起的调用进行干预,使客户端调用变得更加智能。
发展历程
2007年的淘宝是一个单体应用架构,光用堆机器的方式已经无法支撑业务增长,所以采用应用拆分的方式来满足业务增长需求。
应用拆分离不开RPC框架,而HSF的出现就承担了这个角色,从1.1版本仅为了解决分布式调用的简陋RPC框架,到最新的2.2版本一个高性能、易扩展的SOA框架,HSF的主要发展历程如下表:
基本结构
HSF功能结构上分为6个部分,分别是:服务消费方、服务提供方、地址注册中心、持久化配置中心、元数据存储中心和HSF运维平台(HSF 控制台),它们组合在一起可以提供全功能的分布式服务,其中必须的是服务消费方、服务提供方和地址注册中心,上述功能结构的描述如下表:
在阿里巴巴集团内部:
地址注册中心的角色是由 ConfigServer 承担的
持久化配置中心的角色是由 Diamond 承担的
元数据存储中心的角色是由 Redis 承担的
HSF 控制台的角色是由 HSFOPS 承担的
上述HSF功能结构之间的关系如下图所示:
从上图可以看到,服务提供方在启动后会向地址注册中心发布地址,服务消费方根据服务名向地址注册中心订阅服务地址,当服务地址推送到服务消费方后,服务消费方就可以从地址列表中选择一个地址发起RPC调用。服务提供方在发布地址的同时会将服务元信息发布到元数据存储中心,HSF控制台通过访问元数据存储中心向使用者展示服务的详情,同时HSF控制台还可以通过持久化配置中心和地址注册中心客户端查询服务信息和规则信息。
部署环境
HSF是SOA框架,目的是连接起服务提供方和服务消费方,框架本身是无状态的,而整个服务框架的状态信息是依靠地址注册中心和持久化配置中心。
软件环境分为:日常、预发和生产三种,如果只有一种,生产环境的数据在日常线下就会被访问和操作,这是不允许的,而环境的分类就需要依靠状态信息分开存储,也就是环境的区分依赖于地址注册中心和持久化配置中心(软负载体系)的区分。简单的说,每个环境下都会有一套地址注册中心和持久化配置中心,而HSF框架都是一样的。
在不同环境中,它们之间关系如下图所示:
可以看到地址注册中心和持久化配置中心是相互隔离的,做项目或者需求时,会遵循从日常、预发和线上的顺序发布应用,而在某个环境中进行操作和排查问题时,需要使用部署在对应环境的HSF控制台或者软负载产品(地址注册中心和持久化配置中心)。
编写服务端
服务开始于接口的定义,我们首先定义一个接口。一般会将接口定义在一个工程中,它会打成一个jar包,发布到maven仓库中。服务端实现这些接口,然后发布对应的服务,而消费端通过依赖这个jar包,透过HSF远程调用到服务端。
以下示例项目可以在hsf-guide中找到
服务接口定义
先定义一个OrderService,它在hsf-guide-api这个工程中。
public interface OrderService {
/**
* <pre>
* 根据订单id查询一笔订单
*
* </pre>
*
* @param id 订单id
* @return 如果查询不到返回null
*/
OrderModel queryOrder(Long id);
}
这个工程将会打包成为hsf-guide-api.jar,未来需要调用这个接口的消费方,可以通过依赖这个jar包完成远程调用,比如依赖一个maven坐标即可。
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>hsf-guide-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
业务代码实现
一般意义上,对于业务代码的实现不会耦合上任何框架的约束,对于一个接口的实现一般工程模块会与下图相似。
可以看到对于OrderService的实现OrderServiceImpl,它使用使用spring jdbc来进行数据库操作,配置文件分为biz.xml、dao.xml和datasource.xml,里面分别定义了OrderService以及OrderDAO和数据源。
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDAO orderDAO;
@Override
public OrderModel queryOrder(Long id) {
return orderDAO.queryOrder(id);
}
}
可以看到对于订单的查询非常简单,只是调用DAO进行查询一下返回就好。整个过程没有一点其他框架的影子(除了Spring),非常简单且容易测试(使用spring-test可以方便的进行单元测试)。直到这里还没有涉及到HSF,原因是我们希望你的api和业务实现能够足够的干净和少依赖,只有这样一个本地服务才能够更加安全可靠的转换成为一个分布式服务。
发布服务
当服务编写好了,就可以将服务发布出去,让其他客户端调用到当前机器。
服务发布首先需要进行配置,配置是将一个本地的普通服务转变为分布式服务的前提条件。在配置阶段能够感受到HSF对于服务提供方所提出的语义要求和约束。
API形式配置HSF服务
发布HSF服务可以通过HSFApiProviderBean来完成,该过程只需要做一次,该对象比较重,建议缓存起来。
// ---------------------- 装配 -----------------------//
// [设置] HSF服务发布逻辑
HSFApiProviderBean hsfApiProviderBean = new HSFApiProviderBean();
// [设置] 发布服务的接口
hsfApiProviderBean.setServiceInterface("com.alibaba.middleware.hsf.guide.api.service.OrderService");
// [设置] 服务的实现对象
hsfApiProviderBean.setTarget(target);
// [设置] 服务的版本
hsfApiProviderBean.setServiceVersion("1.0.0");
// [设置] 服务的归组
hsfApiProviderBean.setServiceGroup("HSF");
// [设置] 服务的响应时间
hsfApiProviderBean.setClientTimeout(3000);
// [设置] 服务传输业务对象时的序列化类型
hsfApiProviderBean.setPreferSerializeType("hessian2");
// ---------------------- 发布 -----------------------//
// [发布] HSF服务
hsfApiProviderBean.init();
HSFApiProviderBean构建完成后,设置这个服务的接口名,能够让订阅方根据接口进行订阅,同时来自远端的请求也能够找到本机的具体实现。随后设置了服务的版本与归组,服务通过接口名、版本和归组来确定一个服务实例。可以用maven中对于一个坐标的定位(GAV)来理解这个概念。然后我们将HSFApiProviderBean与具体的服务实现进行绑定,也就是setTarget(Object obj)方法。
基本配置属性表:
Spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean class="com.taobao.hsf.app.spring.util.HSFSpringProviderBean" init-method="init">
<!--[设置] 发布服务的接口-->
<property name="serviceInterface" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
<!--[设置] 服务的实现对象 target必须配置[ref],为需要发布为HSF服务的spring bean id-->
<property name="target" ref="引用的BeanId"/>
<!--[设置] 服务的版本-->
<property name="serviceVersion" value="1.0.0"/>
<!--[设置] 服务的归组-->
<property name="serviceGroup" value="HSF"/>
<!--[设置] 服务的响应时间-->
<property name="clientTimeout" value="3000"/>
<!--[设置] 服务传输业务对象时的序列化类型-->
<property name="preferSerializeType" value="hessian2"/>
</bean>
注解配置HSF服务
SpringBoot广泛使用的今天,使用注解装配SpringBean也成为一种选择,HSF也支持使用注解进行配置,用来发布服务。
首先是在项目中增加依赖starter。
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>
然后将@HSFProvider配置到实现的类型上,上述例子中的API配置等同于如下注解配置:
注意是com.alibaba.boot.hsf.annotation.HSFProvider,不是com.taobao.hsf.app.spring.util.annotation.HSFProvider
@HSFProvider(serviceInterface = OrderService.class, serviceGroup = "HSF", serviceVersion = "1.0.0", clientTimeout = 3000)
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDAO orderDAO;
@Override
public OrderModel queryOrder(Long id) {
return orderDAO.queryOrder(id);
}
}
通过HSF-OPS查询服务
在完成 HSF 服务的发布之后,我们可以通过 HSF 运维系统(HSF-OPS)查询已发布的服务,从而验证服务发布的正确性,并获取服务的相关信息。
服务查询
访问 HSFOPS 的服务查询页面,准备进行服务查询:
日常环境:http://hsf.alibaba.net/hsfops/serviceSearch.htm?envType=daily
预发环境:http://hsf.alibaba-inc.com/hsfops/serviceSearch.htm?envType=pre
线上环境:http://hsf.alibaba-inc.com/hsfops/serviceSearch.htm?envType=online
HSFOPS 支持多种维度的服务查询能力,包括根据服务名查询,根据IP查询,以及根据应用名查询:
服务名:根据服务的 DataId(接口名:版本号)查询服务是否存在
IP:查询指定 IP 提供的服务 或 消费的服务
应用名:查询指定应用提供的服务列表。
注意:
根据 “应用名” 查询服务
查询结果中的 “应用名” 属性
是从 redis 中读取的 缓存 信息,而非 configserver 中的实时发布数据,因此会出现不准确的情况,但并不会影响 HSF 服务的正常调用。
若应用已经正确的配置了 -Dproject.name=yourAppName JVM 参数,则只需耐心等待缓存刷新即可(最迟1小时)。
服务详情
通过 HSFOPS 查询到发布的服务后,点击 “详情” 按钮即可查看服务的详情信息,包括:
- 服务所属的应用信息
- 服务提供方、消费方的 IP 列表
- 服务的方法列表(元数据)
- …
通过HSF-OPS测试服务
在通过 HSF 运维系统(HSF-OPS)的服务查询功能确认服务发布成功后,我们可以通过 “服务测试” 功能快速的发起一次服务调用,验证 HSF 服务中的业务逻辑。
HSF服务测试问题汇总
-
http://gitlab.alibaba-inc.com/middleware-container/hsf-guide/issues/78347
选择测试方法
访问 HSFOPS 的服务测试页面,准备进行服务测试: -
日常环境:http://hsf.alibaba.net/hsfops/testpage/testPage.htm?envType=daily
-
预发环境:http://hsf.alibaba-inc.com/hsfops/testpage/testPage.htm?envType=pre
-
线上环境:http://hsf.alibaba-inc.com/hsfops/testpage/testPage.htm?envType=online
进入服务测试页面后,首先查询需要测试的服务,点击 “选择测试方法” 按钮查看服务的方法列表;在服务的方法列表中,找到需要测试的方法,点击 “测试” 进入方法测试页面。
注意:
服务测试功能具有权限控制,并按照应用级别进行鉴权:
- 日常:全部登录用户可调用
- 预发:仅服务所属应用的 AppOps、开发负责人、测试负责人 可调用
- 线上:仅服务所属应用的 AppOps
可调用
编辑方法参数
进入最终的测试页面后,默认会展示出服务方法的参数格式,在 json 编辑器中,按需输入服务调用参数,并点击 “测试” 按钮,就完成了一次服务测试调用。
调用结果、服务提供方 IP、traceId 等信息会在页面下方的测试结果区域给出。
注意:
- HSFOPS 的服务测试采用 HSF 泛化调用机制,对 DTO 的 “干净” 程度要求比较高,要有标准的 getters 和 setters,并且尽量不要在其中放置业务逻辑
- 对于一些复杂参数的编辑,需要增、删属性字段时,可以切到 code 模式操作
编写调用端
服务提供方应用发布,并且客户端Jar包发布到maven仓库中后,服务消费方就可以调用了。
服务调用与服务的发布过程基本对称,需要对服务进行配置,但是在配置前还是需要先依赖服务提供方发布在maven仓库中的客户端jar包。
依赖客户端Jar包
首先需要询问服务提供方合适的客户端Jar包maven坐标,本文示例中类似如下配置:
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>hsf-guide-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
API形式配置HSF服务
消费HSF服务可以通过HSFApiConsumerBean来完成,该过程只需要做一次,该对象比较重,包括获取到的HSF代理,建议缓存起来。
其实在HSF内部HSFApiConsumerBean对服务的配置也是缓存起来的,也就是说如果堆一个订阅的服务有多次配置,只有第一次配置会生效
// ---------------------- 装配 -----------------------//
// [设置] HSF服务订阅逻辑
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
// [设置] 订阅服务的接口
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.OrderService");
// [设置] 服务的版本
hsfApiConsumerBean.setVersion("1.0.0");
// [设置] 服务的归组
hsfApiConsumerBean.setGroup("HSF");
// ---------------------- 订阅 -----------------------//
// [订阅] HSF服务,同步等待地址推送,默认false(异步),同步默认超时时间3000毫秒
hsfApiConsumerBean.init(true);
// ---------------------- 代理 -----------------------//
// [代理] 获取HSF代理
OrderService orderService = (OrderService) hsfApiConsumerBean.getObject();
// ---------------------- 调用 -----------------------//
// [调用] 发起HSF调用
OrderModel orderModel = orderService.queryOrder(1L);
HSFApiConsumerBean构建完成后,设置这个服务的接口名、版本与归组,就可以从地址注册中心订阅服务地址,当地址注册中心将地址推送到客户端后,就可以发起远程调用了。
基本配置属性表:
Spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean id="CallHelloWorld" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean">
<!--[设置] 订阅服务的接口-->
<property name="interfaceName" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
<!--[设置] 服务的版本-->
<property name="version" value="1.0.0"/>
<!--[设置] 服务的归组-->
<property name="group" value="HSF"/>
</bean>
注解配置HSF服务
SpringBoot广泛使用的今天,使用注解装配SpringBean也成为一种选择,HSF也支持使用注解进行配置,用来订阅服务。
首先是在项目中增加依赖starter。
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>
通常一个HSF Consumer需要在多个地方使用,但并不需要在每次使用的地方都用@HSFConsumer来标记。只需要写一个统一个Config类,然后在其它需要使用的地方,直接@Autowired注入即可上述例子中的API配置等同于如下注解配置:
@Configuration
public class HsfConfig {
@HSFConsumer(serviceVersion = "1.0.0", serviceGroup = "HSF")
OrderService orderService;
}
在使用时直接注入即可:
@Autowired
OrderService orderService;
一次调用过程
HSF一次调用过程会从服务消费方发起,经过网络抵达服务提供方,再将服务提供方的结果通过网络携带回来,最终返回给用户。这个过程会涉及到多个线程交互,也会涉及到HSF中的不同领域对象。
HSF一次调用过程如下图所示:
作为服务消费方,在客户端线程(比如:tomcat线程)中首先会将用户的参数也就是请求对象进行序列化,将序列化之后的内容放置到请求通信对象中,请求通信对象对应的是HSF协议,它包含诸如请求Id等多个与请求对象无关的内容。请求通信对象会提交给I/O线程,在I/O线程中完成编码,最终发送到服务提供方,此时客户端线程会等待结果返回,处于等待状态。
服务提供方的I/O线程接收到二进制内容,解码后生成通信请求对象并将其递交给HSF服务端线程,在HSF服务端线程完成反序列化还原成请求对象,然后发起反射调用,得到结果,也就是响应对象。响应对象会在HSF服务端线程中完成序列化,并放置到通信响应对象中。HSF服务端线程会将通信响应对象提交给I/O线程,在I/O线程中完成编码,最终发送回服务消费方。
服务消费方收到二进制内容,在I/O线程中完成解码,生成响应通信对象,并唤醒客户端线程,客户端线程会根据响应通信对象中的内容完成反序列化,最终拿到响应对象,一次远程调用结束。
异步调用
HSF的IO操作都是异步的,客户端同步调用的本质是做future.get(timeout)操作,等待服务端的结果返回,这里的timeout就是客户端生效的超时时间(默认3000ms)。默认的同步调用时序图如下所示:
对于客户端来说,并不是所有的HSF服务都是需要同步等待服务端返回结果的,对于这些服务,HSF 提供异步调用的形式,让客户端不必同步阻塞在HSF操作上。 异步调用在发起调用时,HSF服务的调用结果都是返回值的默认值,如返回类型是int,则会返回 0,返回类型是Object,则会返回null。而真正的结果,是在HSFResponseFuture或者回调函数(callback)中获得的。
Future异步调用
HSF发起调用后,用户可以在上下文中获取跟返回结果关联的HSFFuture对象,然后用户可以在任意时刻调用HSFFuture.getResponse(timeout)获取服务端的返回结果。Future异步调用的时序图如下所示:
API形式配置HSF服务
HSF提供了方法级别的异步调用配置,格式为name:${methodName};type:future,由于只用方法名字来标识方法,所以并不区分重载的方法。同名的方法都会被设置为同样的调用方式。具体配置如下:
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.OrderService");
hsfApiConsumerBean.setVersion("1.0.0");
hsfApiConsumerBean.setGroup("HSF");
// [设置] 异步future调用
List<String> asyncallMethods = new ArrayList<String>();
//格式:name:{methodName};type:future
asyncallMethods.add("name:queryOrder;type:future");
hsfApiConsumerBean.setAsyncallMethods(asyncallMethods);
hsfApiConsumerBean.init(true);
// [代理] 获取HSF代理
OrderService orderService = (OrderService) hsfApiConsumerBean.getObject();
// ---------------------- 调用 -----------------------//
// [调用] 发起HSF异步调用, 返回null
OrderModel orderModel = orderService.queryOrder(1L);
// 及时在当前调用上下文中,获取future对象;因为该对象是放在`ThreadLocal`中,同一线程中后续调用会覆盖future对象,所以要及时取出。
HSFFuture hsfFuture = HSFResponseFuture.getFuture();
// do something else
// 这里才真正地获取结果,如果调用还未完成,将阻塞等待结果,5000ms是等待结果的最大时间
try {
System.out.println(hsfFuture.getResponse(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
这里提下超时的概念,HSF默认的超时配置是3000ms,如果过了超时时间,业务对象未返回,这时调用HSFFuture.getResponse会抛出超时异常;HSFFuture.getResponse(timeout),如果这里的timeout时间内,业务结果没有返回,也没有超时,可以调用多次执行getResponse去获取结果。
Spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean id="orderService" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean">
<property name="interfaceName" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
<property name="version" value="1.0.0"/>
<property name="group" value="HSF"/>
<!--[设置] 订阅服务的接口-->
<property name="asyncallMethods">
<list>
<value>name:queryOrder;type:future</value>
</list>
</property>
</bean>
注解配置HSF服务
SpringBoot广泛使用的今天,使用注解装配SpringBean也成为一种选择,HSF也支持使用注解进行配置,用来订阅服务。
首先是在项目中增加依赖starter。
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>
通常一个HSF Consumer需要在多个地方使用,但并不需要在每次使用的地方都用@HSFConsumer来标记。只需要写一个统一个Config类,然后在其它需要使用的地方,直接@Autowired注入即可上述例子中的API配置等同于如下注解配置:
@Configuration
public class HsfConfig {
@HSFConsumer(serviceVersion = "1.0.0", serviceGroup = "HSF", futureMethods = "sayHelloInFuture")
OrderService orderService;
}
在使用时直接注入即可:
@Autowired
OrderService orderService;
Callback异步调用
客户端配置为callback方式调用时,需要配置一个实现了HSFResponseCallback接口的listener,结果返回之后,HSF会调用HSFResponseCallback中的方法。时序图如下所示:
API形式配置HSF服务
callback的调用上下文
用户在调用前还可以通过CallbackInvocationContext.setContext(Object obj),来设置一个关于本次调用的上下文信息,该信息存放在threadlocal中。在listener的回调函数中,可以通过CallbackInvocationContext.getContext()来获取该对象。
回调函数示例
public class CallbackHandler implements HSFResponseCallback {
//业务异常时会触发
@Override
public void onAppException(Throwable t) {
t.printStackTrace();
}
//业务返回结果
@Override
public void onAppResponse(Object result) {
// 取callback调用时设置的上下文
Object context = CallbackInvocationContext.getContext();
System.out.println(result.toString() + context);
}
//HSF异常
@Override
public void onHSFException(HSFException e) {
e.printStackTrace();
}
}
接口callback方法配置
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.OrderService");
hsfApiConsumerBean.setVersion("1.0.0");
hsfApiConsumerBean.setGroup("HSF");
// [设置] 异步callback调用
List<String> asyncallMethods = new ArrayList<String>();
asyncallMethods.add("name:queryOrder;type:callback;listener:com.alibaba.middleware.hsf.CallbackHandler");
hsfApiConsumerBean.setAsyncallMethods(asyncallMethods);
hsfApiConsumerBean.init(true);
// [代理] 获取HSF代理
OrderService orderService = (OrderService) hsfApiConsumerBean.getObject();
// 可选步骤,设置上下文。CallbackHandler中通过api可以获取到
CallbackInvocationContext.setContext("in callback");
// 发起调用
orderService.queryOrder(1L); // 这里返回的其实是null
// 清理上下文
CallbackInvocationContext.setContext(null);
// do something else
在调用线程中可以设置上下文,然后在listener中获取使用。相对于Future异步调用,callback会立即知晓结果的返回。
spring配置HSF服务
<bean id="CallHelloWorld" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean">
<!--[设置] 订阅服务的接口-->
<property name="interfaceName" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
<!--[设置] 服务的版本-->
<property name="version" value="1.0.0"/>
<!--[设置] 服务的归组-->
<property name="group" value="HSF"/>
<property name="asyncallMethods">
<list>
<!--future的含义为通过Future的方式去获取请求执行的结果,例如先调用下远程的接口,接着在同一线程继续做别的事情,然后再在同一线程中通过Future来获取结果 -->
<!--name:methodName;type:future|callback-->
<value>name:queryOrder;type:callback;listener:com.alibaba.middleware.hsf.CallbackHandler</value>
</list>
</property>
</bean>
注解配置HSF接口方法为callback调用
将@AsyncOn注解配置到callback类上,指明作用到的接口类和方法。
@AsyncOn(interfaceName = OrderService.class, methodName = "queryOrder")
public class CallbackHandler implements HSFResponseCallback {
@Override
public void onAppException(Throwable t) {
t.printStackTrace();
}
@Override
public void onAppResponse(Object result) {
// 取callback调用时设置的上下文
Object context = CallbackInvocationContext.getContext();
System.out.println(result.toString() + context);
}
@Override
public void onHSFException(HSFException e) {
e.printStackTrace();
}
}
注意
回调函数是由单独的线程池(LinkedBlockingQueue无限队列)来调用的,不要做太费时间的操作,避免影响其他请求的onAppResponse回调。 callback线程默认的corePoolSize, maxPoolSize是机器cpu数目。 下面的-D参数可以去自定义配置。
CALLBACK 线程池最小配置: -Dhsf.callback.min.poolsize
CALLBACK 线程池最大的配置 -Dhsf.callback.max.poolsize
泛化调用
相对于需要依赖业务客户端Jar包的正常调用,泛化调用,不要不依赖二方包,使用其特定的GenericService接口,传入需要调用的方法名,方法签名和参数值进行调用服务。 泛化调用适用于一些网关应用(没办法依赖所有服务的二方包),其中hsfops服务测试也是依赖泛化调用功能
API形式配置HSF服务
将HSFConsumerBean, 配置generic为true,标识HSF客户端忽略加载不到接口的异常。
HSFApiConsumerBean hsfApiConsumerBean = new HSFApiConsumerBean();
hsfApiConsumerBean.setInterfaceName("com.alibaba.middleware.hsf.guide.api.service.OrderService");
hsfApiConsumerBean.setVersion("1.0.0");
hsfApiConsumerBean.setGroup("HSF");
// [设置] 泛化配置
hsfApiConsumerBean.setGeneric("true");
hsfApiConsumerBean.init(true);
//使用泛化接口获取代理
GenericService genericOrderService = (GenericService) hsfApiConsumerBean.getObject();
// ---------------------- 调用 -----------------------//
// [调用] 发起HSF泛化调用, 返回map类型的result。
Map orderModelMap = (Map) genericOrderService.$invoke("queryOrder",
// 方法入参类型数组(xxx.getClass().getName())
new String[] { Long.class.getName() },
//参数,如果是pojo,则需要转成Map
new Object[] { 1L });
可以看到,GenericService提供的$invoke方法包含了真实调用的方法名、入参类型和参数,以便服务端找到改方法。由于没有依赖服务端的API jar包,传入的参数如果是自定义的DTO,需要转成客户端可以序列化的Map类型。
调用传方法签名和参数说明
1.方法没有入参,可以只传methodName: service.$invoke(“sayHello”, null, null)
2.方法类型有泛型的,比如List,只需要传java.util.List,即List.class.getName()的值,不要传成java.util.List,否则会出现方法找不到的错误。
3.调用方在不确定格式的情况下,可以写个单元测试,测试时依赖需要泛化调用的二方包,使用HSF提供的工具类com.taobao.hsf.util.PojoUtils的 generalize() 方法来生成一个 POJO Bean 的Map描述格式。
Map pojoMap = (Map) PojoUtils.generalize(new OrderModel())
4.传递参数为pojo的demo:
class User {
private String name;
private int age;
//需要是标准的pojo格式,这里省略getter setter
}
//直接使用map去构造pojo对应的泛化参数
Map param = new HashMap<String, Object>();
param.put("age", 11);
param.put("name","Miles");
//当传递的参数是声明参数类型的子类时,需要传入class字段,标明该pojo的真实类型(服务端需要有该类型)
param.put("class", "com.taobao.User");
Spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean id="CallHelloWorld" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean">
<!--[设置] 订阅服务的接口-->
<property name="interfaceName" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
<!--[设置] 服务的版本-->
<property name="version" value="1.0.0"/>
<!--[设置] 服务的归组-->
<property name="group" value="HSF"/>
<property name="generic" value="true"/>
</bean>
注意
- 泛化调用,如果客户端没有接口类,路由规则默认不生效
- 泛化调用性能会比正常调用差
- 配置抛出业务异常
-
Dhsf.generic.throw.exception=true (默认是false, 把异常泛化成map返回)
-
- 本地存在异常类,由于com.taobao.hsf.remoting.service.GenericService 上没有声明该异常,如果不是RuntimeException类型或其子类,则会抛出UndeclaredThrowableException,可以通过getCause获取真实异常
-
- 本地没有该异常类,则抛出com.taobao.hsf.util.GenericInvocationException
调用上下文
请求上下文包括一次调用相关的属性,比如调用的地址,调用方的应用名,超时时间等属性和用户在接口定义的参数之外传递自定义的数据。
设置和获取本次调用上下文
com.taobao.hsf.util.RequestCtxUtil提供设置和获取调用上下文的静态方法,基于ThreadLocal工作, getXXX操作会将XXX属性从当前ThreadLocal变量中remove掉,仅作用于当前线程的单次调用。具体属性的设置和获取如下:
客户端
服务端
传递自定义请求上下文
RpcContext提供一种不修改接口,向服务端额外传递数据的方式。参数可以是自定义的DO或者基本类型。要保证对端也有该对应的类型,并且可以能够被序列化。
范例
maven依赖
<dependency>
<groupId>com.taobao.hsf</groupId>
<artifactId>hsf-feature-context</artifactId>
</dependency>
客户端发起调用前,设置上下文
//setup context before rpc call
RPCContext rpcContext = RPCContext.getClientContext();
rpcContext.putAttachment("tetantId", "123");
//rpc call,context也会传到远端
orderService.queryOrder(1L);
服务端业务方法内,获取上下文
//get context data
RPCContext rpcContext = RPCContext.getServerContext();
String myContext = (String)rpcContext.getAttachment("tetantId");
序列化方式配置
序列化的过程是将java对象转成byte数组在网络中传输,反序列化会将byte数组转成java对象。序列化的选择需要考虑兼容性,性能等因素,HSF的序列化方式支持java、hessian、hessian2、json、kyro,默认是hessian2。这些序列化方式的对比和配置(只在服务端配置HSFApiProviderBean)如下表所示:
API形式配置HSF服务
HSFApiProviderBean hsfApiProviderBean = new HSFApiProviderBean();
hsfApiProviderBean.setPreferSerializeType("hessian2");
Spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean class="com.taobao.hsf.app.spring.util.HSFSpringProviderBean" init-method="init">
<!--[设置] 发布服务的接口-->
<property name="serviceInterface" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
<!--[设置] 服务的实现对象 target必须配置[ref],为需要发布为HSF服务的spring bean id-->
<property name="target" ref="引用的BeanId"/>
<!--[设置] 服务的版本-->
<property name="serviceVersion" value="1.0.0"/>
<!--[设置] 服务的归组-->
<property name="serviceGroup" value="HSF"/>
<!--[设置] 服务的响应时间-->
<property name="clientTimeout" value="3000"/>
<!--[设置] 服务传输业务对象时的序列化类型-->
<property name="preferSerializeType" value="hessian2"/>
</bean>
超时配置
有关网络调用的请求,都需要配置超时,HSF的默认超时时间是3000ms。客户端和服务端都可以设置超时,默认优先采用客户端的配置,如果客户端没有配置,使用服务端的超时配置。 在服务端设置超时时,需要考虑到业务本身的执行耗时,加上序列化和网络通讯的时间。所以推荐服务端给每个服务都配置个默认的时间。当然客户端也可以根据自己的业务场景配置 超时时间,比如一些前端应用,需要用户快速看到结果,可以把超时时间设置小一些。
配置的作用范围、作用域,按照优先级由高到低如下表所示:
客户端配置优先于服务端,方法优先于接口
客户端超时配置
API形式配置HSF服务
配置HSFApiConsumerBean的clientTimeout属性,单位是ms,我们把接口的超时配置为1000ms,方法queryOrder配置为100ms,代码如下:
HSFApiConsumerBean consumerBean = new HSFApiConsumerBean();
//接口级别超时配置
consumerBean.setClientTimeout(1000);
//xxx
MethodSpecial methodSpecial = new MethodSpecial();
methodSpecial.setMethodName("queryOrder");
//方法级别超时配置,优先于接口超时配置
methodSpecial.setClientTimeout(100);
consumerBean.setMethodSpecials(new MethodSpecial[]{methodSpecial});
spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean id="CallHelloWorld" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean">
...
<property name="clientTimeout" value="1000" />
<property name="methodSpecials">
<list>
<bean class="com.taobao.hsf.model.metadata.MethodSpecial">
<property name="methodName" value="queryOrder" />
<property name="clientTimeout" value="100" />
</bean>
</list>
</property>
...
</bean>
注解配置
SpringBoot广泛使用的今天,使用注解装配SpringBean也成为一种选择,HSF也支持使用注解进行配置,用来订阅服务。
首先是在项目中增加依赖starter。
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>
通常一个HSF Consumer需要在多个地方使用,但并不需要在每次使用的地方都用@HSFConsumer来标记。只需要写一个统一个Config类,然后在其它需要使用的地方,直接@Autowired注入即可上述例子中的API配置等同于如下注解配置:
@HSFConsumer(clientTimeout = 1000, methodSpecials = @HSFConsumer.ConsumerMethodSpecial(methodName = "queryOrder", clientTimeout = "100"))
private OderService orderService;
客户端全局接口超时配置
- 在启动参数中添加-DdefaultHsfClientTimeout=100
- 在代码中添加System.setProperty(“defaultHsfClientTimeout”, “100”)
服务端方法超时配置
API配置HSF服务
配置HSFApiProviderBean的clientTimeout属性,单位是ms,代码如下:
HSFApiProviderBean providerBean = new HSFApiProviderBean();
//接口级别超时配置
providerBean.setClientTimeout(1000);
//xxx
MethodSpecial methodSpecial = new MethodSpecial();
methodSpecial.setMethodName("queryOrder");
//方法级别超时配置,优先于接口超时配置
methodSpecial.setClientTimeout(100);
providerBean.setMethodSpecials(new MethodSpecial[]{methodSpecial});
Spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean class="com.taobao.hsf.app.spring.util.HSFSpringProviderBean" init-method="init">
...
<property name="clientTimeout" value="1000" />
<property name="methodSpecials">
<list>
<bean class="com.taobao.hsf.model.metadata.MethodSpecial">
<property name="methodName" value="queryOrder" />
<property name="clientTimeout" value="2000" />
</bean>
</list>
</property>
...
</bean>
注解配置HSF服务
注入即可上述例子中的API配置等同于如下注解配置:
@HSFProvider(serviceInterface = OrderService.class, clientTimeout = 3000)
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDAO orderDAO;
@Override
public OrderModel queryOrder(Long id) {
return orderDAO.queryOrder(id);
}
}
服务端线程池配置
HSF服务端线程池主要分为IO线程和业务线程,其中IO线程模型就是netty reactor网络模型中使用的。我们主要讨论业务线程池的配置。业务线程池分为默认业务线程池和服务线程池,其中服务线程池是从默认线程池中分割出来的,如下图所示:
默认线程池配置
服务端线程池是用来执行业务逻辑的线程池,线程池默认的core size是50,max size是720, keepAliveTime 500s。队列使用的是SynchronousQueue,没有缓存队列,不会堆积用户请求。 当服务端线程池所有线程(720)都在处理请求时,对于新的请求,会立即拒绝,返回Thread pool is full异常。可以使用下面VM参数(-D参数)进行配置。
线程池最小配置: -Dhsf.server.min.poolsize
线程池最大的配置: -Dhsf.server.max.poolsize
线程收敛的存活时间: -Dhsf.server.thread.keepalive
服务线程池配置
对于一些慢服务、并发高,可以为其单独配置线程池,以免占用过多的业务线程,影响应用的其他服务的调用。
API形式配置HSF服务
HSFApiProviderBean hsfApiProviderBean = new HSFApiProviderBean();
//...
hsfApiProviderBean.setCorePoolSize("50");
hsfApiProviderBean.setMaxPoolSize("200");
Spring配置HSF服务
Spring框架是在应用中广泛使用的组件,如果不想通过API的形式配置HSF服务,可以使用Spring XML的形式进行配置,上述例子中的API配置等同于如下XML配置:
<bean class="com.taobao.hsf.app.spring.util.HSFSpringProviderBean" init-method="init">
<!--[设置] 发布服务的接口-->
<property name="serviceInterface" value="com.alibaba.middleware.hsf.guide.api.service.OrderService"/>
<property name="corePoolSize" value="50" />
<property name="maxPoolSize" value="200" />
</bean>
注解配置HSF服务
SpringBoot广泛使用的今天,使用注解装配SpringBean也成为一种选择,HSF也支持使用注解进行配置,用来发布服务。
首先是在项目中增加依赖starter。
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>
然后将@HSFProvider配置到实现的类型上,上述例子中的API配置等同于如下注解配置:
@HSFProvider(serviceInterface = OrderService.class, corePoolSize = 50, maxPoolSize = 200)
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDAO orderDAO;
@Override
public OrderModel queryOrder(Long id) {
return orderDAO.queryOrder(id);
}
}
路由规则
HSF 路由规则作用在 服务的消费者,在消费端调用服务前的选址阶段,对地址选择逻辑做干预,从而实现流量划分、读写分离等目标。
注意:
泛化调用不支持路由规则,因为客户端没有对象的接口类
基本原理
HSF 路由规则存储在 Diamond 持久化配置中心 中,通过被客户端订阅,可以动态的更新规则内容,并作用在消费端发起 HSF 服务调用的选址阶段。其中:
规则的 DataId: 服务名.RULES(服务名 = 接口名:版本号)
规则的 GroupId: 服务的组别
规则内容:采用 Groovy 脚本编写的路由规则
规则管理
HSF 路由规则可以在 HSFOPS 上管理,或者直接在 Diamond 控制台按照规范的 DataId 和 GroupId 操作 Diamond 配置。一般,我们推荐在 HSFOPS 上进行路由规则管理。
进入路由规则管理页面:
日常环境:http://hsf.alibaba.net/hsfops/governance/routingRule.htm?envType=daily
预发环境:http://hsf.alibaba-inc.com/hsfops/governance/routingRule.htm?envType=pre
线上环境:http://hsf.alibaba-inc.com/hsfops/governance/routingRule.htm?envType=online
点击页面上的 “新建路由规则” 按钮,按照页面提示输入 DataId(服务名),GroupId(服务的组别) 以及路由规则内容后,点击 “发布” 按钮即可。
规则语法
HSF 路由规则采用 Groovy 脚本编写,并支持三个级别的路由:接口路由,方法路由,参数路由。
接口路由
接口路由定义消费端调用 某个接口 时的路由方式。
例如,针对服务 A 做接口级别路由,其中提供 A 服务的机器有:192.168.1.2、192.168.1.3,希望调服务 A 的消费者,全部请求 192.168.1.3,那么可以创建下述路由规则:
Groovy_v200907@package hqm.test.groovy
public class RoutingRule{
// define address list
Map<String, List<String>> routingRuleMap(){
return [
"G1":["192.168.1.3:*"]
]
}
// define routing rule at interface level
String interfaceRoutingRule(){
return "G1";
}
// turn off regular match of address defined in routingRuleMap()
boolean isIpRegexOn(){
return false;
}
}
方法路由
方法路由定义消费端调用 某个接口的某些方法 时的路由方式。
例如,针对服务 A 中的方法做读写分离(假设读方法以 get 开头,写方法以 put 开头),其中提供 A 服务的机器有:192.168.1.2、192.168.1.3,希望读请求都由 192.168.1.2 处理,写请求都由 192.168.1.3 处理,那么可以创建下述路由规则:
Groovy_v200907@package hqm.test.groovy
public class RoutingRule{
// define address list
Map<String, List<String>> routingRuleMap(){
return [
"read_method_address":["192.168.1.2:*"],
"write_method_address":["192.168.1.3:*"]
]
}
// define routing rule at method level
String mathodRoutingRule(String methodName, String[] paramTypeStrs){
if(methodName.matches("get*"))
return "read_method_address";
else if (methodName.matches("put*"))
return "write_method_address";
else
return null; // it will go on to process interfaceRoutingRule() if it is configured
}
// turn off regular match of address defined in routingRuleMap()
boolean isIpRegexOn(){
return false;
}
}
注意:
方法路由的名称确实为 mathodRoutingRule,这是一个拼写错误,但由于历史原因只能这样用。
参数路由
参数路由根据消费端调用 某个服务的某些方法 时的 实际入参值 进行路由。
例如,针对服务 A 做参数路由,其中提供服务 A 的机器有:192.168.1.2,192.168.1.3,当参数传入整数大于 10 时,路由到 192.168.1.2;小于 10 时到 192.168.1.3 上,那么可以创建下述路由规则:
Groovy_v200907@package hqm.test.groovy
public class RoutingRule{
// define address list
Map<String, List<String>> routingRuleMap(){
return [
"b_seller_address":["192.168.1.2:*"],
"a_seller_address":["192.168.1.3:*"]
]
}
// define routing rule at argument level
Object argsRoutingRule(String methodName, String[] paramTypeStrs){
if (methodName.startsWith("routeBySeller")) {
return {
Object[] args->
if(args[0] > 10)
return "b_seller_address";
else
return "a_seller_address";
}
} else {
return null; // it will go on to process mathodRoutingRule(), interfaceRoutingRule() if they are configured
}
}
// turn off regular match of address defined in routingRuleMap()
boolean isIpRegexOn(){
return false;
}
}
其他
HSF 路由规则的内容还包含一些细节配置,如地址的正则匹配、推空保护等,可参考 路由规则 WIKI 了解更多内容。
归组规则
HSF 归组规则作用在 服务的提供者,在 HSF 服务的发布期生效,将指定服务的组别修改为特定的值。这样,不同分组中的 HSF 服务实例就组成了以 group 为单位的集群,实现服务端集群的划分,从而仅针对部分客户端工作。
例如:
carts 的消费者非常多,有一些是核心应用,有一些是不那么重要的普通应用。核心应用和非核心应用对接口的响应、可靠性要求不尽相同,因此对机器资源的要求也不同。那么 carts 就可以通过归组规则,将服务端的组别划分为 CORE 和 NORMAL,以组别划分集群,对机器资源做合理分配。
完成组别的划分后,carts 就可以让核心应用去消费组别为 CORE 的服务;普通应用去消费组别为 NORMAL 的服务。
注意:操作归组规则的 风险很高,除非你十分清楚你的归组规则带来的效果,否则请不要轻易在线上配置归组规则,避免导致因 组别配置错误 造成消费者 找不到服务提供方地址 的错误。
基本原理
HSF 归组规则存储在 Diamond 持久化配置中心 中,通过被服务提供方订阅,可以动态的更新规则内容,并修改服务的组别。其中:
规则的 DataId: 应用名. GROUPINGRULE(由 JVM 参数 -Dproject.name 设置)
规则的 GroupId: HSF
规则内容:采用 XML 格式编写的归组规则
规则管理
HSF 归组规则可以在 HSFOPS 上管理,或者直接在 Diamond 控制台按照规范的 DataId 和 GroupId 操作 Diamond 配置。一般,我们推荐在 HSFOPS 上进行归组规则管理。
进入归组规则管理页面:
日常环境:http://hsf.alibaba.net/hsfops/governance/groupingRule.htm?envType=daily
预发环境:http://hsf.alibaba-inc.com/hsfops/governance/groupingRule.htm?envType=pre
线上环境:http://hsf.alibaba-inc.com/hsfops/governance/groupingRule.htm?envType=online
点击页面上的 “新建归组规则” 按钮,按照页面提示输入 DataId(应用名),GroupId(HSF) 以及归组规则内容后,点击 “发布” 按钮即可。
规则语法
HSF 归组规则通过 XML 指定需要修改组别的服务。例如,对于应用 A:
A 应用部署的机器:
192.168.1.2
192.168.1.3
192.168.1.4
A 应用提供的服务,组别都是 HSF:
com.alibaba.middleware.hsf.guide.api.service.OrderService:1.0.0
com.alibaba.middleware.hsf.guide.api.service.UserService:1.0.0
希望 192.168.1.2 机器不处理组别为 HSF 的流量,那么可以创建下述归组规则,将 192.168.1.2 发布的服务的组别,动态的改为 NOHSF:
groupingRule@
<rules>
<rule>
<services>
<service>com.alibaba.middleware.hsf.guide.api.service.OrderService:1.0.0</service>
<service>com.alibaba.middleware.hsf.guide.api.service.UserService:1.0.0</service>
</services>
<ips>
<ip>192.168.1.2</ip>
</ips>
<group>NOHSF</group>
</rule>
</rules>
说明:
ip 配置中支持 * 和 ? 通配符
归组规则可以包含多个 rule 节点,每个节点定义了要修改组别的机器(ips)和服务名(services),以及需要改为的组别(group)
归组规则的配置具有较高的风险,详细的注意事项请参考 归组规则 WIKI
同机房规则
HSF 同机房规则作用在 服务的消费者,在消费端调用服务前的选址阶段,根据机房网段信息优先选择同一个机房的服务提供方发起调用,从而减少跨机房流量的产生。
注意:
- 同机房规则默认是 关闭 的。
- 同机房规则并 不是 按照 真实机房 做划分的,而是根据 网段做虚机房 地址选取,因此线上环境仅建议开启 上海中心 环境的同机房规则。
基本原理
HSF 同机房规则存储在 Diamond 持久化配置中心 中,通过被客户端订阅,可以动态的更新规则内容,并作用在消费端发起 HSF 服务调用的选址阶段。其中:
规则的 DataId: 服务名.RULES(服务名 = 接口名:版本号)
规则的 GroupId: 服务的组别
规则内容:采用 XML 编写的同机房规则
规则管理
HSF 同机房规则可以在 HSFOPS 上管理,或者直接在 Diamond 控制台按照规范的 DataId 和 GroupId 操作 Diamond 配置。一般,我们推荐在 HSFOPS 上进行同机房规则管理。
进入同机房规则管理页面:
日常环境:http://hsf.alibaba.net/hsfops/governance/flowControlRule.htm?envType=daily
预发环境:http://hsf.alibaba-inc.com/hsfops/governance/flowControlRule.htm?envType=pre
线上环境:http://hsf.alibaba-inc.com/hsfops/governance/flowControlRule.htm?envType=online
点击页面上的 “新建同机房规则” 按钮,按照页面提示输入 DataId(服务名),GroupId(服务的组别) 以及同机房规则内容后,点击 “发布” 按钮即可。
需要说明的是,在实现原理上 同机房规则与路由规则使用了相同的 diamond 配置。如果在 HSFOPS 中配置同机房规则,则无需关注同配置中的路由规则,仅录入同机房规则即可。然而,如果需要在 Diamond 控制台配置同机房规则,则需要注意是否已经配置了路由规则,若已配置路由规则,则只需要在路由规则中 append 同机房规则内容即可。
规则语法
同机房规则的语法很简单,如果需要开启同机房规则,只需创建下述同机房规则即可;如需关闭同机房规则,只需要将 localPreferredSwitch 的值改为 off 即可。
flowControl@
<flowControl>
<localPreferredSwitch>on</localPreferredSwitch>
<threshold>0.2</threshold>
<exclusions></exclusions>
</flowControl>
规则属性:
- localPreferredSwitch: on|off
- threshold:同机房规则的生效阈值,float 值。
生效阀值的计算方法:
服务可用比例 = 消费者所在机房可用机器数量 / 服务提供方在所有机房的机器总量(“所有机房” 是指一个单元中的所有机房,比如上海中心的所有机房是 eu13 + et2 + eu6) -
- 当服务可用比例 >= threshold 时,启用同机房优先策略
-
- 当服务可用比例 < threshold 时,关闭同机房优先策略,消费者仍然采取随机调用的方式选址
- exclusions:如果期望该规则只对一部分机器生效,可以使用这一属性配置需要排除的IP。例如:172.24.*,表示该同机房规则不会应用于所有以 172.24. 开头的消费者 IP。
服务鉴权
HSF 服务鉴权作用在 服务的提供者,当 HSF 服务提供者接收到消费者调用请求时,首先对调用者进行身份和权限验证,验证通过后才会进行业务逻辑处理。
注意:
HSF 调用的服务鉴权 默认是关闭的。对于一些安全级别高的应用接口(如用户实名信息、报表数据等)需要配置服务鉴权,从而保证只有限定的应用可以调用。
基本原理
HSF 服务鉴权基于中间件 访问控制产品 Dauth 实现,通过为应用分配全局唯一的 AccessKey(身份ID,简称 AK)和 SecretKey(对称加密秘钥,简称 SK)实现签名和鉴权的基础。
服务消费方
HSF 服务鉴权对于消费端透明,在消费端应用在启动时,dauth-client 会读取消费端应用的 AK/SK 并通过 API 提供给 HSF 使用。在消费端发起调用时,HSF 会将目标调用服务和方法签名使用 SK 加密生成 “SK加密签名”,并同方法签名、AK 等信息一起传输到服务端,供服务端鉴权使用。
服务提供方
HSF 服务鉴权一般由服务端应用要求开启,需要服务端做以下配置:
在 HSFOPS 进行授权管理,指定可以调用服务的消费端应用
服务端应用启动时,添加 JVM 参数 -DneedAuth=true 开启鉴权逻辑
在完成以上配置并启动服务端应用后,dauth-client 会获取应用的 AK/SK 以及授权信息,并通过 API 提供给 HSF 使用。在消费端请求到达时,HSF 会将当针对当前请求中的 AppName、SK加密签名等信息做处理,验证请求的合法性。
整体流程如下图所示。
操作指南
HSF 服务鉴权的操作由 服务提供方 的应用负责人执行,具体需要包含以下2个步骤:
环境准备
应用授权管理
环境准备
- 确认 Sar 包版本
HSF 服务鉴权对应用使用的 Sar 包版本有要求,请服务提供方升级 Sar 包至 2017-10-stable 及其之后版本。为保证文档的时效性,推荐直接使用当前集团的 推荐 Sar 包版本。
- 添加 JVM 参数
HSF 服务提供方默认不开启服务鉴权功能,如需开启服务鉴权,需要通过添加 JVM 参数 -DneedAuth=true 来声明。
- 确认 AK/SK 读取成功
HSF 服务鉴权依赖于 Dauth 读取的 AK/SK,因此在首次使用时,请检查应用启动后是否可以成功的读取到 AK/SK。
应用启动后,请检查 /home/admin/logs/spas/spas_sdk.log 日志,如果成功输出 SPAS authority initialized,则说明 AK/SK 读取成功,否则请参考 .spas_key 文件路径 尝试添加处理。
应用授权管理
可以使用 HSFOPS 进行应用级别的授权管理。进入 HSFOPS 服务鉴权页面后,即可在查看到当前登录用户所管理的应用列表。
日常环境:http://hsf.alibaba.net/hsfops/myapps/serviceAuth.htm?envType=daily
预发环境:http://hsf.alibaba-inc.com/hsfops/myapps/serviceAuth.htm?envType=pre
线上环境:http://hsf.alibaba-inc.com/hsfops/myapps/serviceAuth.htm?envType=online
- 鉴权选项
点击页面中相关应用的 “鉴权选项” 按钮,可以设置当前应用 作为服务提供方 是否需要开启服务鉴权,以及鉴权属性。
- 授权管理
当应用作为服务提供方,开启了服务鉴权后,需要通过 “授权管理” 功能设置哪些消费方应用可以访问,哪些应用不可以访问。
点击页面中相关应用的 “授权管理” 按钮,在弹出的对话框中,结合 “鉴权选项” 中的信息,录入需要授权的消费方应用名,或需要禁止访问的消费方应用名。
相关资料
限于篇幅,本文并未介绍与服务级别、方法级别的授权管理方式,如需使用耕细粒度的 HSF 服务鉴权,请参考 ATA 文章 《HSF访问控制介绍》。
常见问题
在一次 HSF RPC 调用过程中,涉及选址、网络I/O、序列化等诸多方面,因此可能发生异常的点也比较分散。这里列出了一些最容易出现的问题,如:
- 找不到服务地址(HSFServiceAddressNotFoundException)
- 服务调用超时(HSFTimeOutException)
- 线程池满(HSF thread pool is full)
- 序列化错误(decode error on client side)
- 未找到需要调用的方法(NoSuchMethodException)
- …
如果在使用中,还遇到了其他问题,可以在 HSF FAQ 中根据异常关键字进行搜索,自助获取解决方案。
常用链接
- WIKI:http://gitlab.alibaba-inc.com/middleware/hsf2-0/wikis/home
- 用户手册:http://hsf-doc.taobao.net/index.html
- 版本信息(Release Notes):http://gitlab.alibaba-inc.com/middleware/hsf2-0/wikis/release-note
- 实现原理:http://site.alibaba.net/middleware-container/hsf-guide/index.html
- FAQ:http://gitlab.alibaba-inc.com/middleware-container/hsf-guide/issues
- 示例工程:http://gitlab.alibaba-inc.com/middleware-container/hsf-guide