简介
1、什么是 Sentinel
是一个 SpringBoot项目。“哨兵”,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。它就是一个分布式系统的流量防卫兵。它相比 Hystrix 更加的强大。
-
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
-
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
-
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
-
完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
2、下载使用
2.1、下载
官网下载:https://github.com/alibaba/Sentinel/releases
选择想要的版本,然后下载 sentinel-dashboard-1.8.4.jar。
2.2、使用
启动后会提供一个 web平台,localhost:8080,账号密码默认 sentinel,就开启监控页面了。
2.2.1、放入 idea 中使用
下载下来是一个 jar包,可以在项目下新建一个目录,然后把下载的 jar包放进去。
然后点击编辑配置,然后点 +,创建一个 JAR应用程序,然后为其命名,选择对应 jar 路径,填写完整对应的工作目录,然后在环境变量中修改其启动端口(默认 8080)。
2.2.2、在文件夹中使用
当然也可以直接去文件夹下 cmd,然后命令启动,如果是使用这种方法,写一个 bat 会方便:
java -Dserver.port=8888 -jar sentinel-dashboard-版本号.jar
3、流量控制
我们的服务不可能无限制地接受和处理客户端的请求,如果不加以限制,当发生高并发情况时,系统资源将很快被耗尽。
为了避免这种情况,我们就可以添加流量控制(也可以说是限流)。当一段时间内的流量到达一定的阈值的时候,新的请求将不再进行处理,这样不仅可以合理地应对高并发请求,同时也能在一定程度上保护服务器不受到外界的恶意攻击。
3.1、实现策略
-
快速拒绝:既然不再接受新的请求,那么我们可以直接返回一个拒绝信息,告诉用户访问频率过高。
-
预热:依然是基于方案一,但是由于某些情况下高并发请求是在某一时刻突然到来,我们可以缓慢地将阈值提高到指定阈值,形成一个缓冲保护。
-
排队等待:不接受新的请求,但是也不直接拒绝,而是进队列先等一下,如果规定时间内能够执行,那么就执行,要是超时就算了。
3.2、判断是否超过流量阈值的算法
3.2.1、漏桶算法
3.2.1.1、思想
就像一个桶开了一个小孔,水流进桶中的速度肯定是远大于水流出桶的速度的,桶是有容量的,所以当桶的容量已满时,就装不下水了,这时就只有丢弃请求了。
3.2.2、令牌桶算法
3.2.2.1、思想
有点像信号量机制。现在有一个令牌桶,这个桶是专门存放令牌的,每隔一段时间就向桶中丢入一个令牌(速度由我们指定)。当新的请求到达时,将从桶中删除令牌(即该请求先去删除令牌才能放行,相当于请求拿到了令牌),接着请求就可以通过并给到服务,但是如果桶中的令牌数量不足,那么不会删除令牌,而是让此数据包等待。
3.2.3、固定时间窗口算法
3.2.3.1、思想
对某一个时间段内的请求进行统计和计数,比如在13:14到 13:15这一分钟内,请求量不能超过 100,也就是一分钟之内不能超过 100次请求。
但是因为时间是固定的,所以只要时间间隔够长,在临界点附近就会存在安全隐患。比如 13:14:59 瞬间来了 100个请求,然后 13:15:01 又瞬间来了 100个请求,那么就是 3秒内 200个请求,这就与我们的初衷背道而驰了。
3.2.4、滑动时间窗口算法
3.2.4.1、思想
相对于固定窗口算法,滑动时间窗口算法更加灵活,它会动态移动窗口,重新进行计算。时间间隔是固定的,但是临界点是根据请求到达的时间动态调整的。但是会消耗更多资源。
即从请求到的时刻计算前 1分钟内的流量计数,如果加起来超过了 100,那就不允许请求通过。
3.3、控制台实现流量控制
点击管理界面左侧的 “簇点链路”,然后就可以展示我们所有的 API接口,点击右侧的 “流控”按钮,然后选择并设置想要的效果就可以创建流量控制规则了。可以点击左侧“流控规则”去看到。
3.3.1、流控模式
-
直接:只针对于当前 API接口
-
关联:当同一个服务的其他接口超过阈值时,会导致当前接口被限流
-
链路:更细粒度的限流,能够精确到具体的方法。即不局限于 API接口了,但是需要使用 @SentinelResource注解开启。选择链路后,可以指定一个入口资源,这个入口资源可以是路径的别名,比如 /user/{id},那入口资源可以写成 /user1/{id},访问此路径就相当于访问同一个 API接口,然后快速访问 /user/{id} 不会被限流,快速访问 /user1/{id} 会被限流。
3.3.1.1、简单实现方法限流
有该注解的方法就会被监控,即“埋点”,启动后在sentinel控制台中,通过指定的名字,可以对该名字对应的资源的访问进行控制。
被使用了 @SentinelResource注解的方法,会让 Sentinel控制台监控此方法,无论被谁执行都在监控范围内。这个注解可以使用在任何方法上。
这个注解还可以指定服务降级后使用的方法等等,有 blockHandler、fallback 等等参数。blockHandler 和 fallback 这俩个属性都可以用来配置一个指定的方法。blockHandler 是细粒度的配置, fallback 就相当于 Hystrix,会立即做降级。如果同时出现,以 fallback 为准。
blockHandler属性指定的方法,一般用于 sentinel控制台中设置的流量控制规则或服务降级规则生效后所调用的方法。
fallback属性指定的方法,这里表示当前方法调用后,如果调用失败了报错或者调用时间超时了,那么会自动调用 fallback 指定的方法。
spring: application: name: borrowservice cloud: sentinel: transport: dashboard: localhost:8858 # 关闭 Context收敛,这样被监控的方法可以进行不同链路的单独控制 web-context-unify: false @Service public class BorrowServiceImpl implements BorrowService{ @Override // 指定的 value 是自定义名称,默认会将方法名作为名称,在控制台中显示 @SentinelResource("getBorrow") public UserBorrowDetail getUserBorrowDetailByUid(int uid) { List<Borrow> borrow = mapper.getBorrowsByUid(uid); User user = userClient.getUserById(uid); List<Book> bookList = borrow .stream() .map(b -> bookClient.getBookById(b.getBid())) .collect(Collectors.toList()); return new UserBorrowDetail(user, bookList); } }
3.3.2、系统规则
左侧菜单栏有那个“系统规则”,点击后可以设置系统的阈值类型。
-
Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
-
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
-
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
-
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
-
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
4、限流和异常处理
之前在控制台中设置的流量控制,限流返回的界面是默认的,我们可以进行自定义。
4.1、设置限流页面
4.1.1、Controller 中的方法
@RequestMapping("/blocked") JSONObject blocked(){ JSONObject object = new JSONObject(); object.put("code", 403); object.put("success", false); object.put("massage", "您的请求频率过快,请稍后再试!"); return object; }
4.1.2、application.yml
spring: cloud: sentinel: transport: dashboard: localhost:8858 web-context-unify: false # 将刚刚编写的请求映射设定为限流页面 block-page: /blocked
4.2、@SentinelResource方法限流
之前使用 @SentinelResource注解方法时,当某个方法被限流时,会直接在后台抛出异常,这是因为没有去指定处理异常的方法,就直接抛出了 BlockException异常。
blockHandler属性,指定在限流后抛出异常时,哪个方法来处理该异常,blockHandler 只能处理限流情况下抛出的异常,不能处理方法本身抛出的其他类型异常。
fallback属性,如果是方法本身抛出的其他类型异常,不是 BlockException异常,可以通过此参数进行指定对异常的替代方案。
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
4.2.1、blockHandler
@Override @SentinelResource(value = "getBorrow", blockHandler = "blocked") public UserBorrowDetail getUserBorrowDetailByUid(int uid) { // ... } // 替代方案,注意参数和返回值需保持一致,并且参数最后需额外添加一个 BlockException public UserBorrowDetail blocked(int uid, BlockException e) { return new UserBorrowDetail(null, Collections.emptyList()); }
4.2.2、fallback
@RequestMapping("/test") @SentinelResource(value = "test", // fallback指定出现异常时的替代方案 fallback = "except", // 忽略的异常,也就是说这些异常出现时不使用替代方案 exceptionsToIgnore = IOException.class) String test(){ throw new RuntimeException("HelloWorld!"); } // 替代方法的返回值和参数必须和原方法一致,最后可以添加一个 Throwable 作为参数接受异常 String except(Throwable t){ return t.getMessage(); }
4.3、热点参数限流
可以针对热点数据,即指定参数进行限流。
在 Sentinel控制台,左侧点击“热点规则”,点击“新增热点限流规则”,资源名就是 @SentinelResource注解中的名称,参数索引就是指定对哪个索引下标的参数进行限流,还可以设置参数例外项,设置特例。
@RequestMapping("/test") @SentinelResource("test") String findUserBorrows2( @RequestParam(value = "a", required = false) String int a, @RequestParam(value = "b", required = false) String b, @RequestParam(value = "c",required = false) String c) { return "请求成功!a = "+a+", b = "+b+", c = "+c; }
5、熔断器和服务降级
当服务链路上,A 调用 B,若 B 出现故障,那么 A 中的大量请求短时间内就得不到响应,就会堆积,这时候就需要进行服务隔离。
当下游服务因为某种原因变得不可用或响应过慢时,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务而是快速返回或是执行自己的替代方案,这便是服务降级。
5.1、服务隔离方案
5.1.1、线程池隔离
线程池隔离实际上就是对每个服务的远程调用单独开放线程池,比如服务A 要调用服务B,在服务A 中创建线程池,因为线程池提供的线程数量是固定的,那么即使在短时间内出现大量请求,由于没有线程可以进行分配,并且资源相互隔离,就不会导致资源耗尽了。
5.1.2、信号量隔离
信号量隔离是使用 Semaphore类实现的,思想是相同的,也是限定能够同时进行服务调用的线程数量,但是它相对于线程池隔离,开销会更小一些,使用效果同样优秀,也支持超时等。Sentinel 也正是采用的这种方案实现隔离的。
5.2、熔断
5.2.1、熔断器工作过程
-
关闭:熔断器不工作,所有请求正常工作。
-
打开:熔断器工作,所有请求一律降级处理。
-
半开:尝试进行一下进行正常流程,要是还不行继续保持打开状态,否则关闭。
5.2.2、控制台设置熔断
点击控制台左侧“熔断规则”,点击右上角的“新增熔断规则”,
5.2.2.1、熔断策略
-
慢调用比例:如果出现那种半天都处理不完的调用,有可能就是服务出现故障,导致卡顿,这个选项是按照最大响应时间(RT)进行判定,如果一次请求的处理时间超过了指定的 RT,那么就被判定为慢调用,然后在一个统计时长内,如果请求数大于最小请求数,并且被判定为慢调用的请求比例已经超过阈值,那么将触发熔断。经过熔断时长之后,将会进入到半开状态进行试探。
-
异常比例:这个与慢调用比例类似,不过这里判断的是出现异常的请求的次数。
-
异常数:这个和“异常比例”的唯一区别就是,只要达到指定的异常数量,就熔断。
5.3、服务降级
Sentinel 的服务降级就是 @SentinelResource注解、blockHandler + 控制台的熔断规则。
6、远程服务调用支持 Sentinel
6.1、Feign 支持 Sentinel
6.1.1、实例1
6.1.1.1、配置文件
feign: sentinel: enabled: true
6.1.1.2、FeignClient接口和实现类
@FeignClient(value="user-service", fallback=UserClientFallback.class) public interface Userclient { @RequestMapping("/user/{uid}") User getUserById(@PathVariable("uid") int uid); } @Compontent public class UserClientFallback implements UserClient{ @Override public User getUserById(int uid) { User user = new User(); user.setName("我是替代方案"); return user; } }
6.2、传统的 RestTemplate 支持 Sentinel
@Bean @LoadBalanced @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class, fallback = "fallback", fallbackClass = ExceptionUtil.class) public RestTemplate restTemplate() { return new RestTemplate(); }
7、简单使用
7.1、实例1
引入依赖,修改配置文件之后,就是将服务连接到 Sentinel控制台,然后让其进行监控。
7.1.1、pom.xml
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
7.1.2、application.yml
spring: application: name: userservice cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: # 添加监控页面地址即可 dashboard: localhost:8858 # 开启 feign 对 sentinel 的支持 feign: sentinel: enabled: true
####
####