前言
在咱们平时工作中,总会有这样的事件产生:服务无奈接受过多的申请而被打挂。
个别咱们能够从两个方面解决:
- 减少节点,程度扩大(钱总是万能的)
- 对申请量过高的接口进行限流(没钱也不是不能够)
突发状况下咱们会先用第一种计划,而后再过渡到第二种。毕竟:穷就一个字
随着这样的事件产生多了,零碎就会能够预计的朝这样的方向演变:
单个接口的限流 -> 多个接口的限流
沉睡能力:限流能够配置,想要对哪个接口进行限流,就改下配置,立刻失效。
单个零碎须要限流 -> 多个零碎须要限流
沉睡能力:限流性能组件化,后续还有零碎须要限流性能,引入依赖即可,不须要反复开发。
- 等等
通过这样的推论:每个零碎都会产生高并发 -> 每个零碎都会朝这个方向演变 -> 总有演变了很久的零碎 -> 网上是否曾经存在这样的轮子?
别说,真的有!明天咱们要意识的配角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.测试
@Testpublic void testBlock() throws IOException { // 初始化规定 this.initFlowRules(); // 模仿高并发拜访 this.request(); // 统计qps this.count(); // 避免程序终止 System.in.read();}
5.测试后果
总体来说,测试后果合乎预期
思考
以上案例是最简略的入门案例,也是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);}
@Servicepublic class FooServiceImpl implements FooService { @Override public String hello(String name){ return "hello " + name; }}
3. 开启切面
@Configurationpublic class SentinelAspectConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); }}
4. 减少注解与限流解决
@SentinelResource(value = "hello", blockHandler = "exceptionHandler")@Overridepublic String hello(String name){ return "hello " + name;}public String exceptionHandler(String name, BlockException ex) { return "被限流了";}
blockHandler: 限流对应的解决办法,办法参数和返回值与业务办法雷同,对应着入门案例中的catch逻辑
对于SentinelResource
注解的更多信息:https://github.com/alibaba/Se...
5. 配置流控规定启动
@SpringBootApplicationpublic 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.后果
在页面上多刷新几次就将呈现咱们在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. 增加依赖
<!-- 通信依赖 --><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. 配置对立异样解决
@Slf4jpublic 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) { } }}
将处理器退出到拦截器中
@Configurationpublic 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'
关上控制台并登录
在簇点链路栏中能够看到呈现了方才拜访的资源地址
点击+流控
按钮
一个qps阈值为2的规定
再次测试,多刷几次:curl 'http://localhost:8080/foo/test'
配置未然失效
眼尖的小伙伴曾经发现了:右边的菜单栏好多规定能够配置,咱们下次再聊吧~
问题
的确,在退出控制台之后解决了之前的问题,然而又产生了新的问题,不晓得小伙伴有没有发现?
之前咱们的流控规定是写在代码里的,服务进行重启都会从新加载到内存中。
当初咱们把规定配置在sentinel控制台,由控制台推送到服务中。
留神:咱们启动sentinel时并没有配置过数据库,所以如果服务重启了,配置会隐没吗?
答案是会的,那么又怎么能力解决这个问题呢?
因为太久没更新过了,还没复原状态,明天的内容也挺多了,下次吧~
小结
明天介绍了Sentinel这个弱小的流量防护工具——尽管只是初窥门径,但不障碍大家感触到他的弱小之处。
咱们从一个最根本的案例登程,通过对上一个案例的思考,引出下一个案例的解决方案,循序渐进。
同时在最初,我还留下了一点点问题供大家思考,大家也能够上官网进行寻找解决方案。
最初,心愿大家有所播种~
下期:Sentinel控制台&整合SpringCloud
想要理解更多精彩内容,欢送关注公众号:程序员阿鉴
集体博客空间:https://zijiancode.cn