红包系统高并发架构:Redis Lua脚本与原子扣减实践 红包系统的核心挑战从来不是“算钱”而是在极短时间内让百万甚至千万用户同时对一个共享账户执行“抢”的动作并且保证每一笔操作都不出错、不超发、不丢单。如果你做过秒杀、抢购、优惠券发放这类高并发场景会很快发现红包系统和它们本质是一类问题热点资源、瞬时高并发、必须强一致。但红包系统又有自己的特殊性——它不是一个静态商品库存而是要在一开始就把总金额拆成多份再被并发领取同时它涉及资金对账和审计要求极高。这篇文章不空谈“架构图 一堆概念”而是把红包系统从发红包到拆红包、抢红包、入账、对账的完整链路拆开讲清楚每一层到底在扛什么压力、用什么技术思路化解、有哪些容易翻车的细节。文中给出可以直接参考的 Java 代码、Redis Lua 脚本、SQL 方案和排查清单哪怕你没有做过红包系统也可以把里面的思路迁移到秒杀、库存、活动奖励等场景。1. 千万并发场景下红包系统面临的核心挑战先给红包系统做一个抽象一次发红包行为实际上是把一个资金池拆分成 N 份然后允许海量用户并发领取。整个过程比普通秒杀复杂的地方在于普通秒杀是卖固定数量的商品库存是整数扣减相对直观。红包系统既要保证总金额不超发又要保证每份金额符合规则大于 0、不超过上限还要处理所有红包被抢完后的资金回退。千万并发一涌进来压力主要落在四个环节。1.1 热点账户写入所有红包金额最初都来自发红包用户的一个账户即使实际扣款已经提前完成系统在抢红包时面临的依然是同一个红包维度上的热点写操作。如果直接在数据库里对红包剩余金额做 update行锁会瞬间堆积数据库 CPU 和 IO 都会打满。这是第一个要解决的问题减少对单一数据行的直接并发写。1.2 红包金额拆分的正确性很多人会把红包金额拆分理解为简单的随机数生成其实不然。系统需要保证拆分出来的每份金额都大于最小单位比如 0.01 元。所有子红包金额之和等于红包总额。分配结果尽量随机避免前几个红包金额过大或过小影响体验。如果拆分逻辑写得不严谨很可能出现“最后一份金额为负数”或者“所有金额加起来不等于总金额”的事故这类问题在资金场景中是绝对不可接受的。1.3 并发领取时的原子性假设一个红包已经被抢走了 80 份此时还有几千个请求并发来抢系统必须保证同一时刻只有一个请求能成功扣减红包剩余金额。如果扣减动作不是原子的就会出现超抢现象系统最终发出的金额大于红包总额。要做到这一点单纯靠数据库悲观锁可以解决但性能不行单纯靠分布式锁又容易出现锁粒度过大拖垮整个红包操作的问题。更常见的做法是使用 Redis 的原子操作或者用 Lua 脚本保证“判断 - 扣减”这是一个原子步骤。1.4 数据一致性与对账红包系统涉及资金不能只用缓存里的数据就完事。常规方案是 Redis 处理瞬时流量数据库落最终账两者之间必然存在时间差。如果 Redis 突然宕机或者进程崩溃如何保证不丢单、不重复入账如果部分用户抢到了红包但异步记账失败如何发现并补偿这些问题都需要一套对账和补偿机制来处理。从这一层看红包系统的技术难点不单是“高并发”而是“高并发下的资金正确性”。2. 红包系统整体架构分层在设计方案之前先看整体架构。红包系统通常从前到后分为接入层、业务逻辑层、缓存层、存储层和异步对账层。层次核心职责典型技术选型关注重点接入层鉴权、限流、反作弊、负载均衡Nginx、网关、Sentinel入口流量管控业务逻辑层发红包、抢红包、查询红包状态Spring Boot 等业务服务无状态设计、幂等缓存层红包拆分结果、预扣库存、瞬时计数Redis原子性、过期策略、持久化存储层红包订单、用户明细、账户流水MySQL 分库分表幂等写入、资金安全异步层对账、回退、通知、补偿MQ、定时任务最终一致性写红包和抢红包需要分开设计原因在于两者的压力模型完全不同。写红包流程用户发起发红包请求。服务端校验账户余额和红包参数。调用账户服务冻结或扣减金额生成红包总单。按照拆分算法生成 N 个子红包写入 Redis 预置数据。返回红包 ID用户可以分享。抢红包流程用户点击红包请求进入服务。先做风控、重复校验等前置判断。从 Redis 中通过原子操作领取一个子红包。若领取成功发送消息到 MQ触发异步金额入账。用户查询领取结果时以数据库或 Redis 中状态为准。这个流程中有一个容易被新手忽略的点子红包的分配结果其实在发红包那一刻就已经在 Redis 里准备好了。抢红包请求只是在“拿一份现成的结果”而不是每次现算。这可以显著降低抢红包时的计算开销。3. 发红包阶段预拆分与预扣避开热点写发红包阶段面临的是“一次性创建大量数据”的问题。如果一个普通红包有 1000 个子红包发红包请求到来时要不要立刻在数据库里生成 1000 条记录从并发角度考虑答案是不需要。更推荐的做法是发红包接口在业务库中只生成一条“红包总单”把真实金额先冻结或扣除然后进入预拆分流程。拆分出来的结果并不会立即落到 MySQL而是先放到 Redis 中等用户真正抢到后再异步落库。这里有一个关键设计元红包预拆分的目的是把“写压力”从上亿元的账户和订单表转移到内存结构让每个抢红包请求都尽量只打到 Redis而不是数据库。3.1 预拆分红包的数据结构在 Redis 中每个红包可以维护一个列表List里面预先放好所有子红包金额也可以维护一个计数器剩余个数加上一张预生成结果的哈希表。两种方式各有优劣List 方式实现简单利用RPOP或LPOP原子弹出就好。Hash 计数器方式更灵活但要保证两个数据结构的操作在同一脚本里完成否则会出现个数不一致的问题。下面是一个简化版本的设计思路发红包时把每个子红包金额放入一个 Redis List同时设置剩余个数抢红包直接通过 Lua 脚本弹出并校验。3.2 代码示例预拆分结果写入 Redis这里用 Java 的 Redisson 客户端来做示例主要展示思路具体版本请以项目实际为准。// 文件路径src/main/java/com/example/redpacket/service/RedPacketService.java public String createRedPacket(String userId, BigDecimal totalAmount, int count) { // 1. 参数校验 if (totalAmount null || count 0) { throw new IllegalArgumentException(invalid params); } // 金额单位转换为分避免浮点误差 long totalCent totalAmount.multiply(BigDecimal.valueOf(100)).longValue(); // 2. 预生成子红包金额列表 ListLong amountList splitRedPacket(totalCent, count); // 3. 生成红包 ID String packetId IdGenerator.nextId(); // 4. 将子红包金额写入 Redis List String key red:packet:list: packetId; String countKey red:packet:count: packetId; String statusKey red:packet:status: packetId; RBatch batch redissonClient.createBatch(); // 按金额从大到小压入或者顺序压入均可 for (Long amount : amountList) { batch.getList(key).addAsync(amount); } batch.getAtomicLong(countKey).setAsync(count); batch.getBucket(statusKey).setAsync(ACTIVE); batch.execute(); // 5. 异步写入红包总单此处省略 MQ 发送代码 sendCreatePacketMessage(packetId, userId, totalCent, count); return packetId; }这里要注意拆分结果写入 Redis 和在数据库生成红包总单应该是两个步骤。生产中更稳妥的顺序是先尝试冻结账户资金如果资金不足直接返回失败资金冻结成功后再生成红包记录并写 Redis。如果 Redis 写入失败需要把冻结的资金解冻避免出现用户发红包失败但钱被扣了的情况。4. 拆分算法二倍均值法的原理与实现红包拆分算法直接影响用户体验和系统正确性。最常见的算法之一是“二倍均值法”它的规则如下每次拆红包时当前剩余金额为 M剩余红包个数为 N那么这次拆出的金额等于在(0, M/N * 2]区间内取一个随机数。换句话说当剩余 100 元、还有 10 个红包时本次金额上限是 20 元。这个算法能保证整体金额分布比较均匀同时每个红包金额不会过小。4.1 Java 实现下面是一个将总金额单位分拆成 count 份的 Java 实现避免浮点数精度问题全程使用 long 类型。// 文件路径src/main/java/com/example/redpacket/util/RedPacketSplitter.java /** * 二倍均值法拆红包 * * param totalCent 红包总金额单位分 * param count 红包个数 * return 每个红包的金额单位分 */ public static ListLong splitRedPacket(long totalCent, int count) { ListLong result new ArrayList(count); long remainingCent totalCent; int remainingCount count; for (int i 0; i count - 1; i) { // 当前可分配上限二倍均值 long max remainingCent / remainingCount * 2; // 随机生成一个大于0且不超过max的金额 long amount ThreadLocalRandom.current().nextLong(1, max 1); result.add(amount); remainingCent - amount; remainingCount--; } // 最后一个红包拿走全部剩余金额 result.add(remainingCent); return result; }这段逻辑看起来简单但有几处细节值得深挖。第一个细节nextLong(1, max 1)的边界问题。如果max算出来等于 1那本次只能抢 1 分钱没问题但如果剩余金额和剩余个数之间出现特殊情况比如remainingCent已经很小而max可能小于 1代码就会抛异常。所以这里必须保证remainingCent remainingCount即在入口处做一次校验。第二个细节最后一个红包直接取剩余金额这可能让最后一个红包金额比前面的都大。实际产品中可以再做一次乱序处理避免“手气最佳一定在最后”的规律。第三个细节整个算法的正确性可以用数学归纳法验证。每次分配后剩余金额和剩余个数的关系依然满足拆分条件最后所有分配金额相加一定等于总金额。如果用了浮点数随机几乎没法保证加起来恰好等于总额这也是为什么一定要用long类型。4.2 边界场景上面的实现还需要加强几个边界判断public static ListLong splitRedPacketSafe(long totalCent, int count) { if (count 0) { throw new IllegalArgumentException(count must be positive); } // 保证每个人至少能分到 0.01 元 if (totalCent count) { throw new IllegalArgumentException(total cent must count); } // 如果只有一个人直接返回全部金额 if (count 1) { return Collections.singletonList(totalCent); } return splitRedPacket(totalCent, count); }从工程角度看拆红包结果最好在发红包时一次性生成并保存不要在抢红包的过程中实时计算。原因很简单实时计算需要并发控制否则两个请求可能生成两个相同金额的子红包一次性预生成则天然规避了这个问题。5. 抢红包阶段Redis Lua 脚本保证原子扣减抢红包操作是红包系统并发量最大的环节。每个请求过来后系统需要执行两个动作判断红包是否还有剩余。如果有剩余弹出一个子红包金额并把剩余个数减一。这两个动作必须是一个原子操作。如果在 Java 代码里先 get 再 pop中间一定能插入并发请求导致超抢。如果靠 Redis 的事务或者分布式锁又可能会把并发性能拉下来。最常用也最优雅的方案就是 Lua 脚本。Redis 执行 Lua 脚本时是原子的脚本执行期间不会被其他命令打断。5.1 Lua 脚本示例-- 文件路径src/main/resources/lua/grabRedPacket.lua -- KEYS[1] : red:packet:list:{packetId} 子红包金额列表 -- KEYS[2] : red:packet:count:{packetId} 剩余个数 -- KEYS[3] : red:packet:status:{packetId} 红包状态 -- 返回值 -- nil 或 -1 表示红包已被抢完 -- -2 表示红包已过期或不存在 -- 正数表示抢到的金额单位分 local status redis.call(GET, KEYS[3]) if not status or status ~ ACTIVE then return -2 end local amount redis.call(RPOP, KEYS[1]) if not amount then return -1 end local left redis.call(DECR, KEYS[2]) if left 0 then -- 极端情况个数被减到负数需要回滚 redis.call(LPUSH, KEYS[1], amount) redis.call(INCR, KEYS[2]) return -1 end return tonumber(amount)脚本中先检查红包状态再弹出一个金额最后递减个数。这里之所以把个数维护成单独字段是为了方便快速统计剩余数量以及配合定时任务回退。5.2 Java 调用 Lua 脚本用 Spring Data Redis 或者 Redisson 都可以执行 Lua 脚本下面展示一个基于 Spring Data Redis 的调用示例。// 文件路径src/main/java/com/example/redpacket/service/GrabRedPacketService.java Service public class GrabRedPacketService { Autowired private StringRedisTemplate redisTemplate; Autowired private RedPacketSplitter splitter; // DefaultRedisScript 需要全局定义减少重复创建 private static final DefaultRedisScriptLong GRAB_SCRIPT new DefaultRedisScript(); static { GRAB_SCRIPT.setLocation(new ClassPathResource(lua/grabRedPacket.lua)); GRAB_SCRIPT.setResultType(Long.class); } public Long grabRedPacket(String userId, String packetId) { // 1. 使用 Lua 脚本原子获取金额 ListString keys Arrays.asList( red:packet:list: packetId, red:packet:count: packetId, red:packet:status: packetId ); Long amountCent redisTemplate.execute(GRAB_SCRIPT, keys); // 2. 没抢到或者红包已过期 if (amountCent null || amountCent 0) { return null; } // 3. 抢到后发送 MQ 消息触发异步入账 sendGrabSuccessMessage(userId, packetId, amountCent); // 4. 也可以同步写一个内存缓存加速用户查询 return amountCent; } }抢到红包后的入账操作不一定要同步执行。如果把数据库写入放在抢红包请求的链路内Redis 带来的性能优势会立刻被打折扣。更常见的做法是抢红包接口只返回“是否成功、抢到多少金额”然后通过 MQ 异步完成入账、明细写入、通知等操作。5.3 为什么不用分布式锁使用分布式锁也可以保证并发安全但锁的粒度如果是一个红包所有抢这个红包的请求都会被串行化性能上限很低。如果锁的粒度是数据库行那数据库又扛不住。Lua 脚本的好处在于在 Redis 内部完成“判断 扣减”无需上下文切换。一个红包一个 key天然支持不同红包并行处理。脚本执行是原子的不会出现中间态被其他请求读到。代价是红包的预置数据必须完全在 Redis 中这要求 Redis 本身的可靠性和持久化配置要够强。后面会专门讲这个问题。6. 数据库层设计分库分表与幂等写入红包系统的数据库表至少包括红包总单表、红包明细表、用户红包记录表、账户流水表。在千万用户的体量下这些表的数据量会非常可观单表肯定扛不住所以需要做分库分表。6.1 拆分维度常见的拆分方案红包明细表按红包 ID 哈希分表。用户红包记录表按用户 ID 分表。账户流水表按用户 ID 分表。需要注意不同分表键选择会影响查询场景。比如用户查询“我抢到的红包列表”如果明细表按红包 ID 分那么查询就会变成跨分片查询性能很差。所以建议用户红包记录表单独设计不要再依赖红包明细表去反查。6.2 幂等写入一个用户对同一个红包只能抢一次。如果不做幂等同一个请求被网关重试、MQ 消息重复消费时就可能给用户入两次账。幂等控制通常依赖唯一键-- 用户红包记录表 CREATE TABLE user_red_packet_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(64) NOT NULL COMMENT 用户ID, packet_id VARCHAR(64) NOT NULL COMMENT 红包ID, amount_cent BIGINT NOT NULL COMMENT 金额单位分, status TINYINT NOT NULL COMMENT 状态0创建 1入账成功, create_time DATETIME NOT NULL, update_time DATETIME NOT NULL, UNIQUE KEY uk_user_packet (user_id, packet_id) ) COMMENT 用户红包领取记录表;在业务层写入记录使用“先查重再插入”还是“直接插入靠唯一键报错”高并发场景下不要先查后插直接执行插入如果唯一键冲突说明重复请求直接返回旧结果即可。这样既能保证幂等又不需要额外加锁。6.3 异步入账的最终一致抢红包成功以后消息发到 MQ消费端执行入账。这里会有几个问题消费者取到消息准备写库恰好数据库宕机了。消费者写完用户红包记录还没来得及更新账户余额就重启了。Redis 里红包已经弹出但 MQ 消息丢了。这些问题说明异步链路不能只靠一条消息还需要对账机制兜底。更合理的流程是Redis 弹出金额标记用户已抢到写入一个“待入账”状态。MQ 消费者执行入账先写用户红包记录再写账户流水。定时任务扫描“待入账”超时的数据重新发送消息或直接标记失败。对账系统定期比对 Redis 已弹出数据、数据库已入账数据、红包总金额发现不一致时触发补偿。这部分最好不要放在抢红包主链路里因为对账天然是重操作。7. 高并发下的缓存策略与热 Key 处理红包系统里面 Redis 承担了大部分瞬时读写所以 Redis 的性能和稳定性直接决定红包系统能不能撑住千万并发。这里要特别说明一点千万并发只是一个营销词汇真正到一台 Redis 实例上的往往是一个热点红包的抢红包 QPS。可能一个超级大红包瞬间有几十万请求这才是最大压力来源。7.1 热 Key 问题当某个红包特别火爆时所有请求都会命中同一个 key这会导致 Redis 单分片压力巨大。针对热 Key常见的手段有本地缓存前置把红包状态、剩余个数等极少变化的数据放到本地缓存 JVM 里减少对 Redis 的请求量。但注意本地缓存只能缓存状态不能处理原子扣减。热点打散把同一个红包的 List 拆成多个小 List比如 10 个分片每个请求先随机到一个分片再在分片上执行 LPOP。这能有效降低单个 key 的访问热点但会让“红包是否抢完”的判断变复杂因为需要检查所有分片。读写分离未命中时再回源到 Redis避免把所有流量都打到一个 key 上。7.2 本地缓存加速抢红包这里提供一个思路抢红包请求先查一个内存中的红包营销状态如果红包已经处于“已被抢完”状态直接返回失败不再访问 Redis。由于红包一旦抢完就基本不会再变化使用 Caffeine 等本地缓存是安全的只需设置很短的过期时间。// 文件路径src/main/java/com/example/redpacket/cache/RedPacketStatusCache.java Component public class RedPacketStatusCache { private final CacheString, Integer statusCache Caffeine.newBuilder() .expireAfterWrite(Duration.ofSeconds(5)) .maximumSize(100_000) .build(); public int getStatus(String packetId) { Integer status statusCache.getIfPresent(packetId); if (status ! null) { return status; } // 查询 Redis 中的红包状态 String key red:packet:status: packetId; String value redisTemplate.opsForValue().get(key); int realStatus ACTIVE.equals(value) ? 1 : 0; statusCache.put(packetId, realStatus); return realStatus; } public void invalidate(String packetId) { statusCache.invalidate(packetId); } }使用本地缓存的前提是系统允许短暂地拿到“红包已结束”的假状态。因为一个红包从“还有剩余”变成“被抢完”是一个非常快的状态迁移即使延迟几秒才看到对用户影响也不大但如果反过来把未结束的红包显示成结束问题就大了。所以本地缓存只推荐用于提前拦截肯定失败的情况核心扣减还是要走 Redis。7.3 Redis 持久化与容灾红包系统里 Redis 一旦丢失数据会直接影响资金计算。如果 Redis 崩了用户抢到的红包可能记录丢失或者 Redis 恢复后又出现重复抢。在这种场景下Redis 必须开启 AOF 持久化且刷盘策略至少使用everysec不能使用纯内存模式。生产环境建议使用 Redis Cluster 或者云厂商提供的高可用版本主从切换的时间直接影响红包系统可用性。更稳妥的做法是红包金额预拆分结果除了 Redis还要在数据库保留一份“预拆分快照”万一 Redis 数据丢失可以从快照重建。8. 性能优化与容量评估千万并发不是靠一个点优化出来的而是整条链路的容量都要匹配。虽然不同的红包活动规模不同但容量评估的方法是一致的。8.1 指标估算在设计红包系统时先要预估几个关键数字峰值 QPS比如活动最高每秒进入抢红包的请求数。红包总个数预估一天会发出多少个红包决定存储容量。数据保留周期红包明细需要保留多久决定是否要做归档。消息积压上限MQ 消费能力能否跟上红包产出速度。以常见的企业红包活动为例如果峰值抢红包 QPS 是 50 万那么业务服务至少需要 50 万 QPS 的 Redis 操作能力同时要留至少 30% 的余量否则突发流量一来就会打满 CPU。8.2 限流与降级即使系统容量设计得再高也挡不住过量流量因此必须有限流和降级策略。抢红包接口的限流层次通常有三层接入层限流按用户维度、IP 维度限制请求频率防止脚本批量刷。服务层限流基于 Sentinel 或 Guava RateLimiter 控制单个红包的并发抢购量。Redis 层保护控制单实例 QPS超过阈值直接拒绝或者返回“系统繁忙”。还需要考虑降级方案。比如 Redis 集群某个分片抖动时可以切换到备用 Redis如果 Redis 全部不可用应该快速失败并给用户一个明确提示而不是让用户长时间等待。8.3 压测与验证上线前一定要做压测压测重点不是单纯看 QPS而是看“在达到目标 QPS 时错误率和响应时间是否达标”。红包系统的压测需要验证正确性压测过程中总共发放的金额与领取金额是否一致。性能接口平均响应时间和 P99 响应时间。稳定性持续压测 10-30 分钟后内存和 CPU 是否稳定。容灾杀掉一个 Redis 节点或关闭某个服务后系统能否自动恢复。模拟千万并发不能在测试环境里简单用线程池循环请求更应该用压测平台进行集群压测同时通过 MQ 消费情况、数据库写入速率来观察整个链路的瓶颈在哪里。9. 红包系统常见问题与排查思路下面整理一些红包系统开发中比较常见的问题以实际排查路径给出更具体的定位思路。问题现象可能原因排查方式解决方案红包还没抢完用户就提示已抢完本地状态缓存过期时间过长或错误缓存了未完成状态检查红包状态缓存查看 Redis 剩余个数只缓存最终状态缩短过期时间并发抢红包出现金额超发Lua 脚本没有保证原子性或者扣减逻辑存在 read-modify-write检查抢红包日志比对红包总金额与所有领取明细用 Lua 脚本保证判断和扣减原子性用户抢到红包但迟迟不入账MQ 消费者消费失败或消息丢失查看 MQ 积压数量、消费失败日志增加重试机制和对账任务重复入账用户红包记录表没有唯一键查询用户红包记录表看是否存在两条相同 userIdpacketId 数据增加唯一索引通过冲突捕获实现幂等拆红包结果金额不合法拆分算法边界未处理剩余金额小于剩余个数复现发红包场景打印拆分结果加入总额与个数关系校验Redis 主从切换导致红包数据短暂不可用未配置足够高可用的 Redis 架构查看 Redis 连接异常日志使用 Redis Cluster配置哨兵或云厂商高可用数据库写入成为瓶颈明细表未分表或异步消费速度跟不上查看数据库 CPU、慢查询日志分库分表调整 MQ 消费者数量排查红包系统问题时有一个非常实用的原则先看 Redis再看 MQ最后看数据库。因为红包系统的瞬时判定在 Redis跨系统状态流转在 MQ最终资金一致性在数据库。按这个顺序排查通常能最快定位到问题所在。10. 最佳实践与工程建议10.1 金额一律使用“分”为单位调度系统或业务代码中不要使用 double 表示金额否则会出现浮点误差。数据库DECIMAL可以精确表示但 Java 中的随机拆分和累加仍然推荐以“分”为单位使用long类型只有在展示层再拼接小数点。10.2 幂等设计要前置抢红包的接口、MQ 消费、发红包接口凡是涉及写操作的地方都要在接口设计阶段想好幂等方案。最常见的做法是增加唯一键、使用状态机、传递 requestId。等到上线后发生重复请求才发现幂等问题那就是事故而不是 bug 了。10.3 对账系统不能省生产环境中的红包系统一定要有对账任务。对账任务不复杂只需定时扫描比较三个数据红包总单中的总金额。Redis 中已弹出的子红包金额之和。数据库用户红包记录中的已入账金额之和。如果三个数据对不上说明链路中出现了丢失或重复需要告警并人工介入。10.4 安全与合规涉及资金的操作不能只信任客户端参数。服务端必须对用户身份、红包 ID、金额单位做校验防止用户通过构造请求重复领取或者恶意刷接口。可以对同一用户抢同一个红包做频率限制对异常行为做风控拦截。上线前要在测试环境验证资金流程生产环境变更要有备份和回滚方案数据库变更遵循最小权限原则。10.5 容量规划留冗余千万并发并不是每家公司都需要一次性做到但架构上要能横向扩展。红包服务保持无状态Redis 使用集群MQ 和数据库分片都可以水平扩容。活动上线前做好容量评估压测数据不能只在本地用 idea 跑还要在预发环境做全链路压测。11. 总结红包系统表面上是一个“发红包、抢红包”的业务本质上是一道高并发下的资金一致性考题。拆解下来它的核心只有几件事发红包时完成金额预拆分用 Redis 存放子红包数据。抢红包时用 Lua 脚本把判断和扣减变成原子操作。异步入账要有幂等控制靠 MQ 解耦后端的数据库写入。数据库分库分表明细表和账户流水按不同维度拆分。上线前必须压测运行中必须有对账和监控。这套思路完全可以迁移出去。你去做秒杀系统核心就是在 Redis 里原子扣库存你去做优惠券分发核心是防止超发你去做活动报名系统核心是控制一个人只能报名一次。想明白了红包系统再遇到类似的高并发资金场景设计思路会清晰很多。如果你正在开发红包系统建议从最小可行版本开始先实现拆分算法、Lua 原子扣减、幂等入账这三个闭环然后逐步加上对账、分库分表、降级限流。把这一步跑通了千万并发就不会再是一个模糊的概念而是一组可以被拆开、被度量、被验证的技术指标。