尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringCloud-限流

SpringCloud-限流 企业级 Spring Cloud 项目里限流一般不会只靠某一个组件而是根据流量入口、服务类型和限流目标做分层限流。一、最常见在 Gateway 网关层限流企业项目通常会把第一层限流放在 Gateway。┌── user-service 客户端 ↓ ├── order-service Gateway ────────────┼── product-service ↑ │ 限流例如1 秒最多允许 1000 个请求进入系统超过1001、1002、1003...直接返回HTTP 429 Too Many Requests这样可以防止大量请求继续进入后面的微服务。二、Spring Cloud Gateway 怎么限流Gateway 最经典的方案就是Redis RequestRateLimiter例如spring: cloud: gateway: routes: - id: user-service uri: lb://userservice predicates: - Path/user/** filters: - name: RequestRateLimiter args: key-resolver: #{KeyResolver} redis-rate-limiter.replenishRate: 100 redis-rate-limiter.burstCapacity: 200这里核心是replenishRate 100 burstCapacity 200你可以先简单理解成控制请求进入 Gateway 的速度同时允许一定程度的突发流量。底层通常使用Redis Lua来保证限流判断的原子性。【注意】在业务代码里不需要写任何 Redis 操作代码。因为这里的 Redis 操作已经被 Spring Cloud Gateway 的RequestRateLimiter封装好了。你只需要配置 Redis ↓ 配置 RequestRateLimiter ↓ Gateway 内部自动操作 Redis这里的KeyResolver 也是需要自己写的因为RequestRateLimiter虽然帮你完成了“限流”这件事但是它不知道一个关键问题“我到底应该按照什么维度来限流”是按照 IP用户接口还是 Token所以这个需要开发人员告诉它。代码如下Bean public KeyResolver ipKeyResolver() { return exchange - Mono.just( exchange.getRequest() .getRemoteAddress() .getAddress() .getHostAddress() ); }从当前 HTTP 请求中拿到客户端的 IP 地址然后把这个 IP 作为限流的 Key。比如用户192.168.1.100发起请求GET /user/100这个ipKeyResolver最终就返回192.168.1.100于是 Gateway 就知道这次请求属于192.168.1.100这个限流对象。整个过程就是exchange ↓ 当前 HTTP 请求 ↓ getRemoteAddress() ↓ 客户端网络地址 ↓ getAddress() ↓ IP地址对象 ↓ getHostAddress() ↓ 192.168.1.100如果换成按照用户限流呢那你就不应该取 IP而是取用户 ID。比如你的请求经过认证以后请求头里面有X-User-Id: 10001那么可以写成Bean public KeyResolver userKeyResolver() { return exchange - Mono.just( exchange.getRequest() .getHeaders() .getFirst(X-User-Id) ); }三、企业里真正重要的是按照“谁”限流比如不能简单地说整个 Gateway 每秒只能 1000 个请求。因为这样太粗暴。企业里通常会根据业务设计限流 Key。例如① 按用户限流userId 10001 ↓ 1秒最多 10 次用户 A10 req/s用户 B10 req/s互不影响。② 按 IP 限流例如IP192.168.1.100 ↓ 1秒最多 100 次适合登录 验证码 注册这些容易被恶意刷的接口。③ 按接口限流比如POST /order/create限制1000 req/s而GET /product/list可能允许5000 req/s因为两个接口的重要程度和流量完全不同。④ 按用户 接口限流这在实际项目里也很常见userId API例如用户10001 /order/create ↓ 10 req/s而用户10001 /product/list ↓ 100 req/s不同接口使用不同的额度。四、为什么企业项目喜欢在 Gateway 限流因为 Gateway 是所有外部请求进入微服务系统的统一入口。例如┌── user-service │ 客户端 → Gateway ──┼── order-service │ └── product-service如果没有网关限流客户端 ↓ 100万请求 ↓ Gateway ↓ 100万请求全部进入微服务 ↓ 数据库 ↓ 数据库扛不住 ↓ 整个系统雪崩如果 Gateway 先限流客户端 ↓ 100万请求 ↓ Gateway ↓ 只允许1万请求进入 ↓ 微服务 ↓ 数据库所以Gateway 限流是保护整个微服务系统的第一道防线。五、但是企业级项目不会只有 Gateway 限流因为有些流量不一定经过 Gateway。例如服务 A ↓ 服务 B服务之间的内部调用A → B可能不需要经过 Gateway。所以还需要服务内部限流。六、服务内部常用 SentinelGateway 的限流通常是整个系统入口的粗粒度保护而 Sentinel 可以针对具体微服务、具体接口、具体资源做更细粒度的保护。例如Gateway ↓ 总共允许 5000 QPS但是进入订单服务以后/order/create → 1000 QPS /order/query → 3000 QPS /order/cancel → 500 QPS这样就更加精细。在 Spring Cloud Alibaba 体系里一个非常经典的方案就是Sentinel例如Gateway ↓ Sentinel ↓ Order Service ↓ 数据库Sentinel 可以做流量控制熔断降级系统保护热点参数限流例如/order/create限制1000 QPS超过之后直接拒绝或者进行降级6-1、代码层面的实现在网关层如 Spring Cloud Gateway做完粗粒度的全局限流后进入具体微服务内部使用 Sentinel 做细粒度限流、热点参数限流和熔断降级是生产环境中非常经典的“漏斗式”防护架构。在代码层面这主要依赖于Sentinel 提供的核心注解SentinelResource。以下是现实工程中的标准落地步骤和代码实现。1. 基础准备引入依赖与配置首先在微服务的pom.xml中引入 Sentinel 依赖并在配置文件中连接到 Sentinel Dashboard控制台。!-- 引入 Spring Cloud Alibaba Sentinel -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency# application.yml spring: cloud: sentinel: transport: dashboard: 127.0.0.1:8080 # 指定 Sentinel 控制台地址 port: 8719 # 客户端与控制台通信的端口默认 8719如果被占用会自动 12. 核心接口限流与熔断降级在实际业务代码中我们使用SentinelResource来定义资源。它有两个极为重要的属性分别应对不同的场景blockHandler专门处理Sentinel限流/降级规则触发抛出的BlockException。fallback处理业务代码自身抛出的异常类似 try-catch 兜底一般处理熔断逻辑。import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.stereotype.Service; Service public class OrderService { /** * 场景一核心接口限流与熔断 * value createOrder 定义资源名称在 Sentinel 控制台中将以此名字配置规则 */ SentinelResource( value createOrder, blockHandler handleCreateOrderBlock, fallback handleCreateOrderFallback ) public OrderResponse createOrder(OrderRequest request) { // 模拟业务逻辑可能会因为下游服务超时而抛出异常 if (request.getUserId() null) { throw new IllegalArgumentException(参数异常); } return new OrderResponse(200, 订单创建成功); } /** * 限流触发的兜底逻辑 (BlockHandler) * 注意1. 必须是 public; 2. 返回值必须与原方法一致; 3. 参数列表必须与原方法一致且最后增加一个 BlockException 参数。 */ public OrderResponse handleCreateOrderBlock(OrderRequest request, BlockException ex) { // 通常这里会返回类似 当前下单人数过多请稍后再试 的提示 return new OrderResponse(429, 系统繁忙已被限流); } /** * 业务异常/熔断触发的兜底逻辑 (Fallback) * 注意参数和返回值规则同上最后一个参数可以是 Throwable */ public OrderResponse handleCreateOrderFallback(OrderRequest request, Throwable ex) { // 通常在这里做降级处理比如读取本地缓存或者返回系统开小差提示 return new OrderResponse(500, 创建订单失败系统降级 ex.getMessage()); } }3. 热点参数限流防刷防暴击热点参数限流Param Flow是指针对方法里的某个特定参数进行限流。比如秒杀场景下限制某个productId1秒内最多只能被查询 100 次而其他普通商品不限制。在代码中不需要特殊的注解配置依然使用SentinelResource关键在于方法签名的参数。import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class ProductController { /** * 场景二热点参数限流 * 假设控制台配置对索引为 0 的参数即 productId进行限流阈值为 QPS10。 * 当配置了“例外项”时可以设定比如 productId999某爆款的阈值为 QPS500。 */ GetMapping(/product/detail) SentinelResource(value getProductDetail, blockHandler hotParamBlockHandler) public String getProductDetail( RequestParam(value productId, required false) Long productId, RequestParam(value userId, required false) Long userId) { return 返回商品详情 productId; } // 热点参数限流兜底方法 public String hotParamBlockHandler(Long productId, Long userId, BlockException ex) { return 商品 productId 太火爆啦当前访问人数过多请稍后再试; } }4. 生产环境进阶做法业务代码解耦如果把兜底逻辑blockHandler和fallback全写在业务类里代码会变得非常臃肿。现实开发中我们通常会将其抽离到独立的类中。抽离后的业务类Service public class PaymentService { SentinelResource( value pay, // 指定处理限流的外部类和方法 blockHandlerClass GlobalBlockHandler.class, blockHandler handlePaymentBlock, // 指定处理异常/熔断的外部类和方法 fallbackClass GlobalFallbackHandler.class, fallback handlePaymentFallback ) public String pay(Long orderId) { return 支付成功; } }独立的兜底处理类必须是static方法public class GlobalBlockHandler { // 方法必须是 static 的 public static String handlePaymentBlock(Long orderId, BlockException ex) { return 支付接口已被限流请排队重试; } } public class GlobalFallbackHandler { // 方法必须是 static 的 public static String handlePaymentFallback(Long orderId, Throwable ex) { return 支付服务发生故障已自动熔断降级; } }总结工作流在代码层面打好SentinelResource注解并写好兜底逻辑后服务启动。接下来所有的规则配置QPS阈值、热点参数索引、熔断时间窗等都不需要在代码里写死而是直接登录 Sentinel Dashboard 界面找到对应的资源名称如createOrder动态添加规则规则立即生效。七、业务代码级限流这个就不是一定使用 Gateway 或 Sentinel 了。有些非常特殊的业务操作会直接在业务代码里做控制。例如一个用户一分钟只能发送 3 次验证码。这种限制其实更接近业务规则。例如用户10001 ↓ 发送验证码 ↓ Redis记录 ↓ 1分钟内已经发送3次 ↓ 拒绝Redis 可能记录sms:10001 → 3然后设置TTL 60秒这种严格来说更常被称为业务防刷 / 业务限流而不是传统意义上的系统 QPS 限流。八、MQ 削峰这个严格来说MQ 不是限流。但是企业级高并发系统经常把它和限流一起使用所以你一定要区分。比如10万请求 ↓ Gateway限流 ↓ 微服务 ↓ MQ ↓ 消费者 ↓ MySQLMQ 的作用不是“拒绝请求”。而是把瞬间的大流量变成相对平稳的消费流量。例如瞬间 10万订单请求 ↓ MQ ↓ 消费者每秒处理1000个 ↓ 数据库这叫削峰填谷。所以MQ 是高并发场景下常用的削峰手段与限流配合使用。九数据库层保护到了数据库这里一般不会再说“使用 Gateway 限流。”而是从数据库自身和访问方式保护它。例如服务 ↓ 连接池 ↓ MySQL可以通过数据库连接池大小SQL 优化读写分离分库分表缓存降低数据库访问频率来保护数据库。例如10000 QPS ↓ Redis缓存 ↓ 只有100 QPS真正访问MySQL这其实是缓存削减数据库压力。也不严格属于“限流”但属于系统保护。十、企业级项目最典型的架构你现在可以把它理解成用户 ↓ ┌──────────────┐ │ CDN / WAF │ └──────┬───────┘ ↓ ┌──────────────┐ │ Gateway │ │ 入口限流 │ └──────┬───────┘ ↓ ┌────────────┼────────────┐ ↓ ↓ ↓ User Service Order Service Product Service ↓ ↓ ↓ Sentinel Sentinel Sentinel 接口限流 接口限流 接口限流 ↓ 热点参数限流 ↓ Redis ↓ MQ ↓ MySQL十一、面试的时候不要说成“有六层限流”这是非常重要的。因为实际上Gateway Sentinel Redis业务防刷 MQ 数据库它们解决的问题并不完全一样。你最好把它们分成三类① 真正的“限流”Gateway ↓ RequestRateLimiter 微服务 ↓ Sentinel② 业务防刷验证码 登录 注册 秒杀 点赞 评论通常Redis Lua / 业务逻辑③ 高并发系统保护Redis缓存 MQ削峰 熔断降级 线程池隔离 数据库连接池这些不一定叫“限流”但都是为了防止流量把系统压垮。十二、总结如果面试官问“你们项目中是怎么做限流的”你可以回答我们通常采用分层的流量治理方式。首先在 Gateway 网关层做统一的入口限流例如使用 Spring Cloud Gateway 的 RequestRateLimiter 配合 Redis对外部请求进行粗粒度限流请求进入具体微服务后再使用 Sentinel 针对具体接口、资源以及热点参数进行更细粒度的限流和熔断降级。对于验证码、登录、秒杀等业务还会结合Redis做用户维度的业务防刷。对于突发的大流量场景则会结合 MQ 进行削峰Redis 缓存降低数据库压力。要特别记住一个概念Gateway限流 ↓ 保护整个系统入口 Sentinel限流 ↓ 保护具体微服务和接口 业务防刷 ↓ 保护具体业务规则 MQ ↓ 削峰不是传统意义上的限流 Redis缓存 ↓ 减少数据库压力不是传统意义上的限流十三、比如秒杀系统怎么做假设商品库存100突然10万人同时点击购买。你不能让10万人 ↓ Gateway ↓ Order Service ↓ MySQL这样数据库很容易被打爆。一般会10万人 ↓ Gateway限流 ↓ 只放一部分请求 ↓ 业务层限流 ↓ Redis预扣库存 ↓ MQ削峰 ↓ 异步创建订单 ↓ MySQL这时候你会发现限流、缓存、消息队列、削峰、异步并不是孤立的技术。它们实际上共同组成了高并发系统的保护体系。
返回列表