Spring Cloud 负载均衡/熔断

一、负载均衡

1.1 什么是负载均衡

Load balancing,即负载均衡,是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。

将负载(工作任务,访问请求)进行平衡、分摊到多个操作单元(服务器,组件)上进行执行。是解决高性能,单点故障(高可用),扩展性(水平伸缩)的终极解决方案。

![img](2_Spring Cloud 负载均衡熔断.assets/573911-20190528112846156-21585619.png)**

1.2 为什么需要负载均衡

一个没有负载均衡的 web 架构类似下面这样:

![img](2_Spring Cloud 负载均衡熔断.assets/v2-6a7f624cd5e776a6b0b05a051e65666e_720w.jpg)

在这里用户是直连到 web 服务器,如果这个服务器宕机了,那么用户自然也就没办法访问了。另外,如果同时有很多用户试图访问服务器,超过了其能处理的极限,就会出现加载速度缓慢或根本无法连接的情况。

就像我们在日常生活中经常免不了要去一些比较拥挤的地方,比如地铁站、火车站、电影院、银行等。无论是买票,还是排队入场,这些场所一般都会设置多个服务点或者入口的。如果没有人引导的话,大多数情况下,最近的入口会挤满人。而哪些距离较远的服务点或者入口就宽松很多。

![img](2_Spring Cloud 负载均衡熔断.assets/573911-20190528134421557-1708431773.png)

这种情况下,就会大大浪费资源,因为如果可以把这些排队的人很好的分散到各个入口的话会大大缩短排队时间。其实,网站的建设也是一样的。为了提升网站的服务能力,很多网站采用集群部署,就像话剧院有多个入口一样。这时候,就需要一个协调者,来均衡的分配这些用户的请求,可以让用户的可以均匀的分派到不同的服务器上。

通过在后端引入一个负载均衡器和至少一个额外的 web 服务器,通常情况下,所有的后端服务器会保证提供相同的内容,以便用户无论哪个服务器响应,都能收到一致的内容。

![img](2_Spring Cloud 负载均衡熔断.assets/v2-6aa2607e04cc9d2f0d448f9fa80b2ae2_720w.jpg)

从图里可以看到,用户访问负载均衡器,再由负载均衡器将请求转发给后端服务器。在这种情况下,单点故障现在转移到负载均衡器上了。

二、Spring Cloud的负载均衡策略  

目前,在Spring Cloud 中服务之间通过Restful方式调用有两种方式:

  • RestTemplate+Ribbon
  • Feign

2.1 Ribbon

Ribbon是基于Ribbon实现的一套客户端负载均衡的工具,类似Nginx主要功能时提供客户端的软件负载均衡算法LB就是负载均衡,集中式(F5),进程内(Nginx),消费者可以自动看从Eureka中拿到对应的服务列表,默认进行轮询RoundRobinRule。

RestTemplate的自带的7中均衡策略

![img](2_Spring Cloud 负载均衡熔断.assets/20191111005220209007.png)

2.2 Fegin 

Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。

在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。

Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。

2.3 Ribbon和Feign两个的区别和选择

相同点

Ribbon和Feign都是实现软负载均衡调用

不同点

Ribbon是一个基于 HTTP 和 TCP 客户端的负载均衡器,它可以在客户端配置 RibbonServerList(服务端列表),然后默认以轮询请求以策略实现均衡负载,他是使用可以用restTemplate+Ribbon 使用

Feign是Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient ,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,感觉不到是调用远程方法

选择 选择feign,默认集成了ribbon,写起来更加思路清晰和方便,采用注解方式进行配置,配置熔断等方式方便

2.4 开发

准备

准备两个服务名相同,端口号不同,内容相同的服务生产者,并在注册中心进行注册。

效果如下

![image-20200809220137641](2_Spring Cloud 负载均衡熔断.assets/image-20200809220137641.png)

在客户端实现负载均衡

当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会被LoadBalancerInterceptor类的intercept方法拦截,拦截后将请求的地址中的服务逻辑名转为具体的服务地址,然后继续执行请求,默认进行轮询RoundRobinRule。

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

三、断路器/熔断

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,最终导致自身服务的瘫痪。

举个例子,在一个电商网站中,我们可能会将系统拆分成,用户、订单、库存、积分、评论等一系列的服务单元。用户创建一个订单的时候,在调用订单服务创建订单的时候,会向库存服务来请求出货(判断是否有足够库存来出货)。此时若库存服务因网络原因无法被访问到,导致创建订单服务的线程进入等待库存申请服务的响应,在漫长的等待之后用户会因为请求库存失败而得到创建订单失败的结果。如果在高并发情况之下,因这些等待线程在等待库存服务的响应而未能释放,使得后续到来的创建订单请求被阻塞,最终导致订单服务也不可用。

在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就会因依赖关系形成故障蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构就更加的不稳定。为了解决这样的问题,因此产生了断路器模式。

3.1 什么是断路器

“断路器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

3.2 Netflix Hystrix

在Spring Cloud中使用了Hystrix来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

1、Hystrix具备哪些能力/优点

  • 在通过网络依赖服务出现高延迟或者失败时,为系统提供保护和控制
  • 可以进行快速失败,缩短延迟等待时间和快速恢复:当异常的依赖回复正常后,失败的请求所占用的线程会被快速清理,不需要额外等待
  • 提供失败回退(Fallback)和相对优雅的服务降级机制
  • 提供有效的服务容错监控、报警和运维控制手段

3.3 Feign使用Hystrix

我们不需要在Feigh工程中引入Hystix,Feign中已经依赖了Hystrix。

开启Hytrix

feign:
  # 断路器
  hystrix:
    enabled: true

配置回退机制

创建接口得实现类

@Component
public class PorductUserServiceImpl implements PorductUserService {
    @Override
    public String getUser() {
        return "没有查到用户信息";
    }
}

配置回退机制

// 获取SPRINGCLOUD-PORDUCT服务下得
@FeignClient(value = "springcloud-porduct",fallback = PorductUserServiceImpl.class)
public interface PorductUserService {

    // 映射生产者对应得方法请求路径
    @RequestMapping("/user/getUser")
    String getUser();
}

解决返回XML格式问题

		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            <!--解决默认返回xml格式问题-->
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.dataformat</groupId>
                    <artifactId>jackson-dataformat-xml</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.0.3.RELEASE</version>
            <!--解决默认返回xml格式问题-->
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.dataformat</groupId>
                    <artifactId>jackson-dataformat-xml</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

作业

增删改

生产者负责查询接口

消费者调用接口