高速服务框架HSF的基本原理(上):https://developer.aliyun.com/article/1480831
▐ 泛化调用
相较于需要依赖业务模块API jar包的正常调用方式,泛化调用不需要依赖二方包,使用其特定的GenericService
接口,传入需要调用的方法名、方法签名和参数值进行调用服务。泛化调用适用于一些网关应用(没办法依赖所有服务的二方包),其中HSF-OPS服务测试也是依赖泛化调用功能的。
API配置客户端泛化调用:
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启动参数和代码的方式进行配置。
▐ 路由规则
HSF路由规则保存在Diamond持久化配置中心中。作用在消费者发起HSF服务调用的选址阶段,通过被客户端订阅的方式动态的更新规则内容。
规则以服务名.RULES作为DataId、服务的组别作为GroupId,采用Groovy脚本编写具体的规则内容。支持接口路由、方法路由和参数路由这三种路由方式。
一般推荐在HSF-OPS中进行路由规则的管理与配置。
一个简单的接口路由示例如下图所示:
▐ 归组规则
与路由规则一样,归组规则也保存在Diamond中。在HSF服务的发布期生效,将指定服务的组别修改为特定的值。这样,不同分组中的HSF服务实例就组成了以group为单位的集群,实现服务端集群的划分,从而仅针对部分客户端工作。
规则以应用名.GROUPINGRULE(JVM参数:-Dproject.name)作为DataId、「HSF」作为GroupId,采用XML格式编写具体的规则内容。
注意:在线上环境配置归组规则前要明确配置成功后产生的效果,避免出现消费者找不到服务提供方地址的问题。
一个简单的归组规则配置如下图所示:
▐ 同机房规则
同机房规则保存在Diamond中。作用在消费者发起HSF服务调用的选址阶段,根据机房网段信息优先选择同一个机房的服务提供方发起调用,从而减少跨机房流量的产生。
规则以服务名.RULES作为DataId、服务的组别作为GroupId,采用XML格式编写具体的规则内容。
注意:
- 同机房规则默认是关闭的
- 同机房规则是根据网段作为虚拟机房进行地址选取的
- 同机房规则与路由规则使用了相同的Diamond配置,如果已经配置了路由规则,在原有路由规则的基础上append即可
同机房规则配置如下图所示:
设计模式
设计模式是在软件设计中反复出现的一些问题的解决方案的经验总结。它们是一种被广泛接受的最佳实践,可用于解决特定类型的问题或完成特定类型的任务。
六大设计原则为设计模式提供理论支持,它们分别是:
- 单一职责原则:应该有且仅有一个原因引起类的变更
- 里氏替换原则:所有引用父类的地方必须能透明地使用其子类的对象
- 依赖倒置原则:面向接口编程,依赖抽象而非细节
- 接口隔离原则:接口尽量细化,接口中的方法尽可能的少
- 迪米特法则:一个对象应该对其他对象有最少的了解
- 开闭原则:对拓展开放,对修改关闭
HSF中使用了哪些经典的设计模式呢?
▐ 责任链模式
定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
介绍:
想要在HSF流程发生的过程中对其进行拓展,需要实现ProtocolInterceptor
,它的继承树如下图所示。
ProtocolInterceptor
实现了Protocol
接口,里面只增加了一个方法void setProtocol(Protocol protocol);
,其目的就是形成一个Protocol
链条,这样就能将ProtocolInterceptor
的扩展点拼装到流程链条上。HSF提供了AbstractDelegateProtocolInterceptor
,可以通过继承该抽象类,重写List export();
等方法,轻松的实现流程的扩展。
链的构建为了链能正常运行起来,首先需要构建链。HSF是通过以下方式拼装流程这条链的:
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);
团队介绍
我们来自淘天集团的营销与平台策略技术。我们支撑大促 (双11、618 等)和日销业务;同时我们也直面竞对,深入参与淘宝好价、百亿补贴、聚划算等日销业务的价格心智打造。秉承“简单、开放、自驱”的技术文化,在使命与责任中互相成就,共同成长,践行让业务先赢,技术氛围浓郁。