Browse Source

增加 sentinel 限流 熔断 降级

dengsixing 3 years ago
parent
commit
a5daf71b9b

+ 5 - 0
4dkankan-center-scene/pom.xml

@@ -63,6 +63,11 @@
             <artifactId>lombok</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+
 
     </dependencies>
 

+ 5 - 3
4dkankan-center-scene/src/main/java/com/fdkankan/scene/controller/TestController.java

@@ -1,6 +1,7 @@
 package com.fdkankan.scene.controller;
 
 import cn.hutool.core.collection.ConcurrentHashSet;
+import com.alibaba.csp.sentinel.annotation.SentinelResource;
 import com.alibaba.druid.pool.DruidDataSource;
 import com.alibaba.fastjson.JSONObject;
 import com.fdkankan.common.constant.ConstantFilePath;
@@ -74,9 +75,10 @@ public class TestController {
 //        for (int i = 0; i< 10; i++){
 //            rocketMQProducer.syncSend("qwe",i+"",  "消息体"+i);
 //        }
-//        Long loading =  redisUtil.incr(RedisLockKey.LOCK_FDKANKAN_SCENE_NUMS, 1);
-        String sceneNum = scene3dNumService.generateSceneNum();
-        return sceneNum;
+//        String test = null;
+//        test.equals("123");
+        String ruleDir = System.getProperty("user.home");
+        return ruleDir;
 
     }
 

+ 1 - 0
4dkankan-center-scene/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc

@@ -0,0 +1 @@
+com.fdkankan.common.config.DataSourceInitFunc

+ 9 - 1
4dkankan-center-scene/src/main/resources/bootstrap.yml

@@ -45,7 +45,15 @@ spring:
       discovery:
         server-addr: 192.168.0.47:8848
         namespace: 4dkankan-dev
-
+    sentinel:
+      transport:
+        dashboard: localhost:8080
+        heartbeat-interval-ms: 500
+        port: 8719
+      eager: true #取消sentinel控制台懒加载
+#      log:
+#        dir: ./logs # 默认值${home}/logs/csp/
+#        switch-pid: true # 日志带上线程id
 
 
 

+ 4 - 0
4dkankan-common/pom.xml

@@ -147,6 +147,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-aop</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 156 - 0
4dkankan-common/src/main/java/com/fdkankan/common/config/DataSourceInitFunc.java

@@ -0,0 +1,156 @@
+package com.fdkankan.common.config;
+
+import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
+import com.alibaba.csp.sentinel.datasource.*;
+import com.alibaba.csp.sentinel.init.InitFunc;
+import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
+import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
+import com.alibaba.csp.sentinel.slots.system.SystemRule;
+import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
+import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * <p>
+ * sentinel规则数据持久化到本地文件
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/1/26
+ **/
+public class DataSourceInitFunc implements InitFunc {
+
+    @Override
+    public void init() throws Exception {
+        //持久化在本地的目录
+        String ruleDir = System.getProperty("user.home") + "/sentinel/order/rules";
+        String flowRulePath = ruleDir + "/flow-rule.json";
+        String degradeRulePath = ruleDir + "/degrade-rule.json";
+        String systemRulePath = ruleDir + "/system-rule.json";
+        String authorityRulePath = ruleDir + "/authority-rule.json";
+        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
+
+        this.mkdirIfNotExits(ruleDir);
+        this.createFileIfNotExits(flowRulePath);
+        this.createFileIfNotExits(degradeRulePath);
+        this.createFileIfNotExits(systemRulePath);
+        this.createFileIfNotExits(authorityRulePath);
+        this.createFileIfNotExits(paramFlowRulePath);
+
+        // 流控规则
+        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
+                flowRulePath,
+                flowRuleListParser
+        );
+        // 将可读数据源注册至FlowRuleManager
+        // 这样当规则文件发生变化时,就会更新规则到内存
+        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
+        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<List<FlowRule>>(
+                flowRulePath,
+                this::encodeJson
+        );
+        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中
+        // 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
+        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
+
+        // 降级规则
+        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
+                degradeRulePath,
+                degradeRuleListParser
+        );
+        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
+        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
+                degradeRulePath,
+                this::encodeJson
+        );
+        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
+
+        // 系统规则
+        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
+                systemRulePath,
+                systemRuleListParser
+        );
+        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
+        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
+                systemRulePath,
+                this::encodeJson
+        );
+        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
+
+        // 授权规则
+        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
+                flowRulePath,
+                authorityRuleListParser
+        );
+        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
+        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
+                authorityRulePath,
+                this::encodeJson
+        );
+        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
+
+        // 热点参数规则
+        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
+                paramFlowRulePath,
+                paramFlowRuleListParser
+        );
+        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
+        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
+                paramFlowRulePath,
+                this::encodeJson
+        );
+        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
+    }
+
+    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
+            source,
+            new TypeReference<List<FlowRule>>() {}
+    );
+    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
+            source,
+            new TypeReference<List<DegradeRule>>() {}
+    );
+    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
+            source,
+            new TypeReference<List<SystemRule>>() {}
+    );
+
+    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
+            source,
+            new TypeReference<List<AuthorityRule>>() {}
+    );
+
+    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
+            source,
+            new TypeReference<List<ParamFlowRule>>() {}
+    );
+
+    private void mkdirIfNotExits(String filePath) throws IOException {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            file.mkdirs();
+        }
+    }
+
+    private void createFileIfNotExits(String filePath) throws IOException {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            file.createNewFile();
+        }
+    }
+
+    private <T> String encodeJson(T t) {
+        return JSON.toJSONString(t);
+    }
+}

+ 57 - 0
4dkankan-common/src/main/java/com/fdkankan/common/config/SentinelUrlBlockHandler.java

@@ -0,0 +1,57 @@
+package com.fdkankan.common.config;
+
+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 com.alibaba.csp.sentinel.slots.system.SystemBlockException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fdkankan.common.response.ResultData;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * <p>
+ * sentinel统一异常处理
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/1/26
+ **/
+@Slf4j
+@Component
+public class SentinelUrlBlockHandler implements BlockExceptionHandler {
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
+        String msg = null;
+        if (e instanceof FlowException) {
+            msg = "限流了";
+        } else if (e instanceof DegradeException) {
+            msg = "降级了";
+        } else if (e instanceof ParamFlowException) {
+            msg = "热点参数限流";
+        } else if (e instanceof SystemBlockException) {
+            msg = "系统规则(负载/...不满足要求)";
+        } else if (e instanceof AuthorityException) {
+            msg = "授权规则不通过";
+        }
+        // http状态码
+        response.setStatus(500);
+        response.setCharacterEncoding("utf-8");
+        response.setHeader("Content-Type", "application/json;charset=utf-8");
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+
+        new ObjectMapper()
+                .writeValue(
+                        response.getWriter(),
+                        ResultData.error(-1, msg)
+                );
+    }
+}

+ 63 - 8
4dkankan-common/src/main/java/com/fdkankan/common/exception/GlobalExceptionHandler.java

@@ -1,11 +1,15 @@
 package com.fdkankan.common.exception;
 
 import cn.hutool.core.exceptions.ExceptionUtil;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fdkankan.common.constant.ErrorCode;
 import com.fdkankan.common.constant.ServerCode;
 import com.fdkankan.common.exception.BusinessException;
 import com.fdkankan.common.response.ResultData;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
 import org.springframework.validation.BindException;
 import org.springframework.validation.BindingResult;
 import org.springframework.validation.FieldError;
@@ -16,6 +20,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 
 /**
@@ -30,23 +35,73 @@ public class GlobalExceptionHandler {
      */
     @ResponseBody
     @ExceptionHandler(value = Exception.class)
-    public ResultData exceptionHandler(HttpServletRequest httpServletRequest, Exception e) {
-        log.error("服务错误:", e);
-        return ResultData.error(ServerCode.SYSTEM_ERROR.code(), ExceptionUtil.stacktraceToString(e, 3000));
+    public void exceptionHandler(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Exception e) throws Exception {
+        try{
+            log.error("服务错误:", e);
+//            return ResultData.error(ServerCode.SYSTEM_ERROR.code(), ExceptionUtil.stacktraceToString(e, 3000));
+            // http状态码
+            httpServletResponse.setStatus(500);
+            httpServletResponse.setCharacterEncoding("utf-8");
+            httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
+            httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
+
+            new ObjectMapper()
+                    .writeValue(
+                            httpServletResponse.getWriter(),
+                            ResultData.error(ServerCode.SYSTEM_ERROR.code(), ExceptionUtil.stacktraceToString(e, 3000))
+                    );
+        }finally {
+            //最后抛出异常是为了sentinel熔断异常处理器能捕获到异常,进入熔断处理逻辑
+            throw  e;
+        }
+    }
+
+    /**
+     * 限流熔断异常处理
+     */
+    @ResponseBody
+    @ExceptionHandler({FlowException.class, DegradeException.class})
+    public ResultData flowExceptionHandler(HttpServletRequest httpServletRequest, Exception e) {
+        String requestURI = httpServletRequest.getRequestURI();
+        String resource = null;
+        if(e instanceof FlowException){
+            resource = ((FlowException) e).getRule().getResource();
+        }
+        if(e instanceof DegradeException){
+            resource = ((DegradeException) e).getRule().getResource();
+        }
+
+        log.error("请求限流:requestURI{},resource:{}", requestURI, resource);
+        return ResultData.error(ServerCode.SYSTEM_ERROR.code(), "系统繁忙,请稍后重试");
     }
 
+//    /**
+//     * 限流熔断异常处理
+//     */
+//    @ResponseBody
+//    @ExceptionHandler(value = FlowException.class)
+//    public ResultData flowExceptionHandler(HttpServletRequest httpServletRequest, FlowException e) {
+//        String requestURI = httpServletRequest.getRequestURI();
+//        String resource = e.getRule().getResource();;
+//        log.error("请求限流:requestURI{},resource:{}", requestURI, resource);
+//        return ResultData.error(ServerCode.SYSTEM_ERROR.code(), "系统繁忙,请稍后重试");
+//    }
+
+
+
+
     /**
      * 校验错误拦截处理
      */
     @ResponseBody
     @ExceptionHandler({BindException.class,MethodArgumentNotValidException.class})
-    public ResultData validationBodyException(Exception exception) {
+    public ResultData validationBodyException(Exception e) {
         BindingResult result = null;
-        if(exception instanceof MethodArgumentNotValidException){
-            result = ((MethodArgumentNotValidException) exception).getBindingResult();
+        if(e instanceof MethodArgumentNotValidException){
+            result = ((MethodArgumentNotValidException) e).getBindingResult();
         }
-        if(exception instanceof BindException){
-            result = ((BindException) exception).getBindingResult();
+        if(e instanceof BindException){
+            result = ((BindException) e).getBindingResult();
         }
         String message = "";
         if (result.hasErrors()) {