目录
假如你去设计Sentinel,你将如何判定哪些数据是热点数据
核心知识点
Sentinel降级概念
当访问量过大,大量的线程堆积会造成服务器宕机,我们可以通过降级来熔断这个请求来保证服务的稳定性,还在这里的熔断属于主动熔断
基于Sentinel实现服务降级(主动熔断)
慢调用比列
第一步:写测试方法
/** * 演示降级操作,通过如下代码创造降级条件: * 服务响应速度慢或服务不稳定,经常出现异常 * */ //自增API,线程是安全的,对象内的1为初始值 private AtomicLong atomicLong = new AtomicLong(1); @GetMapping("/sentinel05") public String doSentinel05() throws InterruptedException { //先获取,再自增 long count = atomicLong.getAndIncrement(); if (count%2==0){//判断,偶数让线程休眠 Thread.sleep(200);//偶数线程休眠200ms,模拟线程阻塞情景 } return "sentinel05 test ..."; }
第二步:在蔟点链路给资源配置降级信息(难点)
解释:在统计时长(2s)内发送请求数大于最小请求(5)个时,慢调用比例生效,假设这些请求的比例阈值(30%)超过最大RT(200ms)时,也就是这些请求响应时间过长或者不响应时,就将这这服务断开20s,也就是熔断时长,在这20s内该服务处于不可用状态,20s后重新进行一轮的计算
第三步:测试降级
控制台报错,显示的内容是我们自定义的实现BlockExceptionHandler接口的异常处理类,但这是限流的异常信息,用在降级后的错误显示内容显然有些不合适,我们可以通过debug检查一下降级所报的异常实现什么类型,对降级所报的异常进行处理
第四步:debug
Sntinue的所有异常都是通过实现BlockException接口进行处理的,我们通过debug发现,此时BlockException的参数e为DegradeException实现类对象,说明此时的异常处理类DegradeException
第五步:使用BlockException的instanceof方法来对异常进行一个判断,再处理
String msg ="访问太过频繁,反应不过来啦!666";//默认为限流异常(直接和关联方式) if (e instanceof DegradeException){//熔断异常 msg = "服务不可用,稍后再访问吧!"; } out.println(msg); out.flush(); out.close();
第六步:测试降级后的错误信息
异常比列
第一步:写测试方法,模拟出现异常情景
/** * 演示降级操作,通过如下代码创造降级条件: * 服务响应速度慢或服务不稳定,经常出现异常 * */ //自增API,线程安全,对象内的参数为初始值 private AtomicLong atomicLong = new AtomicLong(1); @GetMapping("/sentinel05") public String doSentinel05() throws InterruptedException { //先获取,在自增 long count = atomicLong.getAndIncrement(); //判断,偶数线程阻塞或抛异常 if (count%2==0){ //线程休眠200ms,模拟线程阻塞 //Thread.sleep(200); //或者直接抛出异常 throw new RuntimeException("服务调用失败!"); } return "sentinel05 test ..."; }
第二步:指定资源配置降级
此处的比例阈值为这些请求的30%都报异常时,就会熔断这个服务20s
第三步:测试降级
每两次就会抛异常,因为我们的测试方法抛出了运行时异常
2秒内请求大于5次,且报异常的服务超过了30%,将该服务熔断20s
Sentinel系统规则实践
Sentinel热点规则实践
热点规则产生背景
当某一商品瞬间被大量人访问时,我们通过传递此商品的id到服务器进行热点限流
当某一用户大量访问时,我们通过传递此用户的id到服务器进行热点限流
实现原理
我们将热点数据通过参数传递到服务,并根据配置的限流阈值与模式,对包含热点资源的服务调用进行限流
热点限流可以看做一种特殊的流量控制,只针对包含热点参数的服务调用进行限流
Sentinel会利用LRU策略统计最近常用的热点参数,结合令牌桶的算法来进行参数级别的流控
快速入门
第一步:因为热点限流是基于@SentinelResource注解,也就是通过切面的通知方法进行限流,所以热点限流的异常处理方法应写在自定义的切面异常处理类中
//处理由@SentinelResource注解修饰时的方法处理限流时的异常 @Component @Slf4j public class ResourceBlockHandler { /** * 这里异常处理方法的要求: * 1.修饰符 public static * 2.返回值类型与@SentinelResource注解描述的方法返回指相同 * 3.参数列表与@SentinelResource注解描述的方法返回值相同 * 可以在最后多添加一个blockHandle参数 * */ public static String doHandle(BlockException ex){ log.error("链路限流"); return "访问太频繁"; } public static String doHandle(Integer id,BlockException ex){ log.error("热点限流"); return "访问人数太多,稍后再试试吧!"; } }
因为此方法携带的有integer参数,所以可以与上一个方法同名
第二步:编写测试方法
@GetMapping("/sentinel06") @SentinelResource(value = "resource",blockHandlerClass = ResourceBlockHandler.class,blockHandler = "doHandle") //此处@RequestParam("id"),是为了向前端反向兼容,请求时该链接必须携带参数,否则报错 public String doSentinel06(@RequestParam("id") Integer id){ return "Get Resource By"+id; }
第三步:配置热点限流规则
参数索引为@SentinelResource标识方法的参数下标,第一个参数为0,第二个参数为1....在统计窗口时长内超过单机阈值则进行热点限流
第四步:测试
服务器打印错误日志
2022-03-02 20:46:27.016 ERROR 15180 --- [nio-8082-exec-1] c.j.p.service.ResourceBlockHandler : 热点限流
特定参数设置
点开热点规则,对目标资源进行编辑,点开高级选项
例外项为在同一个统计窗口时长内,对不同的单机阈值进行热点限流
这里表示参数为1,单机阈值为1,这里表示参数为10,单机阈值为5
Sentinel授权限流实践
概述
很多时候,我们需要根据资源调用方进行限流,这时候就用到Sentinel的黑白名单功能,当设置白名单时,只有位于白名单的调用者才能调用资源,当设置黑名单时,除了黑名单的调用方,其余都能调用资源
快速入门
第一步:通过实现RequestOriginParser接口,重写里面的方法来获取请求地址中的参数,请求头,ip等信息,来进行授权限流,这个类要交给Spring容器管理
@Component public class DefaultRequestOriginParser implements RequestOriginParser { /** * 这个方法基于业务规则对请求数据进行解析 * */ @Override public String parseOrigin(HttpServletRequest request) { 1.解析请求参数,并返回参数值,然后将这个值应用在Sentinel的授权规则上 String origin = request.getParameter("origin"); return origin; } }
第二步:配置授权限流
请求路径参数为app1,app2时,限流,其余不限流
第三步:测试
我们自定义的DefaultRequestOriginParser实现类DefaultRequestOriginParser通过origin(K)拿到参数值app1(V),所以限流
授权异常处理
第一步:我们发现该异常是我们自定义BlockExceptionHandler实现类所报的异常,我们通过debug发现该异常类型为AuthorityException
第二步:设置AuthorityException异常的前端显示内容
@Component public class SentinelExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception { //设置响应状态码 response.setStatus(429); //设施设备响应编码格式 response.setContentType("text/html;charset=UTF-8"); //设置输出流对象 PrintWriter out = response.getWriter(); String msg ="访问太过频繁,反应不过来啦!666";//默认为限流异常(直接和关联方式) if (e instanceof DegradeException){//熔断异常 msg = "服务不可用,稍后再访问吧!"; } if (e instanceof AuthorityException){//授权异常 msg = "您已被拉入黑名单,无法访问该资源"; } out.println(msg); out.flush(); out.close(); } }
第三步:测试
我们该可以通过设置请求头内容进行授权限流
在自定义RequestOriginParser实现类获取请求头信息
@Component public class DefaultRequestOriginParser implements RequestOriginParser { /** * 这个方法基于业务规则对请求数据进行解析 * */ @Override public String parseOrigin(HttpServletRequest request) { //1.解析请求参数,并返回参数值,然后将这个值应用在Sentinel的授权规则上 // String origin = request.getParameter("origin"); // return origin; //2.解析请求头,并返回参数值,然后将这个值应用在Sentinel的授权规则上 String token = request.getHeader("token"); System.out.println(token); return token; //3.解析ip地址,并返回参数值,然后将这个值应用在Sentinel的授权规则上 // String ip = request.getRemoteAddr(); // System.out.println("ip地址:"+ip); // return ip; } }
配置授权限流信息
以为请求头无法在地址栏填写,我们使用http文件,token的值为AAA
只有AAA才能访问/provider/sentinel01资源,因为只有AAA在白名单里
我们还可以通过获取调用者的ip来进行授权限流
在自定义RequestOriginParser实现类获取ip信息,并在控制台打印
@Component public class DefaultRequestOriginParser implements RequestOriginParser { /** * 这个方法基于业务规则对请求数据进行解析 * */ @Override public String parseOrigin(HttpServletRequest request) { //1.解析请求参数,并返回参数值,然后将这个值应用在Sentinel的授权规则上 // String origin = request.getParameter("origin"); // return origin; //2.解析请求头,并返回参数值,然后将这个值应用在Sentinel的授权规则上 // String token = request.getHeader("token"); // System.out.println(token); // return token; //3.解析ip地址,并返回参数值,然后将这个值应用在Sentinel的授权规则上 String ip = request.getRemoteAddr(); System.out.println("ip地址:"+ip); return ip; } }
配置授权限流规则,将127.0.0.1放入白名单
在浏览器输入ip为127.0.0.1进行测试
可以访问到/provider/sentinel01资源
在浏览器输入ip为localhost时,就报错了,因为后端控制器输出的ip为0.0.0.0.0.1
注意:不能在http文件进行测试,因为idea可能会把localhost和127.0.0.1绑定在一起
请求的底层实现原理
第一步:从Tomcat的线程池获取一个线程
第二步:通过I/O对象读取http协议中的请求数据
第三步:解析http中的数据(请求头,请求行,请求体)
第四步:创建请求对象(request),响应对象(response),然后对请求数据进行封装
第五步:通过Filter对象对请求进行过滤
第六步:将合法的请求交给Servlet去处理(拦截器/controller..)
第七步:处理结束返回响应数据并传递给客户端
常见问题
何为熔断
让外部应用停止对该资源的访问,类似跳闸,前方施工请绕行
为什么要使用熔断
当一些服务平均响应时间太长或经常抛出异常,这儿样可能会造成调用链堆积,从而导致系统崩溃
Sentinel限流异常的顶底父类
BlockException
Sentinel降级抛出的异常是哪个类
DegradeException
Sentinel出现异常的处理接口是哪个
BlockExceptionHandler,此接口阿里有默认实现,我们也可以自定义此接口实现类
第一个类DefaultBlocKExceptionHandler为阿里默认异常处理实现类
第二个类为我们自定义的实现类,注意,此类要交给Spring容器管理
Sentinel降级策略都有哪些
慢调用比例,异常比例,异常数
底层基于什么对热点数据进行限流
AOP
假如你去设计Sentinel,你将如何判定哪些数据是热点数据
LRU算法
java中是否有直接封装了LRU算法的类
有,LinkHashMap
你了解的系统限流规则都有哪些
cpu,RT,QPS,线程数等
如何理解Sentinel中的授权规则
对资源的访问限制,不是基于频次,而是基于中自定义黑白名单
Sentinel中解析黑白名单的接口是谁
RequestOriginParser