Sentinel流量防卫兵

前言 在我们平常工作中,总会有这样的事情发生:服务无法承受过多的请求而被打挂。
一般我们可以从两个方面处理:

  1. 增加节点,水平扩展(钱总是万能的)
  2. 对请求量过高的接口进行限流(没钱也不是不可以)
突发情况下我们会先用第一种方案,然后再过渡到第二种。毕竟:穷就一个字
随着这样的事情发生多了,系统就会可以预计的朝这样的方向演变:
  • 单个接口的限流 -> 多个接口的限流
    觉醒能力:限流可以配置,想要对哪个接口进行限流,就改下配置,立即生效。
  • 单个系统需要限流 -> 多个系统需要限流
    觉醒能力:限流功能组件化,后续还有系统需要限流功能,引入依赖即可,不需要重复开发。
  • 等等
通过这样的推论:每个系统都会发生高并发 -> 每个系统都会朝这个方向演变 -> 总有演变了很久的系统 -> 网上是否已经存在这样的轮子?
别说,真的有!今天我们要认识的主角Sentinel就是这样的又大又圆的轮子~
介绍 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。
官网地址:https://sentinelguard.io/zh-cn/
话不多说,先来个案例感受感受
案例 需求:要求每秒钟通过的qps限定在20
注:案例中所有统计相关的代码只是为了更加直观的体现sentinel的作用
引入依赖:
com.alibaba.csp sentinel-core 1.8.2

1. 定义流控规则
private void initFlowRules() { // 定义流控规则 FlowRule rule = new FlowRule(); // 资源名与需要限流的资源名相同 rule.setResource("HelloWorld"); // 设置限流方式为QPS rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 设置QPS为20 rule.setCount(20); // 加载规则 FlowRuleManager.loadRules(Collections.singletonList(rule)); }

2. 模拟请求
// 记录请求总数 private static final AtomicInteger TOTAL = new AtomicInteger(); // 记录请求通过数 private static final AtomicInteger PASS = new AtomicInteger(); // 记录请求拒绝数 private static final AtomicInteger BLOCK = new AtomicInteger(); private void request() { for (int i = 0; i < 30; i++) { new Thread(() -> { while (true){ // 记录总qps TOTAL.incrementAndGet(); // 进行限流 try (Entry entry = SphU.entry("HelloWorld")) { // 记录通过数 PASS.incrementAndGet(); } catch (BlockException e) { // 记录拒绝数 BLOCK.incrementAndGet(); } // 模拟业务等待0-50毫秒 try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(50)); } catch (InterruptedException ignored) { } } }).start(); } }

3. 统计
public void count() { new Thread(() -> { int oldTotal = 0, oldPass = 0, oldBlock = 0; while (true){ // 计算当前qps int total = TOTAL.get(); int secondsTotal = total - oldTotal; oldTotal = total; // 计算每秒通过数 int pass = PASS.get(); int secondsPass = pass - oldPass; oldPass = pass; // 计算每秒拒绝数 int block = BLOCK.get(); int secondsBlock = block - oldBlock; oldBlock = block; log.info("当前qps:{}, pass: {}, block:{}", secondsTotal, secondsPass, secondsBlock); try { // 停顿一秒 TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ignored) { } } }).start(); }

4.测试
@Test public void testBlock() throws IOException { // 初始化规则 this.initFlowRules(); // 模拟高并发访问 this.request(); // 统计qps this.count(); // 防止程序终止 System.in.read(); }

5.测试结果
Sentinel流量防卫兵
文章图片

总体来说,测试结果符合预期
思考 以上案例是最简单的入门案例,也是Sentinel的核心所在。
其中关键的代码便是:SphU.entry("HelloWorld")
如果还想在其他业务代码中增加限流,则需要做出如下修改并增加流控规则
try (Entry entry = SphU.entry("资源名")) { // 业务代码 } catch (BlockException e) { // 根据异常进行处理 }

但是很明显,这是一个通用代码块,唯一的变量就是"资源名",我们很容易就想到通过切面的方式进行优化
如果是你,你会想要怎么改造它呢?
我们先来看看Sentinel的切面使用方式吧
整合SpringBoot 1.引入注解依赖
com.alibaba.csp sentinel-annotation-aspectj 1.8.2

2. 编写Controller&Servcie的Demo
@RestController @RequestMapping("/foo") public class FooController {@Autowired private FooService fooService; @GetMapping public String hello(String name) { return fooService.hello(name); } }

public interface FooService {String hello(String name); }

@Service public class FooServiceImpl implements FooService {@Override public String hello(String name){ return "hello " + name; } }

3. 开启切面
@Configuration public class SentinelAspectConfiguration {@Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } }

4. 增加注解与限流处理
@SentinelResource(value = "https://www.it610.com/article/hello", blockHandler = "exceptionHandler") @Override public String hello(String name){ return "hello " + name; } public String exceptionHandler(String name, BlockException ex) { return "被限流了"; }

blockHandler: 限流对应的处理方法,方法参数和返回值与业务方法相同,对应着入门案例中的catch逻辑
关于SentinelResource注解的更多信息:https://github.com/alibaba/Se...
5. 配置流控规则启动
@SpringBootApplication public class SentinelDemoApplication {public static void main(String[] args) { // 初始化流控规则 initFlowRules(); SpringApplication.run(SentinelDemoApplication.class, args); }private static void initFlowRules(){ // 定义流控规则 FlowRule rule = new FlowRule(); // 资源名注解中的相同 rule.setResource("hello"); // 设置限流方式为QPS rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 设置QPS为2 rule.setCount(2); // 加载规则 FlowRuleManager.loadRules(Collections.singletonList(rule)); } }

6.测试
curl 'http://localhost:8080/foo?name=张三'
7.结果
Sentinel流量防卫兵
文章图片

在页面上多刷新几次就将出现我们在exceptionHanlder中返回的"被限流了"提示语
再次思考 整合是整合了,不知道大家有没有像我一样:有股吃了苍蝇一般的难受感
一个注解就要配一个限流规则,反正我算是吐了。
有关限流异常处理的逻辑可以使用公共的,大家可以查看上面贴出的官方文档链接
那么我们应该怎么样才能让自己内心畅通呢?
我们仔细品一下加载规则的逻辑,如果我们把这个步骤写成一个接口?
哦豁,那我这个规则岂不是想加就加,想改就改?
这里我就不演示了,因为Sentinel已经把这件事情做了,并且还很贴心的做了一个控制台~
整合Sentinel控制台 1. 安装Sentinel控制台
下载jar包 下载地址:https://github.com/alibaba/Se...
启动
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

默认账号密码为:sentinel sentinel
如果想要修改默认的账号密码,可增加参数
-Dsentinel.dashboard.auth.username=sentinel
-Dsentinel.dashboard.auth.password=123456
2. 添加依赖
com.alibaba.csp sentinel-transport-simple-http 1.8.2 com.alibaba.csp sentinel-spring-webmvc-adapter 1.8.2

3. 编写测试接口
@GetMapping("/test") public String test() { return "ok"; }

4. 配置统一异常处理
@Slf4j public class MyBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_OK); try (PrintWriter out = response.getWriter()) { out.write(new ObjectMapper().writeValueAsString("{\"message\":\"被限流了\"}")); out.flush(); } catch (IOException ignored) { } } }

将处理器加入到拦截器中
@Configuration public class InterceptorConfig implements WebMvcConfigurer {@Override public void addInterceptors(InterceptorRegistry registry) { // Add Sentinel interceptor addSpringMvcInterceptor(registry); }private void addSpringMvcInterceptor(InterceptorRegistry registry) { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setBlockExceptionHandler(new MyBlockExceptionHandler()); // 区分请求方式 config.setHttpMethodSpecify(true); registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } }

6.配置控制台地址
在resources下新建sentinel.properties配置文件
#应用名称 project.name=sentinel-demo #sentinel控制台地址 csp.sentinel.dashboard.server=localhost:8081

5.测试
第一次请求接口用于触发控制台初始化
curl 'http://localhost:8080/foo/test'
打开控制台并登录
Sentinel流量防卫兵
文章图片

在簇点链路栏中可以看到出现了刚才访问的资源地址
点击+流控按钮
Sentinel流量防卫兵
文章图片
一个qps阈值为2的规则
再次测试,多刷几次:curl 'http://localhost:8080/foo/test'
Sentinel流量防卫兵
文章图片

配置已然生效
眼尖的小伙伴已经发现了:左边的菜单栏好多规则可以配置,我们下次再聊吧~
问题 确实,在加入控制台之后解决了之前的问题,但是又产生了新的问题,不知道小伙伴有没有发现?
之前我们的流控规则是写在代码里的,服务停止重启都会重新加载到内存中。
现在我们把规则配置在sentinel控制台,由控制台推送到服务中。
注意:我们启动sentinel时并没有配置过数据库,所以如果服务重启了,配置会消失吗?
答案是会的,那么又怎么才能解决这个问题呢?
由于太久没更新过了,还没恢复状态,今天的内容也挺多了,下次吧~
小结 今天介绍了Sentinel这个强大的流量防护工具——虽然只是初窥门径,但不妨碍大家感受到他的强大之处。
我们从一个最基本的案例出发,通过对上一个案例的思考,引出下一个案例的解决方案,循序渐进。
同时在最后,我还留下了一点点问题供大家思考,大家也可以上官网进行寻找解决方案。
最后,希望大家有所收获~
下期:Sentinel控制台&整合SpringCloud
想要了解更多精彩内容,欢迎关注公众号:程序员阿鉴
【Sentinel流量防卫兵】个人博客空间:https://zijiancode.cn

    推荐阅读