springcloud组件原理总结

SpringCloud组件原理总结

文章目录


参考资料:1 深入理解SpringCloud与微服务架构

​ 2 Hystrix原理与实战

1 简介

SpringBoot是由Pivotal团队提供的全新的Web框架,它的特点是简化了开发和部署的过程,简化了spring复杂的配置和依赖管理,通过起步依赖和内置Servlet容器能够使开发者快速搭建一个Web工程。

SpringCloud的就是通过提供一系列的开发组件和框架,帮助开发者快速搭建一个分布式的微服务系统。SpringCloud提供了开发分布式微服务系统的一些常用组件,例如服务注册和发现、配置中心、熔断器、智能路由、微代理、控制总线、全局锁、分布式会话等。

2 组件原理

2.1 Eureka

Eureka是一个用于服务发现与注册的组件

2.1.1 Eureka的一些概念

1 Register——服务注册

当EurekaClient向EurekaServer注册时,EurekaClient提供自身的元数据,比如IP地址、端口、运行状况指标的Url、主页地址等信息。

2 Renew——服务续约

EurekaClient在默认情况下会每隔30秒发送一次心跳来进行服务续约。通过服务续约来告知EurekaServer该EurekaClient仍然可用,没有出现故障。

3 Fetch Registries——获取服务注册列表信息

EurekaClient 从EurekaServer获取服务注册表信息,并将其缓存在本地。EurekaClient会使用服务注册列表信息查找其它服务的信息,从而进行远程调用,该注册列表信息每隔30秒更新一次,如果返回列表信息不同,EurekaClient会自己处理这些信息。如果由于某种原因导致注册列表信息不能及时匹配,EurekaClient会重新获取整个注册表信息。EurekaClient和EurekaServer可以使用JSON和XML数据格式进行通信。在默认情况下EurekaClient使用JSON格式的方式来获取服务注册列表。

4 Canel——服务下线

EurekaClient在程序关闭时可以向EurekaServer发送下线请求。该下线请求不会自动完成,需要在程序关闭时调用如下代码:

DisCoveryManager.getInstance().shutdownComponent();

5 Eviction——服务剔除

在默认情况下,如果EurekaClient连续90秒没有发送服务续约(即心跳),EurekaServer会将该服务实例从服务注册列表删除,即服务剔除。

2.1.2 Register 源码分析

客户端:

EurekaClient在启动时会将自身的服务信息发送到EurekaServer,这个操作是由DiscoveryClient类中的register()实现的,DiscoveryClient类实现EurekaClient,EurekaClient继承lookupServer接口,DiscoveryClient是单例的。

而DiscoveryClient的register()是被InstanceinfoReplicator类(实现了Runnable接口)中的run()调用的,InstanceInfoReplicator类是在DiscoveryClient初始化过程中使用的。

DiscoverClient的初始化方法为initScheduledTasks(),方法中先判断配置属性是否获取服务列表,然后判断是否注册到Eureka属性,如果注册则初始化InstanceInfoReplicator。

服务端:

EurekaServer中,有一个EurekaBootStrap类,类中的initEurekaServerContext()方法中初始化两个类PeerAwareInstanceRegistryImpl和PeerEurekaNodes,

PeerAwareInstanceRegisterImpl中的register()方法提供了注册,并且将服务注册后的信息同步到其它的EurekaServer服务中,这个注册方法被作为一个API提供出去,EurekaClient注册时调用此API。

2.2 Ribbon

负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如Nginx。另外一种是将负载均衡逻辑以代码形式封装到服务消费者的客户端上,服务消费者客户端维护了一个服务提供者的信息列表,有了信息列表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。

Ribbon是Netflix公式开源的一个负载均衡组件,它属于上述的第二种方式,将负载均衡逻辑封装在客户端中,并运行在客户端的进程里。Ribbon是经过了云端测试的IPC库,可以很好的控制HTTP和TCP客户端的负载均衡行为。

2.2.1 LoadBalancerClient简介

负载均衡器的核心类为LoadBalancerClient,LoadBalancerClient可以获取负载均衡的服务提供者的实例信息。

使用方式如下,注入Bean

@Autowired
private LoadBalancerClient loadBalancer; 

ServiceInstance instance=loadBalancer.choose("serverName");//获取服务实例
String host = instance.getHost();//获取地址
String port = instance.getPort();//获取端口

LoadBanlanceClient的choose(“serverName”)可以轮流获取得到节点信息。注:获取节点是从EurekaClient获取的列表信息,也可以自定义,这里不多介绍。

2.2.2 源码解析Ribbon

首先,跟踪LoadBalanceClient的源码,它是一个接口类,继承了ServiceInstanceChooser接口,它的实现类为RobbinLoadBalanceClient。

RobbinLoadbalanceClient是一个非常重要的类,它实现了choose()方法,choose方法用于选择具体服务实例。该方法通过getServer()方法获取实例,最终交给ILoadBalance类去选择服务实例,ILoadBalance的最终实现类是DynamicServerListLoadBalancer。查看该类的源码,发现该类的构造中需要如下几个参数。

  • IClientConfig

    用于配置负载均衡的客户端

  • IRule

    负载均衡策略,默认的实现类有七种

    • BestAvailableRule:选择最小请求数
    • ClientConfigEnabledRoundRobinRule:轮询
    • RandomRule:随机
    • RetryRule:根据轮询的方式重试
    • WeightedResponseTimeRule:根据响应时间去分配一个weight,weight越低,被选择的可能性就越低。
    • ZoneAvoidanceRule:根据server的zone区域和可用性来轮询选择。
  • IPing

    用于向server发送“ping”,来判断该server是否有响应,从而判断该server是否可用。

    有五种实现

    • PingUrl:真实的去ping某个Url,判断其是否可用。
    • PingConstant:固定返回某服务是否可用,默认返回true,即可用。
    • NoOpPing:不去Ping,直接返回true,即可用。
    • DummyPing:直接返回true,并实现了initWithNiwsConfig方法。
    • NIWSDiscoveryPing:根据服务发现的状态判断,如果为IntanceStatus.UP,则可用,否则不可用。
  • ServerList

    定义获取所有server的注册列表信息的接口。

  • ServerListFilter

    定义了可根据配置去过滤或者特性动态地获取符合条件的server列表的方法。

2.3 Feign

Feign的源码实现过程如下。

  1. 首先通过@EnableFeignClients注解开启FeignClient的功能。只有这个注解存在,才会在程序启动时开启对@FeignClient注解的包扫描。
  2. 根据Feign的规则实现接口,并在接口上面加上@FeignClient注解。
  3. 程序启动后,会进行包扫描,扫描所有的@FeignClient的注解的类,并将这些信息注入IOC容器中。
  4. 当接口的方法被调用时,通过JDK的代理来生成具体的RequestRemplate模板对象。
  5. 根据RequestTemplate再生成Http请求的Request对象。
  6. Reuqst对象交给Client去处理,其中Client的网络请求框架可以是HttpURLConnection、OKHttp、HttpClient。
  7. 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。

2.4 Hystrix

2.4.1 什么是Hystrix

在分布式系统中,服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务会出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞。Hystrix是Netfkix公司开源的一个项目,它提供了熔断功能,能够阻止分布式系统中出现联动故障。Hystrix是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。

2.4.2 Hystrix的设计原则

总的来说,Hystrix的设计原则如下。

  • 防止单个服务的故障耗尽整个服务的Servlet容器(例如tomcat)的线程资源。
  • 快速失败机制,如果某个服务出现了故障,则调用该服务的请求快速失败,而不是线程等待。
  • 提供了回退(fallback)方案,请求发生故障时,提供设定好的回退方案。
  • 使用熔断机制,防止故障扩散到其它服务。
  • 提供熔断器的监控组件Hystrix Dashboard,可以实时监控熔断器的状态。

2.4.3 Hystrix的工作机制

首先,当服务的某个API的失败次数在一定时间内小于设定的阈值,熔断器处于关闭状态,该API接口正常提供服务。当API接口处理请求的失败次数大于设定的阈值时,Hystrix判定该接口出现了故障,打开熔断器,这时请求该API接口会执行快速失败的逻辑(即fallback回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态。处于打开状态的熔断器,一段时间后会处于半打开状态,并将一定数量的请求执行正常逻辑。剩余的请求会执行快速失败,若执行正常逻辑的请求失败了,则熔断器打开;若执行成功了,则熔断器关闭。这样熔断器就有了自我修复能力。

2.4.4 Hystrix处理流程

Hystrix流程图如下:

springcloud组件原理总结

Hystrix整个工作流如下:

  1. 构造一个 HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数;
  2. 执行命令,Hystrix提供了4种执行命令的方法,后面详述;
  3. 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动;
  4. 判断熔断器是否打开,如果打开,跳到第8步;
  5. 判断线程池/队列/信号量是否已满,已满则跳到第8步;
  6. 执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
  7. 统计熔断器监控指标;
  8. 走Fallback备用逻辑
  9. 返回请求响应

2.4.5 Hystyrix 原理

Hystrix通过命令模式对发送请求的对象和执行请求的对象进行解耦,将不同类型的业务请求封装为对应的命令请求。如订单服务查询商品,查询商品请求->商品Command;商品服务查询库存,查询库存请求->库存Command。并且为每个类型的Command配置一个线程池,当第一次创建Command时,根据配置创建一个线程池,并放入ConcurrentHashMap,如商品Command:

final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>();
...
if (!threadPools.containsKey(key)) {
    threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
}

后续查询商品的请求创建Command时,将会重用已创建的线程池。线程池隔离之后的服务依赖关系:

springcloud组件原理总结

通过将发送请求线程与执行请求的线程分离,可有效防止发生级联故障。当线程池或请求队列饱和时,Hystrix将拒绝服务,使得请求线程可以快速失败,从而避免依赖问题扩散。

上一篇:Feign与Hystrix的搭配


下一篇:服务间调用Feign + Hystrix