代码层面的流控
1、创建SpringBoot项目
pom信息如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR3</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
2、编写接口HelloController
@RestController public class HelloController { @GetMapping("/say-hello") @SentinelResource(value = "sayHello",blockHandler = "helloBlockHandler") public String sayHello() { return "hello,sentinel"; } public String helloBlockHandler(BlockException e){ return "此路不通!"; } }
切记,helloBlockHandler与主方法要保持一致,入参可以多一个BlockException
3、编写规则并依赖
这里面有两个实现方式:1、使用配置类初始化规则 2、使用SPI机制实现拓展点规则
1、使用配置类初始化规则
定义一个配置类SentinelConfig,代码如下:
@Configuration public class SentinelConfig { @Bean public SentinelResourceAspect sentinelResourceAspect(){ return new SentinelResourceAspect(); } @PostConstruct private void initRules() { List<FlowRule> rules=new ArrayList<FlowRule>(); FlowRule rule = new FlowRule(); rule.setResource("sayHello");//与@SentinelResource中的value保持一致 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); } }
2、使用SPI机制实现拓展点规则
①实现InitFunc方法,代码如下:
public class FlowRuleInitFunc implements InitFunc { public void init() throws Exception { List<FlowRule> rules=new ArrayList<FlowRule>(); FlowRule rule = new FlowRule(); rule.setResource("sayHello"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); } }
②在resources下创建META-INF/services/com.alibaba.csp.sentinel.init.InitFunc文件,然后把上面①中的代码所在类的路径添加进去即可
com.cb.springbootsentinel.func.FlowRuleInitFunc
4、启动验证
启动项目,访问:http://localhost:8080/say-hello
可以浏览器或者专门的API工具疯狂请求,就会发现刚开始是正常输出: hello,sentinel
由于限流,后面会出现:此路不通!
补充:使用Sentinel Dashboard控制台对接口直接进行流控
继续在上线的工程中编写,先去除所有的资源埋点(默认情况下,大多都是走控制台直接流控,有可视化页面,不用改代码,那为啥不用呢)
1、创建一个接口类DashController
代码如下:
@RestController public class DashController { @GetMapping("/dash") public String dash(){ return "dash"; } }
2、添加配置文件
#注册到dashboard服务名 spring.application.name=sentinel-server #dashboard访问地址 spring.cloud.sentinel.transport.dashboard=127.0.0.1:8080
3、进入控制台
找到sentinel-server服务,进入 簇点链路(这里会显示所有请求过的接口服务列表),如图
点击流控按钮,会有一个设置弹框,如下图:
为了方便显示效果,单机阈值填写1即可,其他都默认,然后点击新增,然后查看流控规则,会显示刚才我们新增的这条数据,如图:
4、启动服务
访问http://localhost:8080/dash
第一次请求会正常返回字符串:dash
如果频繁的刷新浏览器,最终会出现:Blocked by Sentinel (flow limiting)
就说明,控制台的流控操作到这里就完成且成功了
补充:自定义URL限流异常
类似于手机号白名单的操作,
默认情况下,URL层面的限流会直接返回:Blocked by Sentinel (flow limiting)
代码如下:
@Service
public class CustomUrlBlockHandler implements UrlBlockHandler {
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
httpServletResponse.setHeader("Content-Type","application/json;charset=UTF-8");
String msg="{\"code\":900,\"msg\":\"访问人数太多了,等会在请求!\"}";
httpServletResponse.getWriter().write(msg);
}
}
如果要跳转到中转页面,可以用下面的配置:
spring.sentinel.servlet.block-page.url=要跳转的url
然后重启服务,访问有流控的接口,会返回上述代码中自定义的json字符串的内容,如下:
补充:URL请求资源的清洗
也可以说是针对某些特殊接口的统一化
背景:流控,其实就是对http进行统计的,但是我们写接口,会遇到这种接口:/xxxx/xxx/{id},这种接口其实就一个,但是参数是可变的,所以也可以理解每次请求URL都是不一样的,这样的话,流控就没办法做统计了,看代码:
@RestController public class CleanController { @GetMapping("/clean/{id}") public String dash(@PathVariable("id") Integer id) { return "clean" + id; } }
这样的接口会造成两个问题:
①限流统计不准确,本来就是同一个接口,因为id的可变,导致每次统计的URL不一样
②导致资源数量过多,因为可变参数可能会很多,sentinel默认资源数是6000,如果id是>6000,如果正好也请求了>6000次,那多出来的资源将不会受到限流限制
所以针对这种接口,我们要做一个类似通配符的操作,让对应的这种接口都算在同一个接口下面:想上面的/clean/{id}这种接口全部规整到/clean/*下面,不管后面的参数怎么变,都算是/clean/*下面的请求,这样的话就可以统计了
@Service public class CustomUrlCleaner implements UrlCleaner { public String clean(String url) { if (url == null || url.equals("")) { return url; } else if (url.startsWith("/clean/")) { return "/clean/*"; } return url; } }
到这里其实就可以了,访问一下http://localhost:8080/clean/1,再去观察一下Sentinel Dashboard控制台的簇点链路,会出现如图的接口:
你在请求http://localhost:8080/clean/2,也只是有这一个clean/*,然后针对这个接口做流控即可,效果就不展示了哈
下图是以上所有代码的实现的项目结构,只供参考: