1. 这不是教科书里的“分布式锁Demo”而是一线系统里真正扛住秒杀流量的那套组合拳你肯定见过这样的代码RLock lock redisson.getLock(order:123); lock.lock(); try { /* 处理订单 */ } finally { lock.unlock(); }——写得漂亮跑在本地IDE里也丝滑。但当真实流量涌进来订单创建接口QPS冲到800、库存扣减并发超2000时这套逻辑大概率会在凌晨三点把你从被窝里拽出来。这不是危言耸听而是我过去三年在多个高并发电商中台项目里反复验证过的事实单靠Redisson的RLock根本撑不住真实业务场景它必须和阻塞队列、线程池、重试机制、降级策略捆在一起才能成为生产环境里真正可靠的“安全阀”。今天要讲的就是这套组合拳怎么搭、为什么这么搭、每个环节踩过哪些坑。关键词很明确Java、Redis、Redisson、分布式锁、BlockingQueue、线程池——它们不是孤立的技术点而是一条完整的“并发控制流水线”。适合谁看如果你正在写一个需要强一致性的支付回调、库存扣减、优惠券发放模块或者正被“超卖”“重复下单”“线程阻塞雪崩”问题折磨那这篇就是为你写的。它不讲Redisson API有多少个方法只告诉你在订单号为ORD-20240521-88921的请求进来时系统内部到底发生了什么、哪个环节该加日志、哪个参数调错0.1秒就会让整条链路卡死。我经历过最典型的一次故障某次大促前压测库存服务在QPS 1200时开始出现大量RedisTimeoutException监控显示Redis连接池耗尽但Redis服务器本身CPU和内存都只有40%。排查三天才发现问题不在Redis而在应用层——线程池配置了corePoolSize10但每个请求获取分布式锁后又去调用一个同步HTTP库存接口这个接口平均RT 300ms10个线程全卡在等待响应上新请求全堵在BlockingQueue里队列满后拒绝策略直接抛异常前端看到的就是“系统繁忙”。后来我们把线程池拆成两层一层专管锁竞争小核心短超时一层专管下游调用大核心异步回调再配合锁自动续期和队列容量动态预估才稳住。所以你看标题里这五个词从来就不是并列关系而是有主次、有先后、有依赖的协作体系。接下来我们就一层层剥开这个体系的真实构造。2. 整体设计思路为什么必须是“锁→队列→线程池”三级漏斗而不是简单套API2.1 单一锁机制的致命缺陷它解决的是“互斥”不是“拥塞”很多人对分布式锁的理解停留在“防止并发修改同一数据”这一层这没错但远远不够。Redisson的RLock本质是一个分布式互斥原语它的核心价值是保证“同一时刻只有一个线程能进入临界区”。但它完全不关心“这个线程要在这个临界区里待多久”、“如果它卡住了其他线程要不要等”、“等多久算合理”。这就导致三个现实问题第一锁持有时间不可控。比如你在lock.lock()之后要查数据库、调第三方API、发MQ消息任何一个环节慢了锁就一直被占着。Redisson虽然支持看门狗自动续期默认30秒但如果业务逻辑本身执行超过30秒且中间没有I/O阻塞触发续期心跳锁还是会被释放导致其他线程误入——这就是典型的“锁提前失效”。第二无排队管理能力。lock.lock()是阻塞式获取线程会一直挂起直到拿到锁。当100个请求同时抢order:123这把锁第1个拿到剩下99个全在Redisson客户端线程里等着。这些等待线程本身不消耗CPU但会占用JVM线程栈默认1MB/线程99个就是近100MB内存更严重的是一旦Redis网络抖动这些线程可能集体超时醒来瞬间发起99次重试形成“惊群效应”把Redis打挂。第三缺乏背压与熔断。系统资源CPU、内存、下游接口是有限的但锁机制本身不提供任何限流、降级、拒绝策略。它像一个没有闸门的水龙头上游水流多大它就放多大直到下游管道爆裂。提示Redisson的tryLock(long waitTime, long leaseTime, TimeUnit unit)看似能控制等待时间但waitTime只是客户端层面的等待它无法感知下游服务的实际负载。比如你设waitTime500ms但第1个线程拿锁后处理了600ms后面99个线程全在500ms后失败退出业务上就是99次“下单失败”用户体验极差。2.2 阻塞队列给并发请求装上“缓冲水箱”实现可控排队解决方案不是抛弃锁而是给锁前面加一道“缓冲”。这道缓冲就是BlockingQueue。它的作用非常具体将无序、瞬时、不可控的并发请求转化为有序、平滑、可度量的队列任务。注意这里说的不是Redis里的List或Stream做队列而是JVM进程内的java.util.concurrent.BlockingQueue比如ArrayBlockingQueue或LinkedBlockingQueue。为什么因为Redis队列如LPUSH/BRPOP引入了额外的网络IO和序列化开销对于高频、低延迟的锁竞争场景它反而成了瓶颈。而JVM内队列插入和取出都是纳秒级操作零序列化零网络延迟。我们真正需要排队的是“申请锁”这个动作本身而不是“执行业务逻辑”。所以标准做法是所有请求先入JVM队列由一个或多个专用消费者线程从队列里取任务、申请锁、执行业务、返回结果。这个设计带来了三个关键收益可控的并发度队列容量maxQueueSize就是系统能容忍的最大积压请求数。超过这个数你可以选择throw exception、return fail、降级为异步处理而不是让线程无限等待。平滑的流量整形即使上游突发1000QPS只要队列没满消费者线程就能以自己稳定的节奏比如每秒处理200个消化掉避免下游被冲垮。清晰的监控指标队列长度、消费者线程数、平均等待时间、拒绝率——这些全是可量化、可告警的核心SLO指标。而单纯看Redis的KEYS lock:*你根本不知道系统压力在哪。2.3 线程池为队列消费者提供“动力引擎”实现资源隔离与弹性伸缩有了队列就必须有消费者。这个消费者不能是new Thread()随手起的必须是受管控的线程池。为什么必须用线程池因为单线程消费者会成为新的瓶颈。假设队列里有1000个任务单线程一个一个处理每个耗时100ms全部处理完要100秒用户早关页面了。而用10个线程的池子并行处理理论耗时降到10秒。但线程池的配置绝不是拍脑袋定的。它必须和队列、锁、业务特性深度耦合。我们来看一个真实配置案例// 库存扣减专用线程池 ThreadPoolExecutor inventoryExecutor new ThreadPoolExecutor( 4, // corePoolSize最小常驻线程数对应最低保障吞吐 16, // maxPoolSize最大线程数应对流量高峰 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new ArrayBlockingQueue(200), // 与BlockingQueue联动容量200 new ThreadFactoryBuilder().setNameFormat(inventory-consumer-%d).build(), new RejectedExecutionHandler() { Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 拒绝策略记录日志 发送告警 调用降级逻辑如返回“库存紧张请稍后再试” log.warn(Inventory queue is full, rejecting task: {}, r); AlertService.send(inventory_queue_full); fallbackDeduction(); } } );这个配置背后有严密的计算逻辑corePoolSize4基于P95业务处理耗时85ms和目标吞吐47 QPS反推47 ≈ 1000ms / 85ms * 4。意思是4个线程刚好能维持每秒47次稳定处理。maxPoolSize16按大促峰值QPS180预估180 ≈ 1000ms / 85ms * 15.3向上取整为16。queue capacity200这是最关键的联动参数。它必须大于maxPoolSize * avgResponseTime16 * 85ms ≈ 1360ms即队列能容纳约1.3秒的峰值流量。200是留出30%余量后的安全值。拒绝策略不是CallerRunsPolicy让调用线程自己执行因为那会污染业务线程导致整个Web容器线程池被拖垮。必须是主动降级告警。注意千万不要把所有业务共用一个线程池库存、订单、支付必须分池。我见过一个项目把所有异步任务扔进同一个Executors.newFixedThreadPool(10)结果支付回调失败导致线程卡死连带库存扣减也全挂——这就是典型的“故障域未隔离”。2.4 五要素协同一张图看清它们如何咬合成一个闭环现在我们把Java语言载体、Redis存储底座、Redisson锁封装、BlockingQueue排队中枢、线程池执行引擎这五个要素放在一次真实的库存扣减请求生命周期里看它们如何协作请求接入Nginx转发POST /api/inventory/deduct?skuId1001count1到应用节点A。入队Controller层不直接操作Redis而是构建一个DeductTask对象含skuId、count、traceId调用blockingQueue.offer(task)。如果队列满立即执行降级逻辑。消费线程池中的某个工作线程比如inventory-consumer-3从队列poll()出一个DeductTask。加锁该线程调用redisson.getLock(inventory:sku:1001).tryLock(3, 30, TimeUnit.SECONDS)。3秒是最大等待时间防死等30秒是锁租期防业务卡死。执行拿到锁后执行真正的库存扣减SQLUPDATE inventory SET stock stock - 1 WHERE sku_id 1001 AND stock 1并检查affectedRows 1。释放与清理无论成功失败都unlock()成功则发MQ通知下游失败则记录补偿任务。监控埋点在每个环节入队耗时、锁等待耗时、SQL执行耗时、总耗时打点上报到Prometheus。这七个步骤环环相扣。BlockingQueue是流量入口的“节流阀”线程池是动力系统的“变速箱”Redisson锁是临界区的“安全门”Redis是门禁系统的“中央数据库”Java是整个系统的“操作系统”。少一个系统就缺一块骨头。3. 核心细节解析从代码到配置每一个参数都决定生死3.1 Redisson客户端配置不是默认值就能用连接池和超时是命门Redisson不是拿来即用的库它的客户端配置直接影响锁的可靠性和系统稳定性。很多团队线上事故根源都在Config对象配错了。我们来看一份经过生产验证的最小化安全配置Config config new Config(); // 1. 使用单机模式生产环境推荐哨兵或集群此处简化 config.useSingleServer() .setAddress(redis://192.168.1.100:6379) .setPassword(your_password) // 生产必须设密码 .setConnectTimeout(3000) // 连接建立超时3秒足够 .setTimeout(2000) // 命令执行超时2秒这是关键 .setRetryAttempts(3) // 网络失败重试次数 .setRetryInterval(1500); // 重试间隔1.5秒 // 2. 连接池配置——最容易被忽视的性能杀手 config.useSingleServer() .setConnectionPoolSize(64) // 连接池最大连接数不是越大越好 .setConnectionMinimumIdleSize(24); // 最小空闲连接数保障低峰期快速响应 // 3. 线程池配置——影响锁续期和心跳 config.setThreads(16) // Redisson内部处理命令的线程数建议CPU核数*2 .setNettyThreads(32); // Netty IO线程数建议CPU核数*4 RedissonClient redisson Redisson.create(config);为什么setTimeout20002秒如此关键因为Redisson的tryLock底层是EVAL脚本它包含SETNXEXPIRE两个原子操作。如果Redis响应慢于2秒这个命令就会被客户端主动中断抛出RedisTimeoutException。而这个异常必须被上层捕获并走降级否则用户看到的就是500错误。我们曾在线上发现因Redis主从同步延迟EVAL脚本在从节点执行超时导致锁获取失败率飙升。将setTimeout从默认的3秒降到2秒配合重试反而提升了成功率——因为2秒内没响应大概率是网络抖动重试比死等更有效。connectionPoolSize64也不是随便写的。计算公式是maxPoolSize * (avgCommandsPerTask 1)。我们的库存线程池maxPoolSize16每个任务平均发3个Redis命令tryLock、unlock、publish所以16 * 4 64。如果池子太小如3216个线程抢32个连接必然排队太大如128又浪费连接资源增加Redis服务器连接数压力。实操心得上线前务必用redis-cli --latency测试Redis PING延迟确保P99 5ms。如果延迟高setTimeout必须相应调大但绝不能超过业务SLA比如下单接口要求99% 1s那setTimeout最多设800ms。3.2 分布式锁的正确用法tryLock不是万能钥匙leaseTime必须精算Redisson的RLock提供了lock()和tryLock()两个主要方法。lock()是无条件阻塞生产环境绝对禁止使用。tryLock(long waitTime, long leaseTime, TimeUnit unit)才是唯一合法入口。但它的三个参数每个都有深意waitTime客户端等待锁可用的最长时间。设为0表示不等待立即返回true/false设为3000表示最多等3秒。强烈建议设为非0值但不超过1秒。理由如果锁被一个慢任务长期持有你等3秒毫无意义不如快速失败走降级。我们线上统一设为500500ms。leaseTime锁的自动释放时间租期。设为-1表示永不过期靠看门狗续期这是默认行为。但永不过期是危险的。看门狗续期依赖nettyThreads线程定时发送PTTL命令如果这个线程被GC停顿或CPU打满续期失败锁就会意外释放。所以更安全的做法是显式设置一个合理的租期比如3000030秒并确保你的业务逻辑100%能在30秒内完成。怎么保证在业务方法开头打日志log.info(deduct start, cost: {}ms, System.currentTimeMillis() - startTime)监控P99耗时租期设为P9920%余量。TimeUnit单位必须是TimeUnit.MILLISECONDS避免用SECONDS导致租期被放大1000倍tryLock(3, 30, SECONDS)实际是30秒但tryLock(3, 30, MILLISECONDS)是30毫秒后者显然不合理。一个典型的、经过压测验证的锁获取代码public boolean tryAcquireLock(String lockKey, long waitTimeMs, long leaseTimeMs) { RLock lock redisson.getLock(lockKey); try { // 关键waitTimeMs设为500leaseTimeMs设为30000 boolean isLocked lock.tryLock(waitTimeMs, leaseTimeMs, TimeUnit.MILLISECONDS); if (!isLocked) { log.warn(Failed to acquire lock for key: {}, waitTime: {}ms, lockKey, waitTimeMs); metrics.counter(lock.acquire.fail).increment(); return false; } log.debug(Successfully acquired lock for key: {}, lockKey); metrics.counter(lock.acquire.success).increment(); return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error(Interrupted while acquiring lock for key: {}, lockKey, e); return false; } }注意tryLock方法会抛出InterruptedException必须捕获并正确处理Thread.currentThread().interrupt()否则会破坏线程中断状态导致后续await()等操作异常。3.3 BlockingQueue选型与容量规划ArrayBlockingQueue为何胜过LinkedBlockingQueueJVM内队列ArrayBlockingQueue和LinkedBlockingQueue是两大主力。很多教程说“Linked无界更灵活”但在高并发控制场景ArrayBlockingQueue是唯一选择。原因有三第一内存确定性。Array是数组创建时就分配好固定内存new ArrayBlockingQueue(200)占用约200 * 40字节 8KB不会因任务对象增多而动态扩容OOM。而Linked是链表每个节点都是一个Node对象包含item、next引用在高并发下会疯狂创建对象触发频繁GC甚至OOM。我们曾在一个日均亿级请求的系统里把队列从Linked换成ArrayFull GC频率从每小时3次降到0。第二性能可预测。Array的offer()和poll()是纯数组索引操作无锁、无CAS吞吐稳定。Linked的offer()需要CAS更新tail指针在高并发下CAS失败率高性能抖动大。第三强制容量意识。Array必须指定容量这迫使你在设计阶段就思考“我的系统最大能承受多少积压”。而Linked的“无界”是假象它最终会吃光堆内存。生产环境里“无界队列”是重大隐患。容量怎么定不是拍脑袋。我们用一个真实公式queueCapacity (peakQPS × avgResponseTimeMs) ÷ 1000 × safetyFactor其中peakQPS历史最高或压测峰值QPS比如180avgResponseTimeMs锁业务逻辑的平均耗时比如120ms从tryLock开始到unlock结束safetyFactor安全系数通常1.2~1.5我们取1.3。代入(180 × 120) ÷ 1000 × 1.3 28.08向上取整为32。但这是理论最小值还要考虑监控粒度和运维习惯最终定为200200是2的幂JVM对齐友好。提示ArrayBlockingQueue的offer(E e)方法返回boolean代表是否入队成功。你必须检查这个返回值如果返回false说明队列已满必须立刻执行降级逻辑如返回{code:503,msg:系统繁忙}而不是忽略或抛异常。3.4 线程池参数的黄金三角coreSize、maxSize、queueSize必须联动计算线程池的三个核心参数不是独立配置的而是一个“黄金三角”必须联动计算。错误的组合会导致两种极端一种是资源浪费线程太多空转一种是性能雪崩线程太少队列爆满。我们用一个表格展示不同组合下的真实表现corePoolSizemaxPoolSizequeueSize场景表现问题诊断22200低峰期CPU 10%高峰期队列积压拒绝率15%coreSize太小无法消化基础流量1616200低峰期CPU 80%频繁GC线程上下文切换高coreSize太大空转浪费资源41650高峰期队列瞬间满拒绝率40%大量告警queueSize太小缓冲不足416500高峰期无拒绝但平均等待时间达800ms用户体验差queueSize太大掩盖了处理能力瓶颈最优解是coreSize保障基线吞吐maxSize应对峰值queueSize提供平滑缓冲三者满足queueSize ≈ maxSize × avgResponseTimeMs ÷ 1000 × 1.3。以我们库存服务为例基线QPS47 →coreSize ceil(47 × 0.085) 40.085是85ms换算成秒峰值QPS180 →maxSize ceil(180 × 0.085) 16queueSize 16 × 85 ÷ 1000 × 1.3 ≈ 176→ 取200这个三角一旦确定就不要轻易改动。我们曾因临时调大maxSize到32导致线程数暴增jstack一看32个线程全在java.net.SocketInputStream.socketRead0上等待MySQL响应——这才是真正的瓶颈不是线程不够是DB连接池满了。所以调参前先看监控再算公式最后验证。4. 实操过程详解从零搭建一个可落地的库存扣减服务4.1 环境准备与依赖引入Maven坐标和版本锁定项目基于Spring Boot 2.7.xJDK 11所有依赖版本必须严格锁定避免传递依赖冲突。以下是pom.xml中关键部分properties redisson.version3.23.2/redisson.version lettuce.version6.2.7.RELEASE/lettuce.version /properties dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Redisson 客户端 -- dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version${redisson.version}/version /dependency !-- Lombok 简化代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- Micrometer 监控 -- dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency /dependencies特别注意redisson-spring-boot-starter的版本必须与redisson核心库一致。我们曾因starter用3.22.0redisson用3.23.2导致RedissonAutoConfiguration类找不到setCodec()方法启动失败。版本锁定是上线前的第一道防线。4.2 Redisson配置类用ConfigurationProperties实现外部化把Redisson配置从代码里抽出来放到application.yml便于不同环境dev/test/prod差异化管理# application.yml redisson: address: redis://192.168.1.100:6379 password: your_password connect-timeout: 3000 timeout: 2000 retry-attempts: 3 retry-interval: 1500 connection-pool-size: 64 connection-minimum-idle-size: 24 threads: 16 netty-threads: 32对应的Java配置类Configuration EnableConfigurationProperties(RedissonProperties.class) public class RedissonConfig { Bean(destroyMethod shutdown) public RedissonClient redissonClient(RedissonProperties properties) { Config config new Config(); SingleServerConfig server config.useSingleServer() .setAddress(properties.getAddress()) .setPassword(properties.getPassword()) .setConnectTimeout(properties.getConnectTimeout()) .setTimeout(properties.getTimeout()) .setRetryAttempts(properties.getRetryAttempts()) .setRetryInterval(properties.getRetryInterval()); server.setConnectionPoolSize(properties.getConnectionPoolSize()); server.setConnectionMinimumIdleSize(properties.getConnectionMinimumIdleSize()); config.setThreads(properties.getThreads()); config.setNettyThreads(properties.getNettyThreads()); return Redisson.create(config); } } Data ConfigurationProperties(prefix redisson) public class RedissonProperties { private String address; private String password; private int connectTimeout; private int timeout; private int retryAttempts; private int retryInterval; private int connectionPoolSize; private int connectionMinimumIdleSize; private int threads; private int nettyThreads; }这样运维同学只需改YAML无需动代码就能调整所有Redisson参数。4.3 核心服务类InventoryService的完整实现这是整个架构的“心脏”。我们把它拆成三个层次Task任务定义、Consumer消费者、Service业务服务。第一步定义任务对象Data Builder AllArgsConstructor NoArgsConstructor public class DeductTask { private String skuId; private Integer count; private String traceId; // 全链路追踪ID private long createTime; // 创建时间戳用于计算等待耗时 }第二步构建阻塞队列和线程池Component public class InventoryQueueManager { // 队列容量200与线程池maxSize联动 private final BlockingQueueDeductTask queue new ArrayBlockingQueue(200); // 线程池配置见前文 private final ThreadPoolExecutor executor; public InventoryQueueManager() { this.executor new ThreadPoolExecutor( 4, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(200), new ThreadFactoryBuilder().setNameFormat(inventory-consumer-%d).build(), new InventoryRejectedHandler() // 自定义拒绝策略 ); } // 入队方法必须检查返回值 public boolean submit(DeductTask task) { if (task null) return false; task.setCreateTime(System.currentTimeMillis()); boolean success queue.offer(task); if (!success) { log.warn(Inventory queue is full, task rejected: {}, task); metrics.counter(inventory.queue.full).increment(); } return success; } // 启动消费者线程 PostConstruct public void startConsumers() { for (int i 0; i 4; i) { // 启动4个常驻消费者 executor.execute(new InventoryConsumer(queue, redissonClient)); } } }第三步消费者线程实现Slf4j public class InventoryConsumer implements Runnable { private final BlockingQueueDeductTask queue; private final RedissonClient redissonClient; public InventoryConsumer(BlockingQueueDeductTask queue, RedissonClient redissonClient) { this.queue queue; this.redissonClient redissonClient; } Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { DeductTask task queue.poll(1, TimeUnit.SECONDS); // 非阻塞取避免线程饿死 if (task null) continue; long waitTimeMs System.currentTimeMillis() - task.getCreateTime(); metrics.timer(inventory.queue.wait.time).record(waitTimeMs, TimeUnit.MILLISECONDS); // 执行扣减 boolean result deductInventory(task); if (result) { metrics.counter(inventory.deduct.success).increment(); } else { metrics.counter(inventory.deduct.fail).increment(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } catch (Exception e) { log.error(Unexpected error in inventory consumer, e); metrics.counter(inventory.consumer.error).increment(); } } } private boolean deductInventory(DeductTask task) { String lockKey inventory:sku: task.getSkuId(); RLock lock redissonClient.getLock(lockKey); try { // 尝试获取锁500ms等待30秒租期 boolean isLocked lock.tryLock(500, 30000, TimeUnit.MILLISECONDS); if (!isLocked) { log.warn(Failed to acquire lock for sku: {}, traceId: {}, task.getSkuId(), task.getTraceId()); return false; } // 执行数据库扣减 int affectedRows inventoryMapper.deductStock(task.getSkuId(), task.getCount()); if (affectedRows ! 1) { log.warn(Inventory insufficient for sku: {}, required: {}, traceId: {}, task.getSkuId(), task.getCount(), task.getTraceId()); return false; } // 扣减成功发MQ通知 mqProducer.sendDeductSuccess(task.getSkuId(), task.getCount(), task.getTraceId()); return true; } catch (Exception e) { log.error(Error during inventory deduction for sku: {}, traceId: {}, task.getSkuId(), task.getTraceId(), e); return false; } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }第四步Controller暴露接口RestController RequestMapping(/api/inventory) public class InventoryController { Autowired private InventoryQueueManager queueManager; PostMapping(/deduct) public ResponseEntityMapString, Object deduct(RequestBody DeductRequest request) { DeductTask task DeductTask.builder() .skuId(request.getSkuId()) .count(request.getCount()) .traceId(MDC.get(X-B3-TraceId)) // 从MDC取链路ID .build(); boolean submitted queueManager.submit(task); if (!submitted) { // 队列满立即降级 MapString, Object resp new HashMap(); resp.put(code, 503); resp.put(msg, System busy, please try later); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(resp); } // 入队成功返回“受理中” MapString, Object resp new HashMap(); resp.put(code, 200); resp.put(msg, Request accepted, processing...); return ResponseEntity.ok(resp); } }这个Controller不执行任何业务逻辑只做两件事构建任务、入队。真正的扣减由后台消费者线程异步完成。这样接口响应时间稳定在5ms以内纯内存操作彻底解耦了“请求接入”和“业务处理”。4.4 监控与告警用Micrometer暴露关键指标没有监控的分布式系统就像没有仪表盘的飞机。我们在InventoryConsumer中埋点用Micrometer上报到PrometheusComponent public class InventoryMetrics { private final MeterRegistry meterRegistry; public InventoryMetrics(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; } public Counter counter(String name) { return Counter.builder(name).register(meterRegistry); } public Timer timer(String name) { return Timer.builder(name).register(meterRegistry); } public Gauge gauge(String name, AtomicInteger value) { return Gauge.builder(name, value, AtomicInteger::get).register(meterRegistry); } }关键指标包括inventory.queue.size队列当前长度Gaugeinventory.queue.wait.time任务在队列中平均等待时间Timerinventory.deduct.success/fail扣减成功/失败次数Counterlock.acquire.success/fail锁获取成功/失败次数Counter在Prometheus中我们可以写告警规则# 队列长度持续5分钟 150触发告警 redisson_inventory_queue_size 150 # 锁获取失败率 5%持续3分钟
阅读完成 · 觉得有帮助?