第一步:没有 Sentinel 时,我们怎么写限流?
假设你有一个查询订单的接口。为了防止数据库被打垮,你想限制每秒最多 100 个请求查数据库。
最原始的思路是用一个计数器:
java
@Service
public class OrderService {
// 记录当前正在查数据库的请求数
private AtomicInteger counter = new AtomicInteger(0);
// 阈值写死
private static final int MAX = 100;
public Order queryOrder(Long orderId) {
// 1. 先判断当前请求数是否超过阈值
int current = counter.incrementAndGet();
if (current > MAX) {
counter.decrementAndGet();
throw new RuntimeException("系统繁忙,请稍后再试");
}
try {
// 2. 执行业务:查数据库
return orderMapper.selectById(orderId);
} finally {
// 3. 不管成功失败,计数器减回去
counter.decrementAndGet();
}
}
}
这种写法有什么弊端?
-
代码侵入严重:每个需要保护的方法都要复制粘贴这段计数器逻辑,业务代码和限流代码混在一起。
-
无法动态调整 :
MAX = 100写死在代码里,线上流量突增时想改成 200,必须重启应用。 -
只是粗糙的并发数限制:你无法按"每秒 QPS"限流,也无法实现"预热"、"匀速排队"等精细策略。
-
没有监控:你不知道现在实际 QPS 是多少,有没有触发限流,系统状态是黑盒。
-
没有熔断能力:如果数据库已经挂了,这段代码还是会不断尝试查库,浪费资源。
因为这些弊端,所以需要一个专门的限流框架来接管这些横切逻辑。 Sentinel 就是为此设计的。
第二步:Sentinel 的设计思路------它到底做了什么?
Sentinel 的核心思想是:你只管标记"这段代码需要保护",限流的判断、拦截、统计全部交给框架。
它抽象出三个核心概念:
| 概念 | 含义 | 为什么需要它 |
|---|---|---|
| 资源(Resource) | 被保护的代码块,用字符串命名,如 "queryOrder" |
没有资源名,框架不知道你要保护哪段代码 |
| 规则(Rule) | 对这个资源设置限制,如"每秒最多 100 次" | 没有规则,框架不知道限制条件是什么 |
| Slot Chain(处理链) | Sentinel 内部的责任链,依次做统计、限流判断、熔断判断 | 没有处理链,所有逻辑耦合在一起,无法扩展 |
执行流程是这样的:
java
你的业务方法
↓
@SentinelResource("queryOrder") ← 标记资源
↓
Sentinel 拦截
↓
Slot Chain 处理:
1. 统计当前 QPS
2. 检查是否满足限流规则
3. 如果不满足 → 抛出 BlockException
4. 如果满足 → 放行执行业务
↓
业务代码执行
第三步:Spring Boot 集成 Sentinel 的完整流程
3.1 引入依赖
Sentinel 为 Spring Boot 提供了专门的 starter,没有它,你需要手动配置 Sentinel 的初始化、AOP 切面、Web 过滤器等几十行代码。
XML
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sentinel 核心 + Spring Boot 自动配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2022.0.0.0</version>
</dependency>
<!-- 如果要使用 @SentinelResource 注解 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.8</version>
</dependency>
</dependencies>
为什么需要
sentinel-annotation-aspectj?因为
@SentinelResource是基于 AOP(面向切面编程)实现的。没有它,注解不会被拦截,限流逻辑无法织入你的方法。
3.2 配置文件
bash
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8858 # Sentinel 控制台地址(可选,用于可视化监控)
eager: true # 取消懒加载,应用启动时就初始化 Sentinel
为什么配置
eager: true?默认情况下 Sentinel 是懒加载的,第一次请求时才初始化。
如果第一次请求就是高并发,初始化期间的请求可能绕过限流规则。设置为 true 保证启动时就准备好。
3.3 定义资源(注解方式)
为什么不直接在代码里写 SphU.entry(),而是用注解?
因为注解通过 AOP 实现,限流逻辑和业务逻辑完全解耦。没有注解,你的每个方法都要写 try-catch,代码臃肿且难以维护。
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* value = "queryOrder":资源名,Sentinel 通过这个名称找到对应的限流规则
* blockHandler = "queryOrderBlock":被限流时执行的方法名
*/
@SentinelResource(
value = "queryOrder",
blockHandler = "queryOrderBlock"
)
public Order queryOrder(Long orderId) {
// 这里只有纯粹的业务逻辑,没有任何限流代码
return orderMapper.selectById(orderId);
}
/**
* 降级方法:当 queryOrder 触发限流时,会调用这个方法
* 参数必须与原方法一致,最后加一个 BlockException 参数
*/
public Order queryOrderBlock(Long orderId, BlockException ex) {
// 返回降级数据,而不是抛异常让前端报 500
Order fallbackOrder = new Order();
fallbackOrder.setOrderId(orderId);
fallbackOrder.setStatus("SYSTEM_BUSY");
return fallbackOrder;
}
}
blockHandler 为什么必须存在?
如果没有它,Sentinel 默认会抛出 FlowException,Spring Boot 会把这个异常转成 500 错误返回给前端。这对用户不友好。
blockHandler 让你有机会返回一个友好的降级结果(如"系统繁忙"提示)。
3.4 配置限流规则
规则告诉 Sentinel:资源 "queryOrder" 的限流条件是什么。
为什么不把规则写死在注解里?
因为规则需要动态调整。线上 QPS 阈值可能需要根据流量随时修改,如果写在注解里,改一个数字就要重新打包部署。
Sentinel 支持多种规则来源,初学者先理解代码硬编码 的方式(生产环境通常配 Nacos 动态推送)。
java
@Component
public class SentinelRuleConfig {
@PostConstruct
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
// 必须和 @SentinelResource 的 value 完全一致
rule.setResource("queryOrder");
// 限流类型:QPS 模式(还有线程数模式)
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 阈值:每秒最多允许 100 次请求
rule.setCount(100);
// 限流效果:直接拒绝(还有 WarmUp 预热、匀速排队等)
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
3.5 Controller 层调用
java
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
return orderService.queryOrder(orderId);
}
}
第四步:测试验证
启动应用后,Sentinel 已经生效。你可以用 JMeter 或写个简单脚本压测:
java
@Test
public void testLimit() throws InterruptedException {
for (int i = 0; i < 200; i++) {
new Thread(() -> {
Order order = orderService.queryOrder(1L);
System.out.println(order.getStatus());
}).start();
}
Thread.sleep(5000);
}
输出中你会看到:
-
前 100 个左右返回正常订单数据
-
后面的请求返回
SYSTEM_BUSY(即queryOrderBlock的降级结果)
第五步:为什么还需要 Sentinel 控制台?
上面的代码能跑,但有一个问题:规则是硬编码的,改阈值要重启应用。
所以 Sentinel 提供了控制台(Dashboard),它是一个独立的 Web 应用,可以让你:
-
实时查看每个资源的 QPS、通过量、拒绝量
-
动态修改限流规则,无需重启
-
配置熔断规则(当错误率超过阈值时自动熔断)
一般不需要安装一个特别复杂的软件,下载 Sentinel Dashboard 的 jar 包,然后启动它即可。
启动控制台:
java -jar sentinel-dashboard-1.8.8.jar --server.port=8858
然后在你的 application.yml 里配置 spring.cloud.sentinel.transport.dashboard=localhost:8858,应用就会向控制台注册。
默认登录账号通常是:sentinel
密码:sentinel
【注意】:只有配置 Spring Boot 项目连接 Dashboard
java
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8858
然后启动你的 Spring Boot 项目。
这时候你的项目会把 Sentinel 的资源、流量统计等信息发送给 Dashboard。
完整逻辑回顾
| 步骤 | 解决了什么问题 | 如果没有它,会怎样 |
|---|---|---|
引入 sentinel-spring-boot-starter |
自动完成 Sentinel 初始化 | 手动写几十行配置代码 |
使用 @SentinelResource |
通过 AOP 解耦限流与业务 | 每个方法复制粘贴 try-catch |
配置 blockHandler |
被限流时优雅降级 | 前端收到 500 错误 |
定义 FlowRule |
声明限流阈值和策略 | Sentinel 不知道限制条件,限流不生效 |
| 接入 Dashboard | 动态调整规则 + 监控 | 改规则必须重启,系统状态不可见 |