文章目录
一、Sentinel简介
1.sentinel介绍
Sentinel 是由阿里巴巴中间件团队开发的开源项目,是一种面向分布式微服务架构的轻量级高可用流量控制组件。
2.sentinel应用场景
Sentinel 主要以流量为切入点,从流量控制、熔断降级、系统负载保护、实时监控和控制台等多个维度来帮助用户提升服务的稳定性。
3.sentinel与hystrix
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 基于并发数 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 即将发布 | 支持 |
调用链路信息 | 支持同步调用 | 不支持 |
限流 | 基于 QPS / 并发数,支持基于调用关系的限流 | 不支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
实时监控 API | 各式各样 | 较为简单 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
4.sentinel组件介绍
(1)Sentinel 组成
①Sentinel 核心库:Sentinel 的核心库不依赖任何框架或库,能够运行于 Java 8 及以上的版本的运行时环境中,同时对 Spring Cloud、Dubbo 等微服务框架提供了很好的支持。
②Sentinel 控制台(Dashboard):Sentinel 提供的一个轻量级的开源控制台,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。
(2)基本概念
①资源:资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如由应用程序提供的服务或者是服务里的方法,甚至可以是一段代码。
Sentinel 定义资源的方式有下面几种:适配主流框架自动定义资源、通过 SphU 手动定义资源、通过 SphO 手动定义资源、注解方式定义资源。这个稍后会有使用方法教程。
其中注解方式定义资源@SentinelResource参数介绍如下:
参数 | 解释 |
---|---|
value | Sentinel资源的名称,我们不仅可以通过url进行限流,也可以把此值作为资源名配置,一样可以限流。 |
entryType | 条目类型(入站或出站),默认为出站(EntryType.OUT) |
resourceType | 资源的分类(类型) |
blockHandler | 块异常函数的名称,默认为空 |
blockHandlerClass | 指定块处理方法所在的类。默认情况下, blockHandler与原始方法位于同一类中。 但是,如果某些方法共享相同的签名并打算设置相同的块处理程序,则用户可以设置存在块处理程序的类。 请注意,块处理程序方法必须是静态的。 |
fallback | 后备函数的名称,默认为空 |
defaultFallback | 默认后备方法的名称,默认为空 |
defaultFallback用作默认的通用后备方法。 它不应接受任何参数,并且返回类型应与原始方法兼容 | |
fallbackClass | fallback方法所在的类(仅单个类)。默认情况下, fallback与原始方法位于同一类中。 但是,如果某些方法共享相同的签名并打算设置相同的后备,则用户可以设置存在后备功能的类。 请注意,共享的后备方法必须是静态的。 |
exceptionsToTrace | 异常类的列表追查,默认 Throwable |
exceptionsToIgnore | 要忽略的异常类列表,默认情况下为空 |
②规则:围绕资源而设定的规则。Sentinel 支持流量控制、熔断降级、系统保护、来源访问控制和热点参数等多种规则,所有这些规则都可以动态实时调整。
二、Sentinel使用说明
1.控制台Dashboard
(1)下载地址,选择sentinel-dashboard-1.8.6.jar
下载即可。
(2)打开命令窗口,进入jar包存放目录,使用java -jar sentinel-dashboard-1.8.2.jar
进行服务启动即可。
(3)登录网址:http://localhost:8080/,用户名密码为sentinel/sentinel
2.Sentinel 流量控制和熔断降级
(1)首先我们需要构建一个SpringBoot项目,项目结构如下:
(2)然后在pom.xml引入sentinel的依赖。
<!-- sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.1</version>
</dependency>
(3)application.yml配置
server:
port: 8800
spring:
#允许循环依赖
main:
allow-circular-references: true
application:
name: sentinel-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
dashboard: 127.0.0.1:8080
#指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer,
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
#port: 8719
#暴露/actuator/sentinel端点
management:
endpoints:
web:
exposure:
include: '*'
(4)SentinelApplication.java,我这里没有修改
package com.example.sentinel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SentinelApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelApplication.class, args);
}
}
(5)SentinelServerController.java,其中test()是对springboot的联通测试,resource1()是SphU定义资源测试,resource2()是流量监控测试,resource3()是熔断测试。
package com.example.sentinel.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.util.TimeUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.text.SimpleDateFormat;
import java.util.Date;
@Controller
public class SentinelServerController {
@RequestMapping(value = "/test")
@ResponseBody
public String test() {
return "Sentinel server";
}
@RequestMapping(value = "/resource1")
@ResponseBody
public String resource1() {
return resource1bySphU();
}
/**
* 通过 SphU 手动定义资源
* @return
*/
public String resource1bySphU() {
Entry entry = null;
try {
entry = SphU.entry("resource1bySphU");
//您的业务逻辑 - 开始
return "resource1";
//您的业务逻辑 - 结束
} catch (BlockException e1) {
//流控逻辑处理 - 开始
return "resource1 limit";
//流控逻辑处理 - 结束
} finally {
if (entry != null) {
entry.exit();
}
}
}
//
@RequestMapping(value = "/resource2")
//blockHandler 限流后走的方法
@SentinelResource(value="resource2byAnnotation",
blockHandler = "resource2Limit",fallback = "resource2Fallback")
@ResponseBody
public String resource2() {
return "resource2";
}
public String resource2Limit(BlockException exception){
return "您点击太快了,稍后重试!";
}
@RequestMapping(value = "/resource3/{id}")
//抛出异常时,提供 fallback 处理逻辑
//处于熔断开启状态时,原来的主逻辑则暂时不可用,会走fallback的逻辑。
//在经过一段时间(熔断时长)后,熔断器会进入探测恢复状态(HALF-OPEN),此时 Sentinel 会允许一个请求对原来的主业务逻辑进行调用
//若请求调用成功,则熔断器进入熔断关闭状态(CLOSED ),服务原来的主业务逻辑恢复,否则重新进入熔断开启状态(OPEN)
@SentinelResource(value="resource3byAnnotation",fallback = "resource3Fallback")
@ResponseBody
public String resource3(@PathVariable("id") int id) {
monitor();
System.out.println("主逻辑");
//这里模拟服务报错
int defaultId = 5;
if(id < defaultId){
throw new RuntimeException ("服务异常");
}
return "resource3";
}
//注意fallback返回值类型必须与原函数一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;
//fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
public String resource3Fallback (int id){
return "服务出错,请您先访问这里!";
}
/**
* 自定义事件监听器,监听熔断器状态转换
*/
public void monitor() {
EventObserverRegistry.getInstance().addStateChangeObserver("logging",
(prevState, newState, rule, snapshotValue) -> {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (newState == CircuitBreaker.State.OPEN) {
// 变换至 OPEN state 时会携带触发时的值
System.err.println(String.format("%s -> OPEN at %s, 发送请求次数=%.2f", prevState.name(),
format.format(new Date(TimeUtil.currentTimeMillis())), snapshotValue));
} else {
System.err.println(String.format("%s -> %s at %s", prevState.name(), newState.name(),
format.format(new Date(TimeUtil.currentTimeMillis()))));
}
});
}
}
(6)测试流程
首先启动springBoot项目,测试resource1方法是否能正常调通。此时在控制台可以看到服务已经监测成功。
然后测试resource2方法,先测试resource2方法是否能正常调通。然后在控制台的簇点链路的resource2对应的服务中选择新增流控指标,这里是每秒调用两次就会触发。
新增完后在快速点击该链接,发现流控实现成功
最后测试resource3方法,先测试resource3方法是否能正常调通。我这里设置传参5以上服务调用成功,否则失败。
然后在控制台新增熔断设置,选择策略为异常数,这里代表在一秒内有两个以上请求失败两次以上则会熔断。
可以看到服务调用失败时成功触发了熔断机制,并且正确的调用和错误的调用重复几次,可以看到熔断状态的变化
3.常见报错解决
(1)dashboard控制器启动失败:Web server failed to start. Port 8080 was already in use
找到该端口的id然后杀掉该进程即可
netstat -aon|findstr "8080"
taskkill /pid 11596 /f
(2)The dependencies of some of the beans in the application context form a cycle:
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration
┌─────┐
| com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration (field private java.util.Optional com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration.sentinelWebInterceptorOptional)
└─────┘
这是循环依赖问题,由于springBoot版本与cloud版本不匹配导致的,可以参考版本说明。
实在没有合适的版本可以采取粗暴的方式直接允许循环依赖。
spring:
#允许循环依赖
main:
allow-circular-references: true