HSF学习总结
HSF其实是一个RPC框架,RPC是Remote Procedure Call,就是远程服务调用.
这个功能为什么要写个框架而不是十几行代码呢,因为在分布式场景下并不是这种点对点通讯的模式。
rpc要素:where how
地址:注册中心ConfigServer, 这个中心用来管理整个分布式集群里所有的服务对应服务提供者ip的对应关系,
这玩意是不能写在配置文件里的,在分布式场景下扩容、缩容机器ip的变化实在是太正常了,
必须要有一个注册中心来管理地址;hsf的注册中心是ConfigServer;以前开源的rpc框架dubbo也有自己的 注册中心,
像twitter的fonango的注册中心是zk
接口:基于接口编程,不像http请求返回json数据或者json rpc协议或者webservice那样都有一个共同的问题,
就是对开发人员不友好,换句话说就是需要侵入业务流程,不得不让上层的调用为rpc调用写一些复杂丑陋的代码
configServer可以看成一个具有聚合功能的内存数据库, 服务提供者将自己的地址和提供的服务注册到ConfigServer,服务消费者要调用的时候会向ConfigServer查询,其实是订阅这个服务,ConfigServer知道它要订阅之后会做一个聚合的操作,聚合之前对应关系是ip对应服务列表,聚合之后是服务对应ip列表, 聚合之后会向服务订阅者推送这个数据,会告诉订阅者提供这个服务的地址有哪些,后面就没有ConfigServer的事情了,服务消费者拿到这个地址列表之后会随机选择一台机器发起调用,后面rpc整个流程都是点对点,不牵涉到任何环境的变更。
没有地址列表,谈rpc是没有意义的。 地址列表永远是rpc这个场景下最最重要的东西;
ConfigServer是推送模式,不光在消费者第一次订阅时会推送, 后面每当服务提供者变化时都会推送;
这种推送模型有哪些好的和不好的地方 ?
真正的环境只有两套,一个线上环境和线下环境,它们之间的网络是隔离的,但预发环境和线上环境是通的,
但怎么保证预发环境的detail不会调用线上的ic呢?其实靠的就是ConfigServer,预发的ConfigServer和线上的ConfigServer是不同的,预发的detail连的是预发的ConfigServer,预发的ConfigServer肯定没有线上ic的地址,所有永远不会调用到线上的ic,集团的这些环境都是通过中间件的ConfigServer这个产品进行区分的,就是线下环境会有性能环境、二套环境啊,ConfigServer不同罢了,里面的ip列表都不一样。
HSF中服务名约等于接口名
任何HSF编程,都依赖于接口
服务提供接口实现
客户端基于接口编程,内部HSF代理去进行远程通信
客户端基于接口编程,当客户端执行到这个接口的时候,客户端本地是没有任何实现的,hsf会在内部作代理,
当执行到这个接口的某个方法的时候,hsf会远程发送一个tcp的请求,发送给这个服务端,服务端在本地执行这个接口的实现,拿到响应之后发送回给这个客户端,hsf会把这个结果当作这个接口的返回值去给上层的应用;
隔离容器–Pandora(taobao-hsf.sar)
1.所有中间件(hsf/tddl/notify)都在Pandora内部
2.提供类隔离机制
为什么把他们打在一起,因为Pandora提供了一种类隔离的机制,这样的好处是当应用特别复杂的时候,会依赖特别多的jar包,而这些jar包可能又会依赖不同版本的中间件,排除起来会非常的痛苦,当classpath目录下有两个packagename和classname都相同的类的时候,jvm会随机加载一个,这个时候如果不把多余的依赖排除干净的话,是无法保证在运行时期所使用的hsf到底是哪个版本,如果使用sar包的话,惟一确定的一点就是hsf就是sar包里的版本,它只会用sar包里的版本,还有一点,比如说hsf会依赖google的guava库,当你的应用也要用guava库,它们之间是相互隔离的,这个隔离就是通过Pandora来做到的。
Web容器–汤姆猫,J老板
1.HSF和这些玩意其实没什么关系
hsf-standalone可以只用一个main方法就可以启动hsf
配置文件配置好后,如果是在web容器中使用hsf的,先让web容器跑起来,web容器会初始化spring,
spring会在beanFactory初始化所有bean的时候帮你把hsf发布出去,服务就提供出去了,就什么都不用管了。
接口名和版本号作为一个二元组区分一个hsf服务
深入性的原理:
隔离容器Pandora
HSF自身架构
软负载策略
异步调用
线程池
sar是什么(Pandora就是taobao-hsf.sar,所有的中间件都在里面,不仅是hsf, 只是为了兼容才没改名字)
干什么:加载的隔离、模块化
加载隔离的作用:
- 针对三方包来说,比如之前提到的hsf会依赖google的guava库,如果应用也要依赖guava库,它们之前是隔离的,
这是通过hsf进入Pandora之后,Pandora会为它划分出一块单独的classpath,其实是通过一个单独的ClassLoader来实现的,这样的话hsf使用的三方包就不会和应用使用的三方包冲突 - 针对二方包来说,比如说应用要依赖hsf编程,而每个二方包的jar包会依赖的hsf的jar包,而依赖的版本不一样,
由于jvm的随机加载性,可能会出问题,但是如果把tao-hsf.sar放在WEB目录的deploy目录下,
它会提供一种加载隔离的机制,就不用担心依赖的hsf版本的不同而造成运行时加载的问题了。
模块化:一般不用关心,pandora-framework来实现应用拆分成不同的组件,即组件化。
怎么玩的:导出、导入
怎么实现加载的逻辑的呢,怎么保证应用一定会用pandora内部的hsf,而不是用当前classpath下依赖的hsf的jar包呢
这就是导出的概念:pandora是生存在web容器中的,比如说tomcat,tomcat针对每一个war包,它有它自己的war-classloader这个东西,
这个东西会持有pandora的calssloader,当tomcat要加载类的时候,它会优先从pandora的classloader中加载类,
那pandora的classloader很奇怪也会持有tomcat的classloader,这样做之后,加载的流程就变为:
如果pandora中的hsf这个组件把hsfSpringProviderBean这个类选择导出的话,那么当tomcat要加载这个类,
也就是应用要加载hsfSpringProviderBean的时候,它会优先从pandora的classloader中去寻找,
那么pandora发现这个类是被hsf导出的类,它就会把它自己持有的hsf返回给tomcat,tomcat返回给上层的应用。
这样就保证了在tomcat的deploy目录下放置pandora的话,使用的hsf版本一定是pandora中的hsf版本.
导入:比如说hsf要使用fastjson,大部分情况下hsf是不会使用通用的三方库的(fastjson/commons-lang),
但如果应用通hsf传输的DO中包含这些东西,比如应用返回的DO是一个JSONObject的话,那么hsf就要选择使用导入这个功能。回到刚刚那个逻辑:tomcat持有pandora的classloader,然后pandora持有tomcat的classloader, hsf是被pandora去加载的,那么hsf在尝试加载jsonobject这个类进行反序列化的时候,它会在pandora的classloader中优先寻找,但是pandora的classloader中是不包含这个玩意的,因为hsf自身不依赖它,那么pandora就会拿它持有的那个tomcat的classloader去load这个class,就是jsonobject,这个就相当于tomcat对于这种导入的类会使用应用自身cp那些类,这就是导入的逻辑, 其实导入真正的使用场景是对于spring这个东西,因为hsf要配置spring才能使用,
但是hsf自身也是依赖spring编程的,而spring-context如果大家依赖的不一样的话,会造成多份spring-context,那这样的话,应用拿不到hsf的类,hsf拿不到应用的类,所有hsf必须和应用用同一份spring-context,所以hsf选择导入spring。
这就介绍了pandora是怎么实现加载隔离的,就是两个概念,一个导入,一个导出;
有什么好处:对应用无侵入,平滑升级
HSF自身架构
hsf分成三层:
第一个是Proxy层,主要处理hsf和应用交互的一些逻辑,比如做接口的代理,执行业务的方法
第二个是Remoting层,主要处理网络层中的应用层数据,它处理的是rpc协议
第三个是Processer层,主要处理hsf自身的一些逻辑,比如说序列化反序列化,异常处理等
处理完所有的逻辑之后,hsf是基于netty编程的,netty负责处理io模块来进行通信,这样的话,
ConfigServer/Diamond与Proxy层进行交互之后,就组成了整个的consumer/provider的调用过程,
软负载策略–hsf的选址逻辑
什么是软负载,说白了就是导流量, 怎么导流量呢?
第一种方式就是归组,
软负载策略–归组:归组规则是可以通过Diamond配置动态推送的,可以动态的把机器配置成A组别还是B组别,也有利于动态扩容
软负载策略–路由:路由规则–通过配置将客户端流量导到某些机器上。如何配置:Diamond规则,内容是Groovy脚本
与归组规则区别:粒度到方法参数;有保护机制(有算空保护,即当算出来的ip地址是空的时候会随机地全量调用)
归组规则的粒度只能到某个服务,不能到某个方法。
简单的来看,都是通过配置将流量导致某些机器上, 那什么时候用归组规则,什么时候用路由规则。
软负载策略–同机房优先:在阀值范围内,客户端会优先调用同机房的客户端
如何筛选同机房ip:
在同一个虚拟机房(HSF逻辑)
ip前两段相同
阀值的作用:
同机房机器数/总机器数>阀值,会优先调用同机房的机器
同机房机器挂掉->(同机房机器比例<阀值)->随机调用
权重规则–对于provider集群中的某些机器,增加被调用到的概率
未配置权重:每台机器被访问的概率相同
总机器数为m,配置某ip的权重为n后,这个ip被访问的概率为:(1+n)/(m+n),
做法就是在m个ip的列表中在塞进去n个相同的ip即可
异步调用有两种方式Future调用和Callback调用,作用都是差不多的,都是解放真正的调用线程。
个人认为,大部分的异步场景是可以通过消息来解决的。 消息是rpc的一种
线程池–HSF的业务线程池
HSF为所有Provider开一个ThreadPool
支持不同的服务配置单独的线程池
定制化的ThreadPoolExecutor
任务队列为SynchronousQueue(不是LinkedBlockingQueue,可以快速递交,快速失败)
线程复用时间为5min
拒绝策略触发时会打印Jstack
常见问题:
找不到地址
应用启动后没查到自己发布的服务
超时
序列化错误
找不到方法
线程池满
启动出错永远只看第一个,后面一般都是web context挂掉造成的