当前位置: 首页 > article >正文

线程隔离和熔断降级并配置对应的服务降级

目录

隔离和降级

FeignClient整合Sentinel

通过Feign设置服务降级

 1.创建类实现FallbackFactory接口,并让这个类和使用@FeignClient的接口类绑定

2.让order-service服务的feign开启sentinel

3.测试,只开启order-service服务,而不开启user-order服务

总结:

 线程隔离

线程隔离的实现方式

 sentinel的线程隔离

1)配置隔离规则

2)jemter测试

 总结

熔断降级 

慢调用

​编辑 异常比例、异常数

授权规则

基本规则

如何获取origin 

 案例:我们需要只能从gateway网关才能访问资源接口

1.在order-service服务中编写类实现RequestOriginParser接口

2.gateway添加origin请求头

3.在sentinel中设置授权规则

4.结果

 自定义异常结果

异常类型

 自定义异常处理类实现BlockExceptionHandler接口

总结:


隔离和降级

虽然限流可以尽量避免因高并发而引起的服务故障,但服务还是会因为某些原因(服务自己出现问题)而故障。而要将这些故障控制在一定范围内,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降级手段了

线程隔离就是:调用者在调用服务提供者时,给每个调用的请求分配独立线程池,出现故障时,最多消耗这个线程池内资源,避免把调用者的所有资源耗尽。

可以看到下图,我们已经规定了访问其他服务可以使用的最大线程数为10个,服务C出现问题,就只会有10个线程无法使用,而不会导致服务A中的所有线程都不可用,导致线程A宕机。

熔断降级:是在调用方这边加入断路器,统计对服务提供者的调用,如果调用的失败比例过高,则熔断该业务,不允许访问该服务的提供者了。

由下图可以看到,当服务A向服务D由两个请求都出现异常时,在服务A和服务D之间就会出现一道熔断墙,阻止服务A的其他请求向服务D(直接报错,抛异常)

 我们发现不能处理的请求都是直接抛异常,终止请求,我们可以设置降级服务

如:如果访问queryById接口失败,就去访问queryByIdFalback接口作为备选

FeignClient整合Sentinel

SpringCloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel。

通过Feign设置服务降级

 在orderService微服务中会使用UserFeignClient去访问用户的信息

我们需要给UserFiegnClient的方法设置服务降级

User user = userFeignClient.queryById(order.getUserId());

 1.创建类实现FallbackFactory接口,并让这个类和使用@FeignClient的接口类绑定

不要导错包 ,是feign.hystrix.FallbackFactory包下的FallbackFactory

import feign.hystrix.FallbackFactory;
//直接加@Component不能被启动类扫描到,因为与启动类不在同一个包
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
    //cause为远程调用UserFeignClient时出现的异常
    @Override
    public UserFeignClient create(Throwable cause) {
        cause.printStackTrace();
        return new UserFeignClient() {
//重写UserFeign的所有方法,作为服务降级的备选处理方法
            @Override
            public User queryById(Long id) {
                //设置默认用户信息
                User user = new User();
                user.setId(null);
                user.setAddress(null);
                user.setUsername("默认用户");
                return user;
            }
            @Override
            public String getUser(String username, Integer age) {
                return "没有这个用户";
            }
        };
    }
}

 维护到IoC容器中

  @Bean
    public UserFeignClientFallbackFactory userFeignClientFallbackFactory(){
        return new UserFeignClientFallbackFactory();
    }

让UserFeignClient与UserFeignClientFallbackFactory绑定服务降级

//与FallbackFactory绑定降级服务
@FeignClient(value = "user-service",configuration = FeignConfig.class,fallbackFactory = UserFeignClientFallbackFactory.class)
public interface UserFeignClient {
    @RequestMapping("/user/{id}")//使用Get方式发送请求,获取到的响应数据反序列化成User类型
    User queryById(@PathVariable("id")Long id);//这里是把方法参数放到请求路径中,与controller相反

    @RequestMapping("/user/getUser")
    //使用@RequestParam注解让Feign知道这两个请求参数值是要加到url地址中的
    String getUser(@RequestParam("username") String username, @RequestParam("age") Integer age);
}

2.让order-service服务的feign开启sentinel

feign:
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数
  sentinel:
    enabled: true #与sentinel整合,开启服务降级

3.测试,只开启order-service服务,而不开启user-order服务

如果我们之前没有编写服务降级,就会直接报错,查不到用户信息,但是我们已经开启的服务降级

访问接口,返回的用户信息是我们设置的默认用户信心,服务降级成功 

总结:

Feign整合Sentinel的步骤:

  • 在application.yml中配置:feign.sentienl.enable=true
  • 给FeignClient编写FallbackFactory并注册为Bean
  • 将FallbackFactory配置到FeignClient

 线程隔离

线程隔离的实现方式

线程隔离有两种方式实现:

  • 线程池隔离

  • 信号量隔离(Sentinel默认采用)

线程池隔离:给每个服务调用业务分别分配一个线程池,利用线程池本身实现隔离效果

优点:

  • 支持主动超时:服务I的请求主线程到线程池时,如果线程池没有及时给出响应,就直接报错,支持主动超时
  • 支持异步调用:服务I的请求主线程,访问到访问服务A的线程池时,线程池会自己抽出一个空闲线程去访问服务A,而不用请求主线程主动访问

缺点:

线程的二外开销大

信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。

 优点:

轻量级,不需要额外的线程开销,一个线程执行到底

缺点:

  • 不支持主动超时
  • 不支持异步调用

 sentinel的线程隔离

在添加限流规则时,可以选择两种阈值类型:

  • QPS:就是每秒的请求数,在快速入门中已经演示过

  • 线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现线程隔离(舱壁模式)。

案例需求:给 order-service服务中的UserClient的查询用户接口设置流控规则,线程数不能超过 2。然后利用jemeter测试。

1)配置隔离规则

因为order-service服务的feign已经整合的sentinel,也会把feign接口的方法作为资源

选择feign接口后面的流控按钮:

这里设置了访问/user/{id}接口时,同时最多使用的线程数为2个 

 

2)jemter测试

一瞬间同时创建10个线程

一次发生10个请求,有较大概率并发线程数超过2,而超出的请求会走之前定义的失败降级逻辑。 

访问/user/{id}时同时使用的线程超过2时,就是触发线程隔离,然后服务降级 

 总结

线程隔离的两种手段是?

  • 信号量隔离

  • 线程池隔离

信号量隔离的特点是?

  • 基于计数器模式,简单,开销小

线程池隔离的特点是?

  • 基于线程池模式,有额外开销,但隔离控制更强

熔断降级 

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。 熔断这个服务之后,我们会设置一个熔断时间,一旦过了这个时间就会尝试放行一次请求,如果这个这个请求还是异常,就继续熔断,等待下一次的熔断时间结束

断路器控制熔断和放行是通过状态机来完成的:

 

状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后(熔断时间到)会进入half-open状态
  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
    • 请求成功:则切换到closed状态
    • 请求失败:则切换到open状态

断路器熔断策略有三种:慢调用、异常比例、异常数 

慢调用

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。

这个设置了10000ms时间范围内如果10个请求内有5个以上请求的响应时间大于500ms,就会触发熔断状态 ,熔断的时长为5s,过了5s才会尝试放行一次请求,如果成功就取消熔断,如果还是失败,就继续熔断

 异常比例、异常数

异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

例如,一个异常比例设置:

解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.4,则触发熔断。

一个异常数设置:

 解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于2次,则触发熔断。

授权规则

授权规则可以对请求方来源做判断和控制。

基本规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。

  • 白名单:来源(origin)在白名单内的调用者允许访问

  • 黑名单:来源(origin)在黑名单内的调用者不允许访问

 

  • 资源名:就是受保护的资源,例如/order/{orderId}

  • 流控应用:是来源者的名单,

    • 如果是勾选白名单,则名单中的来源被许可访问。
    • 如果是勾选黑名单,则名单中的来源被禁止访问。

 

我们允许请求从gateway到order-service,不允许浏览器访问order-service,那么白名单中就要填写网关的来源名称(origin)

如何获取origin 

Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。

public interface RequestOriginParser {
    /**
     * 从请求request对象中获取origin,获取方式自定义
     */
    String parseOrigin(HttpServletRequest request);
}

这个方法的作用就是从request对象中,获取请求者的origin值并返回。

默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值default。i

因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的origin这样一来接口资源就可以知道访问的来源(origin)是哪一个

 案例:我们需要只能从gateway网关才能访问资源接口

我们可以在gateway中给每一个请求添加origin=gateway的请求头,让sentinel只能放行origin为gateway的请求去访问资源

1.在order-service服务中编写类实现RequestOriginParser接口

这个类的作用就是从请求中解析出origin(请求来源)并返回

package cn.itcast.order.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 从请求中获取origin
 */
@Component
public class HeaderOriginParser implements RequestOriginParser {
    //返回访问这个资源的origin来源
    @Override
    public String parseOrigin(HttpServletRequest request) {
        //获取origin的请求头信息
        String origin = request.getHeader("origin");
        //浏览器访问时没有添加origin请求头
        if(StringUtils.isBlank(origin)){
            return "blank";
        }
        return origin;
    }
}

2.gateway添加origin请求头

      default-filters: #给所有的微服务都加上这个过滤器群
        - AddRequestHeader=origin,gateway

3.在sentinel中设置授权规则

设置只有origin为gateway的请求才能访问资源

4.结果

从gateway发出的请求可以访问 

 

直接从浏览器发送请求会失败

 

 自定义异常结果

默认情况下,发生限流、降级(被调用的服务出问题)、授权拦截时,都会抛出异常到调用方。异常结果都是显示flow limmiting(限流)。这样不够友好,无法得知是限流还是降级还是授权拦截。 

异常类型

而如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口:

public interface BlockExceptionHandler {
    /**
     * 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
     */
    void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}

这个方法有三个参数:

  • HttpServletRequest request:request对象
  • HttpServletResponse response:response对象
  • BlockException e:被sentinel拦截时抛出的异常

这里的BlockException包含多个不同的子类:

异常说明
FlowException限流异常
ParamFlowException热点参数限流的异常
DegradeException降级异常
AuthorityException授权规则异常
SystemBlockException系统规则异常

 自定义异常处理类实现BlockExceptionHandler接口

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
    }
}

 重启测试,在不同场景下,会返回不同的异常消息.

总结:

限流是对一个服务进行保护,防止高并发导致服务崩溃,本质上服务没有出问题

线程隔离和熔断降级式防止一个服务出现问题,而当这个服务出现问题时,原先的请求就得不到想要的响应数据,而是一个异常,所以我们可以设置一个服务降级,不直接抛出异常,而是写一种默认的处理方法(如返回默认账号)


http://www.kler.cn/a/386256.html

相关文章:

  • ChatGPT登录失败的潜在原因分析
  • 从0学习React(11)
  • 如何在CentOS 7上搭建SMB服务
  • 【JavaEE初阶 — 多线程】生产消费模型 阻塞队列
  • 高效稳定!新加坡服务器托管方案助力企业全球化布局
  • androidstudio下载gradle慢
  • Wecom酱搭建企业微信发送消息
  • Leetcode:26. 删除有序数组中的重复项——Java快慢指针暴力解法
  • 华为云计算HCIE-Cloud Computing V3.0试验考试北京考场经验分享
  • 实战项目:通过自我学习让AI学习五子棋 - 1 - 项目定义
  • 【C语言】文件操作(超万字解析+形象图解)
  • Jest进阶知识:React组件的单元测试
  • 万字长文详解JavaScript基础语法--前端--前端样式--JavaWeb
  • redis 写入权限配置
  • 常用的 Lambda 表达式案例解析
  • 《 C++ 修炼全景指南:十九 》想懂数据库?深入 B 树的世界,揭示高效存储背后的逻辑
  • 不加锁解决线程安全
  • AWS账号安全:如何防范与应对账号被盗风险
  • 【mysql相关】
  • 使用ChatGPT神速精读文献,12个高阶ChatGPT提示词指令,值得你复制使用
  • 哪些人群适合考取 PostgreSQL 数据库 PGCM 证书?
  • 【C++练习】使用海伦公式计算三角形面积
  • CDN到底是什么?
  • 《IDE 使用技巧与插件推荐》
  • 从xss到任意文件读取
  • vue组件传参的八种方式详细总结