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

news2025/3/30 16:52:11

文章目录

  • 案例:预约抢茅子
  • 复杂度分析
    • 商品预约阶段
    • 等待抢购阶段
    • 商品抢购阶段
    • 订单支付阶段
  • 技术方案
    • 商品预约阶段
      • 一、基于 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 - elapsedTime
        scheduleLockExpiration(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]) then
    return redis.call('DECRBY', KEYS[1], ARGV[1])
else
    return -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 then
        return 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. 体验优化:倒计时提醒与支付链路优化,提升用户支付率。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2322660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

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

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

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;也就是系统中…

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

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

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

视图和索引相关的语句 1&#xff0e;引入视图的主要目的是什么? 答&#xff1a;数据库的基本表是按照数据库设计人员的观点设计的&#xff0c;并不一定符合用户的需求。SQL Server 2008可以根据用户需求重新定义表的数据结构&#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;并凭借卓越的…

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…

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

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

PHP框架 ThinkPHP 漏洞探测分析

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

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…

STM32学习笔记之存储器映射(原理篇)

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

如何通过数据可视化提升管理效率

通过数据可视化提升管理效率的核心方法包括清晰展示关键指标、及时发现和解决问题、支持决策优化。其中&#xff0c;清晰展示关键指标尤为重要。通过数据可视化工具直观地呈现关键绩效指标&#xff08;KPI&#xff09;&#xff0c;管理者能快速、准确地理解业务现状&#xff0c…

数据结构:利用递推式计算next表

next 表是 KMP 算法的核心内容&#xff0c;下面介绍一种计算 next 表的方法&#xff1a;利用递推式计算 如图 6.3.1 所示&#xff0c;在某一趟匹配中&#xff0c;当对比到最后一个字符的时候&#xff0c;发现匹配失败&#xff08;s[i] ≠ t[j]&#xff09;。根据 BF 算法&…

每日算法-250326

83. 删除排序链表中的重复元素 题目描述 思路 使用快慢指针遍历排序链表。slow 指针指向当前不重复序列的最后一个节点&#xff0c;fast 指针用于向前遍历探索。当 fast 找到一个与 slow 指向的节点值不同的新节点时&#xff0c;就将 slow 的 next 指向 fast&#xff0c;然后 …