Sentinel流量防卫兵

前言

在我们平常工作中,总会有这样的事情发生:服务无法承受过多的请求而被打挂。

一般我们可以从两个方面处理:

  1. 增加节点,水平扩展(钱总是万能的)
  2. 对请求量过高的接口进行限流(没钱也不是不可以)

突发情况下我们会先用第一种方案,然后再过渡到第二种。毕竟:穷就一个字

随着这样的事情发生多了,系统就会可以预计的朝这样的方向演变:

  • 单个接口的限流 -> 多个接口的限流

    觉醒能力:限流可以配置,想要对哪个接口进行限流,就改下配置,立即生效。

  • 单个系统需要限流 -> 多个系统需要限流

    觉醒能力:限流功能组件化,后续还有系统需要限流功能,引入依赖即可,不需要重复开发。

  • 等等

通过这样的推论:每个系统都会发生高并发 -> 每个系统都会朝这个方向演变 -> 总有演变了很久的系统 -> 网上是否已经存在这样的轮子?

别说,真的有!今天我们要认识的主角Sentinel就是这样的又大又圆的轮子~

介绍

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。

官网地址:https://sentinelguard.io/zh-cn/

话不多说,先来个案例感受感受

案例

需求:要求每秒钟通过的qps限定在20

注:案例中所有统计相关的代码只是为了更加直观的体现sentinel的作用

引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.2</version>
</dependency>

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.测试结果

image

总体来说,测试结果符合预期

思考

以上案例是最简单的入门案例,也是Sentinel的核心所在。

其中关键的代码便是:SphU.entry("HelloWorld")

如果还想在其他业务代码中增加限流,则需要做出如下修改并增加流控规则

try (Entry entry = SphU.entry("资源名")) {
    // 业务代码
} catch (BlockException e) {
    // 根据异常进行处理
}

但是很明显,这是一个通用代码块,唯一的变量就是"资源名",我们很容易就想到通过切面的方式进行优化

如果是你,你会想要怎么改造它呢?

我们先来看看Sentinel的切面使用方式吧

整合SpringBoot

1.引入注解依赖

<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-annotation-aspectj</artifactId>
  <version>1.8.2</version>
</dependency>

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 = "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/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81

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.结果

image

在页面上多刷新几次就将出现我们在exceptionHanlder中返回的"被限流了"提示语

再次思考

整合是整合了,不知道大家有没有像我一样:有股吃了苍蝇一般的难受感

一个注解就要配一个限流规则,反正我算是吐了。

有关限流异常处理的逻辑可以使用公共的,大家可以查看上面贴出的官方文档链接

那么我们应该怎么样才能让自己内心畅通呢?

我们仔细品一下加载规则的逻辑,如果我们把这个步骤写成一个接口?

哦豁,那我这个规则岂不是想加就加,想改就改?

这里我就不演示了,因为Sentinel已经把这件事情做了,并且还很贴心的做了一个控制台~

整合Sentinel控制台

1. 安装Sentinel控制台

下载jar包

下载地址:https://github.com/alibaba/Sentinel/releases

启动

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. 添加依赖

<!-- 通信依赖 -->
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-transport-simple-http</artifactId>
  <version>1.8.2</version>
</dependency>
<!-- webmvc适配 -->
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-spring-webmvc-adapter</artifactId>
  <version>1.8.2</version>
</dependency>

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'

打开控制台并登录

image

在簇点链路栏中可以看到出现了刚才访问的资源地址

点击+流控按钮

image

一个qps阈值为2的规则

再次测试,多刷几次:curl 'http://localhost:8080/foo/test'

image

配置已然生效

眼尖的小伙伴已经发现了:左边的菜单栏好多规则可以配置,我们下次再聊吧~

问题

确实,在加入控制台之后解决了之前的问题,但是又产生了新的问题,不知道小伙伴有没有发现?

之前我们的流控规则是写在代码里的,服务停止重启都会重新加载到内存中。

现在我们把规则配置在sentinel控制台,由控制台推送到服务中。

注意:我们启动sentinel时并没有配置过数据库,所以如果服务重启了,配置会消失吗?

答案是会的,那么又怎么才能解决这个问题呢?

由于太久没更新过了,还没恢复状态,今天的内容也挺多了,下次吧~

小结

今天介绍了Sentinel这个强大的流量防护工具——虽然只是初窥门径,但不妨碍大家感受到他的强大之处。

我们从一个最基本的案例出发,通过对上一个案例的思考,引出下一个案例的解决方案,循序渐进。

同时在最后,我还留下了一点点问题供大家思考,大家也可以上官网进行寻找解决方案。

最后,希望大家有所收获~

下期:Sentinel控制台&整合SpringCloud

想要了解更多精彩内容,欢迎关注公众号:程序员阿鉴

个人博客空间:https://zijiancode.cn

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容