Sentinel 与 Semaphore 限流/并发控制
很多人第一次接触 Sentinel时,常见问题基本都是这几个:
Sentinel能不能做接口 QPS 限流Sentinel能不能按单个 IP 做限流Sentinel能不能做方法级并发数限制Sentinel和Semaphore该怎么选@SentinelResource里的blockHandler和fallback分别是什么
回答
1 Sentinel 适合做什么
Sentinel 适合做这些保护能力:
- 资源级 QPS 限流
- 热点参数限流
- 熔断降级
- 系统保护
- Dubbo / HTTP / 网关入口流控
2 Sentinel 不适合做什么
如果你的需求是:
同一时刻只允许 1 个线程进入某个方法,第二个线程必须稳定被拒绝
那 Sentinel 不是最合适的工具。
更直接的方案通常是:
SemaphoreReentrantLock- 分布式场景用 Redis 锁 / ZooKeeper 锁 / 数据库锁
3 选型
- 资源 QPS 限流:
Sentinel - 单 IP QPS 限流:
Sentinel 热点参数限流 - 单机内严格方法并发数限制:
Semaphore - 分布式互斥:分布式锁
Sentinel 的基本使用
你可以把 Sentinel 理解成:
给“资源”打标,然后基于规则去保护这个资源。
这里的“资源”可以是:
- 一个 HTTP 接口
- 一个 Service 方法
- 一个 Dubbo 调用
- 一个下游依赖调用
比如你定义一个资源名:
public static final String RESOURCE_NAME = "orderService#createOrder";
然后把它挂到一个方法上:
@SentinelResource(value = RESOURCE_NAME) public void createOrder() { // business logic }
后面只要给这个资源配置规则,Sentinel 就能对它做保护。
@SentinelResource 的进价使用
最常见写法:
@SentinelResource( value = RESOURCE_NAME, blockHandler = "handleBlock", fallback = "handleFallback" ) public DemoResult doWork(long sleepMs, boolean throwException) { if (throwException) { throw new IllegalStateException("demo fallback"); } return DemoResult.success(); }
对应处理方法:
public DemoResult handleBlock(long sleepMs, boolean throwException, BlockException ex) { return DemoResult.blocked("blocked by sentinel"); } public DemoResult handleFallback(long sleepMs, boolean throwException, Throwable ex) { return DemoResult.fallback("fallback: " + ex.getMessage()); }
blockHandler 和 fallback 的区别
这是使用 Sentinel 时最容易混淆的一点。
blockHandler
处理的是:
- 被 Sentinel 规则拦截
例如:
- QPS 超限
- 热点参数超限
- 线程数超限
- 熔断触发
本质上是:
业务逻辑还没真正执行,Sentinel 先把请求挡住了
fallback
处理的是:
- 方法执行过程中的业务异常
例如:
- 主动
throw - 空指针
- 调下游失败
- 非法状态
本质上是:
业务已经进入方法体,只是执行过程中出错了
简单区分
blockHandler= 被 Sentinel 挡住了fallback= 业务自己跑崩了
一个重要的实践经验
blockHandler 的最后一个参数,推荐统一写:BlockException ex
而不是收窄到某个具体子类,比如: ParamFlowException ex
原因很简单:
- BlockException 是 Sentinel 阻断异常的统一父类
- 注解模式下用父类匹配最稳
- 如果签名写得过窄,可能会出现规则已经触发,但没匹配到
blockHandler,最后掉进fallback
所以通用建议是:
public DemoResult handleBlock(..., BlockException ex)
Sentinel 做普通 QPS 限流
需求:
某个接口或方法,每秒最多只允许通过 1 次
示例代码
@Slf4j @Service public class SentinelQpsDemoService { public static final String RESOURCE_NAME = "sentinelQpsDemoService#simulateQpsProtectedWork"; @SentinelResource( value = RESOURCE_NAME, blockHandler = "handleBlock", fallback = "handleFallback" ) public String simulateQpsProtectedWork(long sleepMs, boolean throwException) { if (throwException) { throw new IllegalStateException("demo fallback"); } sleepQuietly(sleepMs); return "SUCCESS"; } public String handleBlock(long sleepMs, boolean throwException, BlockException ex) { return "BLOCKED"; } public String handleFallback(long sleepMs, boolean throwException, Throwable ex) { return "FALLBACK"; } private void sleepQuietly(long sleepMs) { try { Thread.sleep(sleepMs); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new IllegalStateException(ex); } } }
规则配置
@Configuration public class SentinelQpsDemoConfig { @PostConstruct public void initRules() { FlowRule rule = new FlowRule(); rule.setResource(SentinelQpsDemoService.RESOURCE_NAME); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); FlowRuleManager.loadRules(Collections.singletonList(rule)); } }
预期行为
在同一秒内连续调用两次:
- 第一次:
SUCCESS - 第二次:
BLOCKED
Sentinel 做按 IP 的 QPS 限流
关键思路
Sentinel 并不是天然认识“IP”这个概念。
它做的是:
对某个方法参数做热点参数统计
所以如果你想按 IP 限流,本质上要做两步:
- 先从请求里解析出 IP
- 把 IP 作为方法参数传给 Sentinel
示例代码
@Slf4j @Service public class SentinelIpQpsDemoService { public static final String RESOURCE_NAME = "sentinelIpQpsDemoService#simulateIpQpsProtectedWork"; @SentinelResource( value = RESOURCE_NAME, blockHandler = "handleBlock", fallback = "handleFallback" ) public String simulateIpQpsProtectedWork(String clientIp, long sleepMs, boolean throwException) { if (throwException) { throw new IllegalStateException("demo fallback"); } sleepQuietly(sleepMs); return "SUCCESS: " + clientIp; } public String handleBlock(String clientIp, long sleepMs, boolean throwException, BlockException ex) { return "BLOCKED: " + clientIp; } public String handleFallback(String clientIp, long sleepMs, boolean throwException, Throwable ex) { return "FALLBACK: " + ex.getMessage(); } private void sleepQuietly(long sleepMs) { try { Thread.sleep(sleepMs); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new IllegalStateException(ex); } } }
8.3 热点参数规则
@Configuration public class SentinelIpQpsDemoConfig { @PostConstruct public void initRules() { ParamFlowRule rule = new ParamFlowRule(SentinelIpQpsDemoService.RESOURCE_NAME) .setParamIdx(0) .setCount(1); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); } }
这里的意思是:
- 资源名:
sentinelIpQpsDemoService#simulateIpQpsProtectedWork - 第
0个参数:clientIp - 每个不同的
clientIp各自独立做 QPS 统计 - 阈值:
1
Controller 里怎么传 IP
一个常见写法:
public String resolveClientIp(HttpServletRequest request) { String forwardedFor = request.getHeader("X-Forwarded-For"); if (forwardedFor != null && !forwardedFor.isBlank()) { return forwardedFor.split(",")[0].trim(); } String realIp = request.getHeader("X-Real-IP"); if (realIp != null && !realIp.isBlank()) { return realIp.trim(); } return request.getRemoteAddr(); }
然后调用:
String clientIp = resolveClientIp(request); service.simulateIpQpsProtectedWork(clientIp, 10, false);
预期行为
如果阈值是 1:
- 同一个 IP 连续两次请求:第二次应该
BLOCKED - 两个不同 IP 各请求一次:都应该成功
Sentinel 能不能做方法并发数限制
答案是:
- 能做“线程数保护”
- 但不建议把它当成“严格互斥锁”
示例规则
FlowRule rule = new FlowRule(); rule.setResource("demoMethod"); rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); rule.setCount(1);
为什么不建议当成严格互斥工具
很多人会把它理解成:
同一时间只能有 1 个线程进去,第二个一定被挡
但在实际测试里,你会发现这种理解过于理想化。
FLOW_GRADE_THREAD 更适合被理解为:
- 一种线程数维度的流控保护
- 而不是高精度互斥语义
如果需求是严格的:
当前 JVM 内,这个方法同一时刻最多执行 1 个
那直接用 Semaphore 更合适。
用 Semaphore 做严格方法并发控制
需求:
一个方法在单机内同一时刻只允许 1 个线程执行
示例代码
@Slf4j @Service public class SemaphoreConcurrentDemoService { private final Semaphore semaphore = new Semaphore(1); public String simulateSemaphoreProtectedWork(long sleepMs, boolean throwException) { boolean acquired = semaphore.tryAcquire(); if (!acquired) { return "BLOCKED"; } try { if (throwException) { throw new IllegalStateException("demo fallback"); } Thread.sleep(sleepMs); return "SUCCESS"; } catch (Throwable ex) { return "FALLBACK"; } finally { semaphore.release(); } } }
为什么它更适合做严格限制
Semaphore(1) 的语义非常直接:
- 只有 1 个许可
- 第一个线程拿到许可后
- 第二个线程
tryAcquire()会立刻失败
这个行为比 Sentinel FLOW_GRADE_THREAD 更接近“方法级互斥”的需求。
Sentinel 和 Semaphore 的对比
| 维度 | Sentinel | Semaphore |
|---|---|---|
| 主要用途 | 流控保护 | 并发互斥 |
| 适合做 QPS 限流 | 是 | 否 |
| 适合做热点参数限流 | 是 | 否 |
| 适合做严格方法并发控制 | 不推荐 | 是 |
| 支持熔断降级 | 是 | 否 |
| 适合做分布式流控体系 | 是 | 否 |
| 单机方法互斥实现成本 | 一般 | 很低 |
常见错误理解
误区一:Sentinel 天然支持“按 IP 限流”
不准确。
更准确的说法是:
Sentinel 可以通过“热点参数限流”来实现按 IP 限流
前提是你把 IP 作为参数传进去。
误区二:fallback 就是限流后的兜底
不准确。
限流后的兜底应该优先走:
blockHandler
fallback 是业务异常兜底。
误区三:线程数限制就等于严格锁
不准确。
线程数流控和互斥锁是两种不同的语义。
推荐测试方式
测普通 QPS
连续两次调用同一个资源:
String first = service.simulateQpsProtectedWork(10, false); String second = service.simulateQpsProtectedWork(10, false);
预期:
- 第一次
SUCCESS - 第二次
BLOCKED
测同 IP 限流
String first = service.simulateIpQpsProtectedWork("10.10.10.1", 10, false);
String second = service.simulateIpQpsProtectedWork("10.10.10.1", 10, false);
预期:
- 第一次
SUCCESS - 第二次
BLOCKED
测不同 IP 独立统计
String first = service.simulateIpQpsProtectedWork("10.10.10.1", 10, false);
String second = service.simulateIpQpsProtectedWork("10.10.10.2", 10, false);
预期:
- 两次都成功
测 Semaphore 严格并发限制
使用两个线程同时调用
ExecutorService executor = Executors.newFixedThreadPool(2);
让第一个线程持有许可一段时间,预期第二个线程稳定返回 BLOCKED。
写业务代码时的建议
什么时候选 Sentinel
如果你的目标是:
- 限 QPS
- 做服务保护
- 做热点参数流控
- 做熔断降级
优先选 Sentinel。
什么时候选 Semaphore
如果你的目标是:
- 单机方法内严格并发数限制
- 当前时刻只允许 1 个线程执行业务
优先选 Semaphore。
blockHandler 建议统一写法
建议:
public ReturnType handleBlock(原方法参数..., BlockException ex)
fallback 建议统一写法
建议:
public ReturnType handleFallback(原方法参数..., Throwable ex)
总结
一句话:
Sentinel 的强项是流控保护,Semaphore 的强项是严格并发互斥;QPS、热点参数、熔断这些问题交给 Sentinel,单机内“同一时刻只能执行 1 个”的问题交给 Semaphore。
可以只记这 4 条:
Sentinel非常适合做 QPS 限流- 按 IP 限流本质是“热点参数限流”
blockHandler处理 Sentinel 拦截,fallback处理业务异常- 严格方法并发数限制优先用
Semaphore

浙公网安备 33010602011771号