SpringCloud(一)-Ribbon负载均衡、Hystrix熔断机制、

接上篇文章:
https://www.jianshu.com/p/dac81a7bde30

负载均衡Ribbon

什么是Ribbon

Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为,为Ribbon配置服务提供者地址列表后,Ribbon就可基于某种负载均衡算法、自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等。我们也可以自定义Ribbon的负载均衡算法

实现Ribbon负载均衡

我们启动两个user-service实例,一个8081 一个8082
和Eureka的高可用配置方式一致,复制启动参数,修改启动端口

-Dserver.port=8082
image.png

EureKa监控面板


监控面板

开启负载均衡

Eureka中已经集成了Ribbon,所以我们无需引入新的依赖,直接修改代码
在RestTemplate的配置方法上添加@LoadBalanced注解

@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端
public class ConsumerApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

修改调用方式,不再手动获取ip和端口,直接通过服务名称调用

    @GetMapping("{id}")
    public User queyById(@PathVariable("id") Long id) {
        //根据服务id获取服务实例列表
        String url = "http://user-service/user/" + id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }

源码分析

为什么在这里我们输入服务名称就可以调用了呢?之前还需要获取ip和端口
显然有人帮我们根据service名称,获取到了服务实例和ip端口。它就是LoadBalanceInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id
我们来看一下源码:

image.png
image.png
image.png

修改负载均衡规则的配置入口:consumer的微服务配置

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式:{服务名称}.ribbon.NFLoadBalancerRuleClassName 值就是IRule的实现类

负载均衡策略

Ribbon默认是采用懒加载,即第一次访问时才会去创建负载均衡客户端,往往会出现超时,如果需要采用饥饿加载,随项目启动创建,可以这样配置

ribbon:
  eager-load:
    clients: user-service
    enabled: true

负载均衡的源码流程

image.png

通过RestTemplate进行请求时,我们访问的地址是http://user-service/user/1,RibbonLoadBalancerInterceptor会拦截RestTemplate的请求,并获取请求的host,然后根据服务id获取服务列表,然后会根据负载均衡规则选中某个服务,选中服务后会重写URL,这样就相当于通过ip和端口调用某个服务

Hystrix

简介

Hystrix是Netflix公司的一款组件,是一个开源的延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

雪崩问题

在微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口与才能实现,会形成非常复杂的调用链路:


复杂的调用链路

在一次业务请求中,需要调用A、P、H、I四个服务,这四个服务又可能又调用其他服务,这个时候,如果某个服务出现异常


image.png

例如微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的请求进来后,就会造成越来越多的阻塞
image.png

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
Hystrix解决雪崩问题的手段主要是服务降级

  • 线程隔离
  • 服务熔断

线程隔离,服务降级

原理

线程隔离示意图


线程隔离

解读:
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理

服务降级

优先保证核心服务,而非核心服务不可用或弱可用
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统奔溃,至少可以看到一个执行结果。
服务降级虽然会导致请求失败,但是不会导致阻塞。而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有影响。
触发Hystrix服务降级的情况

  • 线程池已满
  • 请求超时

实战

引入依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

开启熔断

在启动类上添加注解 @EnableCircuitBreaker

@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端
@EnableCircuitBreaker //开启熔断
public class ConsumerApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

我们在类上的注解越来越多,其中 @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker 可以合并为一个注解
@SpringCloudApplication

编写降级逻辑

当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。这样我们就需要提前编写好失败的降级处理逻辑。使用HystrixCommond来完成

  @GetMapping("{id}")
    @HystrixCommand(fallbackMethod = "queryByIdFallBack")
    public User queyById(@PathVariable("id") Long id) {
        //根据服务id获取服务实例列表
        String url = "http://user-service/user/" + id;
        System.out.println("url=========" + url);
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }

    public String queryByIdFallBack(Long id) {
        log.error("查询用户信息失败id:{}", id);
        return "网络错误";
    }
  • @HystrixCommand(fallbackMethod="queryByIdFallBack"):用来声明一个降级逻辑的方法

需要注意的是,熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。
测试:
当user-service正常提供服务时,访问服务与之前一致


image.png

我们将user-service停掉后,发现页面返回了降级处理信息


image.png

默认的Fallback

我们刚才是指定了Fallback,那如果需要服务降级的方法很多,我们岂不是要写很多的fallback,我们可以把Fallback配置加载类上,实现默认fallback
@DefaultProperties(defaultFallback="defaultFallBack")

@RestController
@RequestMapping("consumer")
@Slf4j
@DefaultProperties(defaultFallback = "queryByIdFallBack")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;


    @GetMapping("{id}")
    @HystrixCommand
    public String queyById(@PathVariable("id") Long id) {
        //根据服务id获取服务实例列表
        String url = "http://user-service/user/" + id;
        System.out.println("url=========" + url);
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    public String queryByIdFallBack(Long id) {
        log.error("查询用户信息失败id:{}", id);
        return "网络错误";
    }
}

超时设置

在之前的案例中,请求在超过1秒后会返回错误信息,这是因为Hystrix的默认超时时长为1秒,我们可以在配置中修改这个值

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

这个配置会作用于全局所有方法

服务熔断

熔断原理

熔断器,也叫断路器。Circuit Breaker


image.png

Hystrix的熔断状态机模型


熔断状态机模型

状态机有3个状态

  • Closed:关闭状态,所有请求都正常访问
  • Open:打开状态,所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阀值,则触发熔断,断路器会完全关闭。默认失败比例的阀值是50%,请求次数最少不低于20次
  • Half Open 半开状态。open状态不是永久的,打开后会进入休眠时间,随后断路器会自动进入半开状态,此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠

实战

为了能够精确控制请求的成功或失败,我们在consumer的调用业务中加入一些逻辑

  @GetMapping("{id}")
    @HystrixCommand
    public String queyById(@PathVariable("id") Long id) {
        if (id == 1) {
            throw new RuntimeException("busy");
        }
        //根据服务id获取服务实例列表
        String url = "http://user-service/user/" + id;
        System.out.println("url=========" + url);
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

在这个逻辑中,如果参数id为1,则一定会失败,其它情况都成功
我们准备两个请求窗口

  1. http://localhost:8080/consumer/1 注定失败
  2. http://localhost:8080/consumer/2 注定成功
    修改熔断器默认参数:
circuitBreaker:
requestVolumeThreshold: 10
sleepWindowInMilliseconds: 100
errorThresholdPercentage: 50
  • requestVolumeThreshold:触发熔断的最小请求次数,默认20
  • sleepWindowInMilliseconds:休眠时长,默认5000毫秒
  • errorThresholdPercentage:触发熔断的失败请求最小占比 默认50%

我们的操作是:疯狂的对id=1进行请求,就会触发熔断,断路器会断开,一切请求都会被降级处理,此时访问id=2,发现返回的也是失败,而且失败时间只有20毫秒左右

推荐阅读更多精彩内容