当前位置: 首页 > article >正文

架构思维:预约抢茅子架构设计

文章目录

  • 案例:预约抢茅子
  • 复杂度分析
    • 商品预约阶段
    • 等待抢购阶段
    • 商品抢购阶段
    • 订单支付阶段
  • 技术方案
    • 商品预约阶段
      • 一、基于 Redis 单节点的分布式锁方案
        • 1. 核心流程
        • 2. 关键设计点
      • 二、Redis 单节点方案的局限性
        • 1. 单点故障风险
        • 2. 主从切换问题
      • 三、多节点 Redis 实现高可靠分布式锁(RedLock)
        • 1. RedLock 核心流程
        • 2. RedLock 关键设计
        • 3. RedLock 的争议与改进
      • 四、不同场景下的技术选型
      • 五、补充优化措施
      • 六、小结
    • 等待抢购阶段
      • 一、页面静态化:降低服务端负载
        • 1. 静态资源拆分与缓存
        • 2. CDN缓存策略优化
        • 3. 动态内容降级方案
      • 二、服务端限流:保护核心系统
        • 1. 多层级限流设计
        • 2. Nginx限流配置示例
        • 3. 用户体验优化
      • 三、动态内容优化:降低API压力
        • 1. 客户端长轮询替代短轮询
        • 2. 边缘计算缓存动态数据
      • 四、方案效果与权衡
      • 五、小结
    • 商品抢购阶段
      • 一、流量削峰:异步化处理与消息队列优化
        • 1. 核心流程设计
        • 2. 消息队列
      • 二、扣减库存:高并发下的数据一致性保障
        • 1. Redis集群方案
        • 2. 可靠性增强
      • 三、分库分表:订单存储的高并发支持
        • 1. 分片策略
        • 2. 中间件选型
      • 四、带宽与网络优化
        • 1. 独立域名与DNS
        • 2. 协议优化
        • 3. 边缘计算
      • 五、小结
    • 订单支付阶段
      • 一、支付回调阶段的可靠消息投递设计
        • 1. 整体流程
        • 2. 关键设计
        • 3. 消息消费的幂等性
      • 二、容错与异常处理
        • 1. 支付回调接口的幂等性
        • 2. 消息补偿机制
        • 3. 数据库与MQ的一致性
      • 三、性能优化
        • 1. 本地消息表设计优化
        • 2. 批量处理
        • 3. 异步化处理
      • 四、高可用与监控
        • 1. 消息队列高可用
        • 2. 监控告警
        • 3. 熔断降级
      • 五、方案对比与选型
      • 小结
    • 抢购后不支付导致的库存占用问题
      • 一、预占库存与支付超时释放机制
        • 1. 库存状态分层设计
        • 2. 预占库存实现流程
        • 3. 关键技术实现
      • 二、恶意用户识别与拦截
        • 1. 用户行为风控模型
        • 2. 实时风控系统架构
      • 三、支付倒计时与用户提醒
        • 1. 前端体验优化
        • 2. 支付链路优化
      • 四、数据一致性保障
        • 1. 最终一致性方案
        • 2. 分布式事务
      • 五、监控与容灾
        • 1. 核心监控指标
        • 2. 熔断降级策略
      • 小结

在这里插入图片描述


案例:预约抢茅子

在这里插入图片描述
业务流程如下:

  • 商品预约:用户进入商品详情页面,获取购买资格,并等待商品抢购倒计时。

  • 等待抢购:等待商品抢购倒计时,直到商品开放抢购。

  • 商品抢购:商品抢购倒计时结束,用户提交抢购订单,排队等待抢购结果,抢购成功后,扣减系统库存,生成抢购订单。

  • 订单支付:等待用户支付成功后,系统更新订单状态,通知用户购买成功。


复杂度分析

根据不同的业务流程阶段,逐一分析一下每个环节可能存在的技术挑战

商品预约阶段

在高并发量的情况下,让每个用户都能得到抢购资格 ?


等待抢购阶段

用户预约成功之后,在商品详情页面中,会存在一个抢购倒计时,这个倒计时的初始时间是从服务端获取的,用户点击购买按钮时,系统还会去服务端验证是否已经到了抢购时间。

在等待抢购阶段,流量突增,因为在抢购商品之前(尤其是临近开始抢购之前的一分钟内),大部分用户会频繁刷新商品详情页,商品详情页面的读请求量剧增, 如果商品详情页面没有做好流量控制,就容易成为整个预约抢购系统中的性能瓶颈点


商品抢购阶段

在商品抢购阶段,用户会点击提交订单,这时,抢购系统会先校验库存,当库存足够时,系统会先扣减库存,然后再生成订单。在这个过程中,短时间之内提交订单的写流量非常高


订单支付阶段

在用户支付订单完成之后,一般会由支付平台回调系统接口,更新订单状态。在支付回调成功之后,抢购系统还会通过异步通知的方式,实现订单更新之外的非核心业务处理,比如积分累计、短信通知等


技术方案

商品预约阶段

在商品预约阶段中,高并发场景下需要保证用户预约资格的公平性和可靠性,同时允许预约量超过实际库存。

在商品预约阶段中,高并发场景下需要保证用户预约资格的公平性和可靠性,同时允许预约量超过实际库存。以下是基于分布式锁技术(参考第06讲内容)的完整技术分析,以及针对 Redis 单点故障问题的补充解决方案:


一、基于 Redis 单节点的分布式锁方案

1. 核心流程
// 伪代码示例:用户预约资格发放
public boolean reserveCommodity(String userId, String itemId) {// 生成唯一锁标识String lockKey = "reserve_lock:" + itemId;String clientId = UUID.randomUUID().toString();try {// 尝试获取分布式锁(设置超时防止死锁)boolean locked = redis.set(lockKey, clientId, "NX", "PX", 10000);if (!locked) return false;// ------ 临界区操作(原子性保障) ------// 1. 检查是否已预约(防重复)if (redis.sismember("reserved_users:" + itemId, userId)) {return false;}// 2. 发放预约资格(允许超库存预约)redis.sadd("reserved_users:" + itemId, userId); // 记录预约用户redis.incr("reserve_count:" + itemId);          // 统计预约总数return true;// -----------------------------------} finally {// 释放锁(Lua脚本保证原子性)String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('del', KEYS[1]) " +"else " +"   return 0 " +"end";redis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));}
}
2. 关键设计点
  • 锁粒度:按商品ID分片锁(reserve_lock:itemId),避免全局锁竞争。
  • 防重复预约:使用 Redis 集合(sadd/sismember)记录已预约用户。
  • 超量预约支持:通过 incr 统计预约量,不依赖实际库存限制。
  • 锁超时机制:设置 10s 锁超时(PX 10000),避免客户端异常导致死锁。

二、Redis 单节点方案的局限性

1. 单点故障风险
  • 问题:若 Redis 主节点宕机,锁数据丢失,导致业务中断。
  • 表现
    • 新客户端无法获取锁(锁变量丢失)。
    • 已持有锁的客户端误认为锁仍有效(但锁实际已丢失)。
2. 主从切换问题
  • 若使用 Redis 主从集群,在主节点宕机后,由于 Redis 主从复制是异步的:
    • 客户端A在主节点获取锁。
    • 主节点宕机,从节点升级为新主节点,但未同步锁数据。
    • 客户端B在新主节点也能获取同一把锁,导致锁互斥性失效。

三、多节点 Redis 实现高可靠分布式锁(RedLock)

针对单点故障问题,需引入 RedLock 算法(参考第06讲),基于多个独立 Redis 节点实现分布式锁。

1. RedLock 核心流程
// 伪代码:RedLock 实现
public boolean tryRedLock(String itemId, String clientId, int ttlMs) {List<RedisNode> redisNodes = getRedisClusterNodes(); // 获取所有Redis节点long startTime = System.currentTimeMillis();int successCount = 0;// 向所有节点发起加锁请求for (RedisNode node : redisNodes) {if (node.setLock(lockKey, clientId, ttlMs)) {successCount++;}}// 计算加锁耗时long elapsedTime = System.currentTimeMillis() - startTime;// 判定条件:多数节点加锁成功,且耗时小于锁有效期boolean locked = (successCount >= redisNodes.size() / 2 + 1) && (elapsedTime < ttlMs);if (locked) {// 锁有效期需补偿网络耗时:ttlMs - elapsedTimescheduleLockExpiration(clientId, ttlMs - elapsedTime); return true;} else {// 加锁失败,释放已获得的锁releasePartialLocks(redisNodes, lockKey, clientId);return false;}
}
2. RedLock 关键设计
设计点说明
独立节点部署使用至少5个独立的 Redis 主节点(非集群模式),降低同时故障概率
时钟同步要求各节点需使用 NTP 服务保证时钟同步,避免锁过期时间计算偏差
锁有效期补偿锁实际有效时间 = 初始 TTL - 加锁耗时,防止因网络延迟导致锁提前失效
失败回滚机制加锁失败时需异步释放已获得的锁,避免残留锁数据
3. RedLock 的争议与改进
  • 争议点(Martin Kleppmann):
    • 依赖系统时钟同步,时钟跳跃可能导致锁失效。
    • 锁的有效期难以精确计算,存在理论上的竞态条件。
  • 改进方案
    • 使用 fencing token(递增令牌)机制,确保锁释放后旧请求不生效。
    • 结合业务层幂等性设计,容忍极低概率的锁失效问题。

四、不同场景下的技术选型

场景技术方案优缺点
预约量较小(QPS < 1万)单 Redis 节点 + 哨兵模式实现简单,但主从切换时存在短暂不可用
高可靠性要求(金融场景)RedLock + 5个独立 Redis 节点高可用,但实现复杂、性能较低(需多节点交互)
超高性能要求(QPS > 10万)Redis 集群 + 细粒度锁(按用户ID分片)通过分片提升并发能力,但需解决数据倾斜问题

五、补充优化措施

  1. 无锁化设计尝试

    • 使用 Redis INCR 原子操作统计预约总数,替代分布式锁。
    • 通过 SETNX user_reserved:userId:itemId 1 实现用户级防重复预约。
    • 优点:性能更高;缺点:无法处理跨多键的原子操作。
  2. 熔断降级策略

    • 当 Redis 不可用时,降级到本地限流(如令牌桶算法),保障核心流程可用。
    • 记录预约请求到 Kafka 队列,后台异步补偿处理。
  3. 监控与告警

    • 监控 Redis 锁等待时间、锁竞争频率。
    • 设置锁持有超时告警(如锁持有时间 > 8s 时触发)。

六、小结

在商品预约阶段,通过分布式锁控制资格发放时需综合考虑:

  1. 锁的可靠性:单节点方案简单但存在单点故障,RedLock 更可靠但实现复杂。
  2. 性能与一致性平衡:根据业务容忍度选择最终一致性或强一致性方案。
  3. 容灾设计:结合熔断降级、异步补偿机制应对极端场景。

实际工程中,若允许极低概率的重复预约,可优先使用单 Redis 节点 + 哨兵模式;若需强一致,则选择 RedLock + fencing token 组合方案。


等待抢购阶段

在等待抢购阶段,应对流量突增问题需综合运用页面静态化服务端限流策略,并结合动态内容优化与用户体验设计。


一、页面静态化:降低服务端负载

1. 静态资源拆分与缓存
  • 核心原则:将商品详情页中不变的内容(如商品图片、描述、规格参数)与动态内容(倒计时、库存状态)分离。
  • 实现步骤
    • 生成静态HTML:使用模板引擎(如Thymeleaf、Velocity)在抢购开始前预生成静态页面,存储至CDN。
    • 动态内容异步加载:在静态页面中通过JavaScript调用API获取倒计时、抢购状态等动态数据。
    <!-- 静态页面示例 -->
    <html><body><div id="product-image"><!-- 静态图片URL --></div><div id="countdown-timer"></div><script>// 异步获取倒计时fetch('/api/countdown?itemId=1001').then(response => response.json()).then(data => updateCountdown(data));</script></body>
    </html>
    
2. CDN缓存策略优化
  • 缓存规则
    • 静态资源(HTML、CSS、JS、图片):设置长期缓存(如1年),通过文件名哈希(main.[hash].css)实现版本更新。
    • 动态API响应:在CDN边缘节点缓存倒计时接口(/api/countdown),设置短时间缓存(如1秒),确保用户看到准实时数据。
  • 预热与刷新
    • 预缓存:在抢购开始前1小时,通过CDN预热工具(如阿里云CDN Prefetch)主动加载静态资源至所有节点。
    • 强制刷新:抢购开始时,通过CDN API刷新关键页面(如商品详情页),确保用户获取最新版本。
3. 动态内容降级方案
  • 本地时钟兜底:若倒计时API不可用,前端使用本地时钟计算剩余时间,并在倒计时结束后提示用户刷新页面。
    let serverTime = 1664000000; // 服务端返回的抢购开始时间戳
    let localOffset = Date.now() / 1000 - serverTime;function updateCountdown() {let remaining = serverTime - Math.floor(Date.now() / 1000 - localOffset);if (remaining <= 0) {showBuyButton(); // 显示购买按钮} else {displayTimer(remaining); // 显示倒计时setTimeout(updateCountdown, 1000);}
    }
    

二、服务端限流:保护核心系统

1. 多层级限流设计
层级限流策略工具/实现
CDN/边缘节点限制同一IP的请求频率(如10次/秒)阿里云CDN频率控制、Cloudflare Rate Limiting
网关层按API维度限制QPS(如商品详情页动态接口10万QPS)Nginx limit_req(漏桶算法)、Spring Cloud Gateway RequestRateLimiter
应用层基于用户ID或设备指纹的细粒度限流(如单个用户每秒最多5次请求)Redis + Lua脚本(计数器算法)
服务层熔断非核心服务(如推荐系统、用户画像),保障抢购链路资源Hystrix、Sentinel
2. Nginx限流配置示例
http {limit_req_zone $binary_remote_addr zone=product_detail:10m rate=100r/s;server {location /api/countdown {limit_req zone=product_detail burst=20 nodelay;proxy_pass http://backend_servers;}}
}
  • 解释
    • limit_req_zone:定义限流区域(按IP),10MB内存存储状态,允许100请求/秒。
    • burst=20:允许突发20个请求进入队列。
    • nodelay:突发请求不延迟处理,直接拒绝超限请求。
3. 用户体验优化
  • 排队机制:前端在限流响应(HTTP 429)时展示排队等待页,并自动重试。
    // 前端处理限流响应
    fetch('/api/countdown').catch(error => {if (error.status === 429) {showQueuePage(); // 显示排队页,倒计时后重试}});
    
  • 请求退避策略:前端在重试时采用指数退避(Exponential Backoff),减少服务端压力。
    let retries = 0;
    function fetchWithRetry() {fetch('/api/countdown').catch(() => {setTimeout(() => {retries++;fetchWithRetry();}, Math.min(1000 * 2 ** retries, 30000));});
    }
    

三、动态内容优化:降低API压力

1. 客户端长轮询替代短轮询
  • 方案:使用长轮询(Long Polling)或WebSocket减少无效请求。
    // 长轮询示例
    function longPollCountdown() {fetch('/api/countdown?longPoll=true').then(response => {updateCountdown(response.data);longPollCountdown(); // 递归调用}).catch(() => setTimeout(longPollCountdown, 5000));
    }
    
  • 服务端实现:当倒计时未结束时,服务端挂起请求,直到时间临近(如剩余10秒)再响应。
2. 边缘计算缓存动态数据
  • 方案:将倒计时数据缓存在CDN边缘节点。
    // Cloudflare Worker脚本
    addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
    });async function handleRequest(request) {const cache = caches.default;let response = await cache.match(request);if (!response) {response = await fetch(request);response = new Response(response.body, response);// 缓存1秒,确保各节点数据准实时response.headers.append('Cache-Control', 'max-age=1');event.waitUntil(cache.put(request, response.clone()));}return response;
    }
    

四、方案效果与权衡

指标静态化+限流方案传统动态渲染方案
服务端负载降低80%以上动态请求(仅处理倒计时API)所有请求需动态生成页面,压力大
用户体验页面加载快,但倒计时依赖API(需处理失败场景)页面加载慢,但数据实时性高
开发复杂度需维护静态生成工具与动态API协同开发简单,直接渲染动态页面
成本CDN费用增加,服务器成本降低服务器成本高,CDN费用低

五、小结

等待抢购阶段的流量突增问题需通过动静分离分层限流综合解决:

  1. 静态化:将页面主体内容缓存在CDN,减少回源请求。
  2. 限流:在网关层拦截超量请求,保护后端服务。
  3. 动态内容优化:使用长轮询、边缘缓存降低API压力。
  4. 容灾与监控:通过自动扩缩容和混沌工程保障系统韧性。

商品抢购阶段

在商品抢购阶段,面对瞬时高并发流量,需通过多层次架构设计保障系统的高可用性与数据一致性。主要依赖流量削峰、扣减库存、分库分表三大核心方案。


一、流量削峰:异步化处理与消息队列优化

1. 核心流程设计
用户 网关 MQ 订单服务 Redis DB 提交抢购请求 发送订单请求(含用户ID、商品ID) 返回“排队中”提示 消费者拉取消息 原子扣减库存(Lua脚本) 扣减成功 插入订单(分库分表) 插入成功 通知抢购成功 扣减失败 通知抢购失败 alt [库存充足] [库存不足] 用户 网关 MQ 订单服务 Redis DB
2. 消息队列
  • 防消息丢失

    • 生产者确认:启用RabbitMQ的publisher confirms或Kafka的acks=all
    • 持久化存储:消息持久化到磁盘,副本数≥3(Kafka推荐配置)。
    • 消费者手动ACK:业务处理成功后再提交偏移量。
  • 消息积压处理

    • 动态扩缩容:基于队列长度自动增加消费者实例(如Kubernetes HPA)。
    • 批量消费:单次拉取多条消息(Kafka的max.poll.records=500)。
    • 死信队列:处理失败消息,避免阻塞正常流程。
  • 消息去重

    • 唯一业务ID:订单ID使用雪花算法生成,Redis记录已处理ID。
    String orderId = "ORDER_" + snowflake.nextId();
    if (redis.setnx("order:dedup:" + orderId, "1") == 1) {processOrder(orderId);
    }
    

二、扣减库存:高并发下的数据一致性保障

1. Redis集群方案
  • 架构选择

    • Redis Cluster:自动分片(16384 slots),支持水平扩展。
    • Codis:Proxy-based分片方案,适合大规模集群。
  • 库存扣减Lua脚本

-- KEYS[1]: 库存Key(stock:item_1001)
-- ARGV[1]: 扣减数量(通常为1)
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) thenreturn redis.call('DECRBY', KEYS[1], ARGV[1])
elsereturn -1
end
2. 可靠性增强
  • 多级缓存兜底

    • 本地缓存:Guava Cache记录热点商品库存(短时间缓存,如1秒)。
    • 数据库异步校对:定时任务对比Redis与数据库库存,修复差异。
  • 降级策略

    • 限流降级:库存不足时直接返回失败,避免无效请求穿透。
    • 预扣库存:提前将库存从数据库加载至Redis,避免击穿。

三、分库分表:订单存储的高并发支持

1. 分片策略
  • 分片键选择:以用户ID后4位取模(user_id % 1024),分为16库×64表。
  • 基因法分片:将分片信息嵌入订单ID,避免跨库查询。
    // 订单ID结构: 时间戳(41bit) + 分库编号(10bit) + 分表编号(6bit) + 序列号(7bit)
    long orderId = (timestamp << 23) | (dbNo << 16) | (tableNo << 10) | sequence;
    
2. 中间件选型
中间件优势适用场景
ShardingSphere兼容性强,支持多种数据库需要灵活路由规则的业务
MyCAT成熟稳定,社区活跃传统分库分表改造项目
VitessKubernetes原生,适合云环境大规模MySQL集群管理

四、带宽与网络优化

1. 独立域名与DNS
  • 子域名隔离:将抢购接口(如buy.example.com)与常规业务分离,独立部署负载均衡。
  • DNS负载均衡:配置加权轮询(WRR)或基于地理位置的DNS解析。
2. 协议优化
  • HTTP/2多路复用:减少TCP连接数,提升传输效率。
  • QUIC协议:Google推出的基于UDP的低延迟协议,适用于高并发场景。
3. 边缘计算
  • CDN动态加速:通过边缘节点转发API请求,减少网络延迟。
  • WebSocket长连接:保持用户会话,减少重复握手开销。

五、小结

商品抢购阶段的架构设计需围绕三大核心展开:

  1. 流量削峰:通过异步队列与动态扩缩容应对瞬时高峰。
  2. 扣减库存:基于Redis集群与原子操作保障高并发下的数据一致性。
  3. 分库分表:结合基因分片与柔性事务实现海量订单存储。

订单支付阶段

在订单支付阶段,确保订单状态更新与异步通知的可靠性是核心挑战。要实现可靠消息投递机制的完整解决方案,可以结合本地消息表、幂等性设计与容错策略


一、支付回调阶段的可靠消息投递设计

1. 整体流程
支付平台 订单服务 本地消息表 MQ 积分服务 回调支付结果(含订单ID) 1. 开启事务 2. 更新订单状态为已支付 3. 插入消息记录(状态=待发送) 4. 返回成功响应 5. 异步发送消息(失败重试) 6. 消费消息,处理积分 7. 幂等性校验 8. 确认消费(ACK) 9. 更新消息状态=已处理 支付平台 订单服务 本地消息表 MQ 积分服务
2. 关键设计
  • 本地消息表与事务绑定
    将订单状态更新与消息记录插入放在同一数据库事务中,保证原子性。

    BEGIN TRANSACTION;
    UPDATE orders SET status = 'paid' WHERE order_id = '1001';
    INSERT INTO message_table (msg_id, order_id, status) 
    VALUES ('msg_001', '1001', 'pending');
    COMMIT;
    
  • 异步消息发送
    使用独立线程池或定时任务扫描本地消息表,将status=pending的消息发送到MQ。

    @Scheduled(fixedDelay = 5000)
    public void sendPendingMessages() {List<Message> messages = messageDao.selectPending();for (Message msg : messages) {mqProducer.send(msg);messageDao.updateStatus(msg.getId(), "sent");}
    }
    
  • 消息重试机制
    若MQ发送失败,通过指数退避策略重试(如首次1秒,第二次2秒,第三次4秒)。

3. 消息消费的幂等性
  • 唯一标识:为每条消息生成全局唯一ID(如msg_id),下游服务通过该ID判断是否已处理。

    public void handleMessage(Message msg) {if (redis.setnx("msg_dedup:" + msg.getId(), "1")) {addPoints(msg.getUserId(), msg.getPoints());}
    }
    
  • 业务状态校验
    处理消息前检查业务状态(如积分是否已到账)。

    SELECT * FROM user_points WHERE order_id = '1001';
    -- 若存在记录,则跳过处理
    

二、容错与异常处理

1. 支付回调接口的幂等性
  • 设计要点
    • 支付平台可能多次回调,需保证订单状态更新幂等。
    • 在更新订单状态前先查询当前状态。
    public void handlePaymentCallback(String orderId) {Order order = orderDao.select(orderId);if (order.getStatus().equals("paid")) {return; // 已处理,直接返回}// 处理支付逻辑
    }
    
2. 消息补偿机制
  • 场景:消息发送失败或消费失败。
  • 方案
    • 定时任务扫描:定期检查本地消息表中status=sent但未ACK的消息,重新发送。
    • 死信队列(DLQ):MQ将多次重试失败的消息转入DLQ,触发人工干预。
3. 数据库与MQ的一致性
  • 最终一致性保障
    • 本地消息表:确保消息至少被发送一次。
    • 消费者ACK机制:MQ在消息被成功处理后提交确认(如Kafka的enable.auto.commit=false)。

三、性能优化

1. 本地消息表设计优化
  • 分库分表:按订单ID分片,避免单表过大。
  • 读写分离:将消息表的查询操作路由到从库。
2. 批量处理
  • 消息批量发送:合并多条消息为一批发送,减少MQ调用次数。

    List<Message> batch = messages.subList(0, 100);
    mqProducer.sendBatch(batch);
    
  • 批量更新状态
    发送成功后批量更新消息状态为sent

    UPDATE message_table SET status = 'sent' 
    WHERE msg_id IN ('msg_001', 'msg_002', ...);
    
3. 异步化处理
  • 非阻塞IO:使用Netty或异步Servlet处理支付回调,避免线程阻塞。
  • 线程池隔离:核心业务(订单状态更新)与非核心业务(消息发送)使用独立线程池。

四、高可用与监控

1. 消息队列高可用
  • 集群部署:使用Kafka多副本机制,确保Broker故障时自动切换。
  • 持久化配置
    Kafka设置replication.factor=3min.insync.replicas=2
2. 监控告警
  • 关键指标
    • 消息积压量:MQ消费者Lag(如Kafka的consumer_lag)。
    • 处理延迟:从消息生产到消费的时间差。
    • 错误率:消息发送/消费失败的比例。
3. 熔断降级
  • 规则配置:当积分服务故障时,暂停消息消费并降级。
    // 使用Sentinel熔断
    @SentinelResource(value = "addPoints",fallback = "addPointsFallback",blockHandler = "addPointsBlockHandler"
    )
    public void addPoints(String userId, int points) { ... }
    

五、方案对比与选型

方案优点缺点适用场景
本地消息表强一致性,无外部依赖数据库压力大中小规模业务
RocketMQ事务消息无侵入,天然支持分布式事务依赖特定MQ,成本高高并发、强一致性场景
最大努力通知实现简单,资源消耗低可能丢失消息容忍最终一致性的非核心业务

小结

在订单支付阶段,通过本地消息表 + 异步重试 + 幂等性设计的组合方案,可有效解决支付回调与异步通知的可靠性问题。核心要点包括:

  1. 事务绑定:确保订单状态更新与消息记录原子性。
  2. 消息可靠投递:通过重试、ACK、死信队列实现最终一致性。
  3. 下游幂等:防止重复处理导致数据错误。
  4. 监控与容错:实时感知异常并触发补偿机制。

抢购后不支付导致的库存占用问题

用户提交订单抢到商品后,此时系统的库存已经扣减掉了,但是订单中的状态还是未支付,如果此时用户是恶意的行为,只抢购不支付,那么怎么优化架构设计来应对这样的操作?

一、预占库存与支付超时释放机制

1. 库存状态分层设计
库存系统
可售库存
预占库存
已售库存
  • 可售库存:前端展示的剩余数量,用户可见。
  • 预占库存:用户下单后临时占用的库存(支付超时后释放)。
  • 已售库存:支付成功后永久扣除的库存。
2. 预占库存实现流程
用户 订单服务 Redis 支付服务 定时任务 提交订单 预占库存(DECR可售库存,INCR预占库存) 操作成功 生成待支付订单 返回支付页面(含倒计时) 完成支付 扣减预占库存(DECR预占库存,INCR已售库存) 更新订单状态为已支付 扫描超时订单 释放预占库存(DECR预占库存,INCR可售库存) 标记订单为已取消 alt [超时未支付] 用户 订单服务 Redis 支付服务 定时任务
3. 关键技术实现
  • 库存预占原子性
    使用Redis Lua脚本保证库存操作的原子性:

    -- KEYS[1]=可售库存, KEYS[2]=预占库存
    local available = tonumber(redis.call('GET', KEYS[1]))
    if available <= 0 thenreturn 0
    end
    redis.call('DECR', KEYS[1])
    redis.call('INCR', KEYS[2])
    return 1
    
  • 支付超时管理

    • 延迟队列:使用RabbitMQ死信队列(DLX)或RocketMQ延迟消息触发超时检查。
    • 定时任务分片:按订单ID哈希分片,分布式调度(如ElasticJob)避免单点压力。

二、恶意用户识别与拦截

1. 用户行为风控模型
指标检测规则处置措施
未支付订单率用户近1小时未支付订单数 > 5限制参与抢购1小时
设备指纹关联同一设备生成 > 3个未支付订单封禁设备ID
IP异常请求单个IP来源的抢购请求频率 > 100次/分钟IP限流或临时封禁
2. 实时风控系统架构
允许
拦截
用户请求
网关层
风控引擎
规则库: 频率/IP/设备
Flink实时计算
风险决策
业务系统
返回错误码
  • 技术选型
    • 流计算:Apache Flink实时统计用户行为指标。
    • 特征存储:Redis存储用户行为计数(如INCR user:1001:unpaid_orders)。
    • 决策引擎:Drools规则引擎动态加载风控策略。

三、支付倒计时与用户提醒

1. 前端体验优化
  • 倒计时同步
    前端定时从服务端同步剩余时间(避免客户端时间篡改):

    function syncCountdown() {fetch('/api/payment/timeleft?orderId=1001').then(res => res.json()).then(data => {updateUI(data.remainingSeconds);});
    }
    // 每10秒同步一次
    setInterval(syncCountdown, 10000);
    
  • 多通道提醒

    • 站内信:用户登录时提示待支付订单。
    • 短信/邮件:支付截止前15分钟发送提醒。
2. 支付链路优化
  • 预创建支付单:在生成订单时预创建支付流水(如支付宝的alipay.trade.precreate),缩短支付跳转时间。
  • 支付状态主动查询:前端轮询支付结果,避免依赖异步回调延迟。

四、数据一致性保障

1. 最终一致性方案
  • 库存释放补偿任务
    定时扫描预占库存与订单状态,修复异常数据:
    -- 补偿任务SQL(伪代码)
    UPDATE inventory 
    SET available = available + reserved, reserved = 0 
    WHERE item_id IN (SELECT item_id FROM orders WHERE status = 'unpaid' AND create_time < NOW() - INTERVAL 30 MINUTE
    );
    
2. 分布式事务
  • Saga模式
    订单服务 库存服务 支付服务 预占库存(Saga起点) 预占成功 发起支付 确认扣减(Saga Commit) 取消预占(Saga Compensate) alt [支付成功] [支付超时/失败] 订单服务 库存服务 支付服务

五、监控与容灾

1. 核心监控指标
指标监控方式告警阈值
预占库存释放延迟Prometheus + Grafana> 5分钟未释放
未支付订单占比ELK日志分析超过10%触发预警
风控规则命中率实时Dashboard单规则命中率突增50%
2. 熔断降级策略
  • 库存服务不可用
    降级为同步扣减数据库库存,事后通过对账修复。
  • 风控服务超时
    跳过风控检查,记录日志事后审计。

小结

通过预占库存机制 + 支付超时释放 + 实时风控拦截的组合方案,可有效解决恶意占库存问题:

  1. 资源隔离:预占库存与实际可售库存分离,避免恶意占用影响正常销售。
  2. 及时释放:通过延迟队列和定时任务确保超时订单快速释放。
  3. 行为管控:实时风控识别恶意用户,降低资源浪费。
  4. 体验优化:倒计时提醒与支付链路优化,提升用户支付率。

在这里插入图片描述

相关文章:

架构思维:预约抢茅子架构设计

文章目录 案例&#xff1a;预约抢茅子复杂度分析商品预约阶段等待抢购阶段商品抢购阶段订单支付阶段 技术方案商品预约阶段一、基于 Redis 单节点的分布式锁方案1. 核心流程2. 关键设计点 二、Redis 单节点方案的局限性1. 单点故障风险2. 主从切换问题 三、多节点 Redis 实现高…...

使用 gone.WrapFunctionProvider 快速接入第三方服务

项目地址&#xff1a;https://github.com/gone-io/gone 本文中源代码&#xff1a; esexamples/es 文章目录 1. gone.WrapFunctionProvider 简介2. 配置注入实现3. 实战示例&#xff1a;Elasticsearch 集成4. 使用方式5. 最佳实践6. 总结 在如何给Gone框架编写Goner组件&#xf…...

基于SpringBoot+Vue的在教务管理(课程管理)系统+LW示例

1.项目介绍 系统角色&#xff1a;管理员、学生、教师功能模块&#xff1a;管理员&#xff08;学院管理、专业管理、班级管理、学生管理、教师管理、课程管理、选课修改&#xff09;、教师&#xff08;授课查询、教师课表、成绩录入&#xff09;、学生&#xff08;选修课程、学…...

gitee 常用指令

1.拉取代码 // http git clone http.........// https git clone https......... 2. 设置自己账户和密码 ----- 绑定git git config --global user.name "你的用户名"git config --global user.email "你的邮箱" 3. 上传本地代码至git git initgit r…...

etcd性能测试

etcd性能测试 本文参考官方文档完成etcd性能测试&#xff0c;提供etcd官方推荐的性能测试方案。 1. 理解性能&#xff1a;延迟与吞吐量 etcd 提供稳定、持续的高性能。有两个因素决定性能&#xff1a;延迟和吞吐量。延迟是完成一项操作所花费的时间。吞吐量是在某个时间段内…...

JIRA/Xray测试管理工具的最佳实践:从基础到高阶的全场景指南

引言&#xff1a;测试管理的数字化转型与工具价值 在数字化时代&#xff0c;软件质量已成为企业竞争力的核心指标。然而&#xff0c;传统的测试管理方式——如Excel记录用例、邮件沟通缺陷、手动执行回归测试——已无法满足快速迭代的敏捷开发需求。据统计&#xff0c;全球因测…...

ubuntu桌面图标异常——主目录下的所有文件(如文档、下载等)全部显示在桌面

ubuntu桌面图标异常 问题现象问题根源系统级解决方案方法一:全局修改(推荐多用户环境)方法二:单用户修改(推荐个人环境)操作验证与调试避坑指南扩展知识参考文档问题现象 主目录文件异常显示 用户主目录(如/home/user/)下的所有文件(如文档、下载等)全部显示在桌面,…...

AIP-191 文件和目录结构

编号191原文链接https://google.aip.dev/191状态批准创建日期2019-07-25更新日期2019-07-25 统一的文件和目录结构&#xff0c;虽然在技术上差别不大&#xff0c;但可以让用户和审查者更容易阅读API界面定义。 指南 注意 以下指南适合于使用protobuf定义的API&#xff0c;例如…...

sql结尾加刷题

找了一下mysql对extractvalue()、updatexml()函数的官方介绍https://dev.mysql.com/doc/refman/5.7/en/xml-functions.html#function_extractvalue ExtractValue(xml_frag, xpath_expr) 知识点 解释一下这两个参数xml_frag&#xff0c;是xml标记片段&#xff0c;第二个参数…...

Linux学习笔记(应用篇三)

基于I.MX6ULL-MINI开发板 LED学习GPIO应用编程输入设备 开发板中所有的设备&#xff08;对象&#xff09;都会在/sys/devices 体现出来&#xff0c;是 sysfs 文件系统中最重要的目录结构 /sys下的子目录说明/sys/devices这是系统中所有设备存放的目录&#xff0c;也就是系统中…...

LLM动态Shape实现原理与核心技术

LLM动态Shape实现原理与核心技术 目录 LLM动态Shape实现原理与核心技术1. **动态Shape核心原理**2. **实现方法与关键技术**3. **示例:vLLM处理动态长度输入**4. **动态Shape vs 静态Shape对比**5. **性能优化案例**总结`SamplingParams` 是什么常见参数及作用使用示例1. 动态…...

MyBatis 语法不支持 having 节点

MyBatis 不支持 having 节点 比如在 GROUP BY 之后添加了 HAVING 子句&#xff0c;其内容为SUM(vsbsad.business_income) > 0&#xff0c;该子句会对分组后的 SUM(vsbsad.business_income) 结果进行过滤&#xff0c;仅保留求和结果不为负数的分组记录。但是试过不支持。可把…...

【redis】事务详解,相关命令multi、exec、discard 与 watch 的原理

文章目录 什么是事务原子性一致性持久性隔离性 优势与 MySQL 对比用处 事务相关命令开启事务——MULTI执行事务——EXEC放弃当前事务——DISCARD监控某个 key——WATCH作用场景使用方法实现原理 事务总结 什么是事务 MySQL 事务&#xff1a; 原子性&#xff1a;把多个操作&am…...

数据库基础知识点(系列七)

视图和索引相关的语句 1&#xff0e;引入视图的主要目的是什么? 答&#xff1a;数据库的基本表是按照数据库设计人员的观点设计的&#xff0c;并不一定符合用户的需求。SQL Server 2008可以根据用户需求重新定义表的数据结构&#xff0c;这种数据结构就是视图。视图是关系数据…...

FreeRTOS 队列结构体 xQUEUE 深度解析

一、核心成员与功能设计 FreeRTOS 的队列结构体 xQUEUE 是任务间通信&#xff08;IPC&#xff09;的核心数据结构&#xff0c;通过统一的设计支持队列、信号量、互斥量等多种同步机制。其设计体现了 ​**"数据拷贝 结构复用"** 的理念&#xff0c;兼顾轻量化与扩展…...

3.3 Taylor公式

1.定义 1.1 taylor公式 1.2 麦克劳林公式 1.3 推论 1.4 拉格朗日余项和皮亚诺型余项 2. 例题 3.几种特殊函数的麦克劳林展开...

2000-2019年各省地方财政行政事业性收费收入数据

2000-2019年各省地方财政行政事业性收费收入数据 1、时间&#xff1a;2000-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区、年份、地方财政行政事业性收费收入 4、范围&#xff1a;31省 5、指标说明&#xff1a;地方财政行政事业…...

Ftrans飞驰云联受邀参加“2025汽车零部件CIO年会“并荣获智象奖

2025年3月6日&#xff0c;由栖观汽车、栖观资讯和飞羽商务主办的“2025第二届中国汽车&零部件CIO年会暨智象奖颁奖盛典”于上海盛大召开&#xff0c;Ftrans飞驰云联作为国内领先的企业文件传输与数据交换解决方案提供商&#xff0c;受邀出席了年会&#xff0c;并凭借卓越的…...

C++vector常用接口和模拟实现

C中的vector是一个可变容量的数组容器&#xff0c;它可以像数组一样使用[]进行数据的访问&#xff0c;但是又不像C语言数组空间是静态的&#xff0c;它的空间是动态可变的。 在日常中我们只需要了解常用的接口即可&#xff0c;不常用的接口查文档即可。 1.构造函数 //空构造…...

oracle查询归档日志使用量

1.统计最近30天的数据 SELECT TRUNC(first_time, DD) "日期", SUM(blocks * block_size) / 1024 / 1024 / 1024 "大小(GB)" FROM v$archived_log WHERE first_time > SYSDATE - 30 -- 统计最近30天的数据 GROUP BY TRUNC(first_time, DD) ORDER BY 1 D…...

计算机二级WPS Office第七套WPS演示

解题过程...

2025-03-26 学习记录--C/C++-PTA 6-3 求链式表的表长

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 6-3 求链式表的表长 本题要求实现一个函数&#xff0c;求链式表的表长。 函数接口定义&#xff1a; &…...

【Mysql】事务管理:原理、操作与应用

文章目录 一、事务概述二、事务的特性&#xff08;ACID&#xff09;原子性&#xff08;Atomicity&#xff09;一致性&#xff08;Consistency&#xff09;隔离性&#xff08;Isolation&#xff09;持久性&#xff08;Durability&#xff09; 三、事务的操作事务的提交方式查看和…...

PHP框架 ThinkPHP 漏洞探测分析

目录 1. PHP历史利用最多的漏洞有哪些&#xff1f; 2. 如何在信息收集的过程中收到框架信息&#xff1f;有什么根据&#xff1f; 3. ThinkPHP框架漏洞扫描有哪些工具&#xff1f;红队攻击有哪些方式&#xff1f; 漏洞扫描工具 红队攻击方式 4. TPscan工具的主要作用及实际…...

A Brief History: from GPT-1 to GPT-3

This is my reading notes of 《Developing Apps with GPT-4 and ChatGPT》. In this section, we will introduce the evolution of the OpenAI GPT medels from GPT-1 to GPT-4. GPT-1 In mid-2018, OpenAI published a paper titled “Improving Language Understanding …...

大模型在支气管肺癌预测及临床决策中的应用研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的 二、大模型预测支气管肺癌的原理与技术基础 2.1 大模型简介 2.2 数据收集与预处理 2.3 模型训练与优化 三、术前预测 3.1 病情评估 3.1.1 肿瘤大小、位置及分期预测 3.1.2 转移风险预测 3.2 手术风险预测 3.2.1 患…...

SylixOS 中 select 原理及使用分析

1、select接口简介 1.1 select接口使用用例 select 是操作系统多路 I/O 复用技术实现的方式之一。 select 函数允许程序监视多个文件描述符&#xff0c;等待所监视的一个或者多个文件描述符变为“准备好”的状态。所谓的”准备好“状态是指&#xff1a;文件描述符不再是阻塞状…...

软考笔记——软件工程基础知识

第五章节——软件工程基础知识 软件工程基础知识 第五章节——软件工程基础知识一、软件工程概述1. 计算机软件2. 软件工程基本原理3. 软件生命周期4. 软件过程 二、软件过程模型1. 瀑布模型2. 增量模型3. 演化模型&#xff08;原型模型、螺旋模型)4. 喷泉模型5. 基于构建的开发…...

FastGPT原理分析-数据集创建第二步:处理任务的执行

概述 文章《FastGPT原理分析-数据集创建第一步》已经分析了数据集创建的第一步&#xff1a;文件上传和预处理的实现逻辑。本文介绍文件上传后&#xff0c;数据处理任务的具体实现逻辑。 数据集创建总体实现步骤 从上文可知数据集创建总体上来说分为两大步骤&#xff1a; &a…...

基于Python的3D贴图制作技术研究与实践

摘要&#xff1a;本文深入探讨了利用Python进行3D贴图制作的技术&#xff0c;介绍了Python在3D图形领域的应用优势&#xff0c;阐述了3D贴图的基本原理和常见类型。详细讲解了借助Python的相关库&#xff0c;如Pillow、OpenCV、PyTorch3D开展3D贴图制作的流程&#xff0c;包括纹…...