哈尔滨网站运营服务商,制作外贸网站公司,wordpress 适合程序员,分类信息源码cms文章目录 Feign整合Sentinel线程隔离熔断降级授权规则自定义异常结果 上一期教程讲解了 Sentinel 的限流规则#xff1a;
Sentinel限流规则#xff0c;这一期主要讲述 Sentinel 的
隔离、降级和授权规则 虽然限流可以尽量避免因高并发而引起的服务故障#xff0c;但服务还… 文章目录 Feign整合Sentinel线程隔离熔断降级授权规则自定义异常结果 上一期教程讲解了 Sentinel 的限流规则
Sentinel限流规则这一期主要讲述 Sentinel 的
隔离、降级和授权规则 虽然限流可以尽量避免因高并发而引起的服务故障但服务还会因为其它原因而故障。而要将这些故障控制在一定范围避免雪崩就要靠线程隔离舱壁模式和熔断降级手段了
不管是线程隔离还是熔断降级都是对客户端调用方 的保护
Feign整合Sentinel
在 SpringCloud 中微服务调用一般都是通过 Feign 来实现的因此做客户端保护必须整合Feign和Sentinel
Feign 的使用在之前的教程中有详细讲解过Eureka结合Feign
实现的步骤如下
1.在调用方的 application.yml 文件中开启 Feign 对 Sentinel 的支持
feign:sentinel:enabled: true # 开启feign对sentinel的支持2.为 FeignClient 编写失败后的降级逻辑
这里有两种方式
① FallbackClass这种方式无法对远程调用的异常做处理 ② FallbackFactory这种方式可以对远程调用的异常做处理
这里我们选择 FallbackFactory
定义一个类 UserClientFallbackFactory名字可以任意然后实现接口 FallbackFactoryUserClient这里泛型要和对应的 Feign 客户端对应
Component
public class UserClientFallbackFactory implements FallbackFactoryUserClient {Overridepublic UserClient create(Throwable cause) {return new UserClient() {Overridepublic String user() {cause.printStackTrace();// 异常时返回空值return ;}};}}3.在 Feign 客户端的 FeignClient 注解中添加属性 fallbackFactory值就是刚才定义的类的 class 对象
FeignClient(name service2, url http://127.0.0.1:8082, fallbackFactory UserClientFallbackFactory.class)这里为了方便就直接定义了对应服务的 url如果结合了注册中心这里只需要填写对应的服务名即可
4.启动测试
访问对应服务端点后进入 Sentinel 控制台可以看到如下界面表示 Feign 已经成功整合了 Sentinel 然后添加限流规则将 QPS 设为 1测试异常情况 然后快速刷新浏览器可以发现限流后返回了空值即触发了 Feign 客户端的失败降级逻辑 线程隔离
线程隔离有两种方式实现
1.线程池隔离
这种方式就是将服务消费者的资源划分成一个又一个独立的线程池假设某一服务提供者挂掉了也只有一个线程池的资源会被耗尽不会导致所有的资源被耗尽
优点
① 支持主动超时可以通过线程池来控制线程比如发现某个线程耗时较长就可以直接终止这个线程 ② 支持异步调用自己的线程继续接收消息把调用交给线程池里的线程
缺点 线程的额外开销比较大需要开启的线程较多同时CPU还需要进行线程的上下文切换
场景 低扇出扇出指的是原本的请求分散后的请求数扇出越高需要开启的线程就越多
2.信号量隔离Sentinel 默认采用
信号量隔离指的是不会为每个业务创建一个线程池而是会统计当前业务已经使用的线程数然后进行限制。当达到这个限制时就会阻止对这个业务的请求即限制每个业务能使用的线程数量
优点 轻量级无额外开销
缺点
① 不支持主动超时 ② 不支持异步调用
场景 高频调用、高扇出
下面是两种方式的对比图 在 Sentinel 控制台中可以选择阈值类型共有两种 ① QPS就是每秒的请求数 ② 线程数是该资源能使用的 tomcat 线程数的最大值。也就是通过限制线程数量实现舱壁模式对应信号量隔离
熔断降级
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例如果超出阈值则会熔断该服务即拦截访问该服务的一切请求。而当服务恢复时断路器会放行访问该服务的请求
断路器是由内部的状态机实现的 整个的流程是正常情况下断路器关闭处于 Closed 状态。如果断路器统计的慢调用或异常比例达到失败阈值会开启断路器进入 Open 状态此时的请求会快速失败。直到熔断时间结束会进入 Half-Open 状态断路器半开启状态此时会尝试放行一次请求如果此次请求成功则关闭断路器进入 Closed 状态如果请求失败则重新打开断路器回到 Open 状态并重复刚才的过程
断路器熔断策略有三种慢调用、异常比例、异常数
1.慢调用
业务的响应时长RT大于指定时长的请求认定为慢调用请求。在指定时间内如果请求数量超过指定请求数并且慢调用比例大于设定的阈值则触发熔断
例如在 Sentinel 控制台中选择新增降级规则并填写如下信息 这个表单的信息表示响应时长RT 超过 500ms 的调用是慢调用统计最近 10000ms 内的请求如果请求量不少于 10 次并且慢调用比例不低于 0.5则触发熔断熔断时长为 5 秒。然后进入 half-open 状态放行一次请求做测试
2.异常比例或异常数
统计指定时间内的调用如果调用次数超过指定请求数并且出现异常指的是程序抛出的异常的比例达到设定的比例阈值或超过指定异常数则触发熔断
例如设置如下的降级规则 这两种设置方式都是一样的区别在于一个是设置异常比例一个是设置异常数但表达的意思都是统计最近 1000ms 内的请求如果请求量不少于 10 次并且异常比例不低于 0.4或异常数不少于2则触发熔断熔断时长为 5 秒。然后进入 half-open 状态放行一次请求做测试
授权规则
授权规则可以对调用方的来源做控制有白名单和黑名单两种方式
白名单来源origin在白名单内的调用者允许访问 黑名单来源origin在黑名单内的调用者不允许访问
在控制台中可以选择新增授权规则此时就可以选择黑名单或白名单两种方式 Sentinel 是通过 RequestOriginParser 接口中的 parseOrigin() 方法来获取请求的来源的
public interface RequestOriginParser {/*** 从请求request对象中获取origin获取方式自定义*/String parseOrigin(HttpServletRequest request);
}但是默认情况下这个方法的返回值永远是 “default”。也就是说Sentinel 默认无法区分所有的请求。因此我们需要自己实现这个接口并且编写业务逻辑
案例
限定只允许从网关发来的请求来访问 service1
实现步骤如下
1.编写类实现 RequestOriginParser 接口并编写 parseOrigin() 方法从 HTTP 请求中获取名为 origin 的请求头并返回
Component
public class HeaderOriginParser implements RequestOriginParser {Overridepublic String parseOrigin(HttpServletRequest httpServletRequest) {String origin httpServletRequest.getHeader(origin);if(StringUtils.isEmpty(origin)) {origin blank;}System.out.println(origin);return origin;}
}2.在 gateway 网关服务中利用过滤器添加 origin 请求头值为 gateway
server:port: 10010 # 网关端口
spring:application:name: gateway # 服务名称cloud:gateway:routes: # 网关路由配置- id: service1uri: http://localhost:8081 # 路由的目标地址predicates: # 路由断言判断请求是否符合路由规则的条件- Path/service1/** # 路径断言default-filters:- AddRequestHeaderorigin,gateway # 添加名为origin的请求头值为gateway
3.在控制台中配置授权规则 4.测试
首先直接跨过网关访问 service1如下图所示可以发现访问失败 然后通过网关来访问 service1如下图所示可以发现访问成功 自定义异常结果
默认情况下发生限流、降级、授权拦截时都会抛出异常到调用方。如果要自定义异常时的返回结果需要实现 BlockExceptionHandler 接口
public interface BlockExceptionHandler {/*** 处理请求被限流、降级、授权拦截时抛出的异常BlockException*/void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
;}可以看到handle() 方法中有一个类型为 BlockException 的参数这个参数表示了具体的异常而 BlockException 包含很多个子类分别对应不同的场景如下所示
异常说明FlowException限流异常ParamFlowException热点参数限流的异常DegradeException降级异常AuthorityException授权规则异常SystemBlockException系统规则异常
实现自定义异常返回结果的具体方式是定义一个类实现 BlockExceptionHandler 接口然后编写业务逻辑如下所示
Component
public class SentinelExceptionHandler implements BlockExceptionHandler {Overridepublic 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;charsetutf-8);response.setStatus(status);response.getWriter().println({\msg\: msg , \status\: status });}
}然后进行测试。先在控制台创建流控规则将 QPS 设为1如下 然后快速刷新浏览器结果如下所示可以看出异常结果和代码所展现的一致 其他的测试也是类似的但是发现热点规则无法触发该逻辑用 debug 设置断点发现在异常时没有进入到对应的代码里
因此建议对于热点规则的异常直接使用全局异常处理即可如下所示
RestControllerAdvice
public class GlobalExceptionHandler {ExceptionHandler(ParamFlowException.class)public void handleParamFlowException(HttpServletRequest request, HttpServletResponse response, Exception e)throws IOException {response.setContentType(application/json;Charsetutf-8);response.setStatus(429);response.getWriter().println({\msg\: 请求被热点参数限流 , \status\: 429 });}}测试后发现异常结果可以正常显示