mcpField
F
flash-sale-scaling

flash-sale-scaling

github.com/finsilabs云与 DevOps6075 次安装6 天前更新
代码仓库

关于 flash-sale-scaling

面向电商团队的技能,帮助准备闪购和突发商品需求高峰。内容介绍平台专项准备,以及预扩容、排队结账、Redis 原子库存预留和熔断器等基础设施模式。

主要功能

  1. 01按平台准备促销活动
  2. 02基础设施预热与扩容
  3. 03基于队列接收订单
  4. 04使用 Redis 原子预留库存
  5. 05熔断器与等待室

适用场景

  1. 01为新品发售准备基础设施
  2. 02减少流量高峰期间的超卖
  3. 03规划大型促销活动的容量

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill flash-sale-scaling

安装后在 Claude Code 中输入/flash-sale-scaling确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/circuit-breaker-metrics-and-db-read-rout/
criteria.json
task.md
evals/redis-atomic-inventory-and-waiting-room/
criteria.json
task.md
evals/sqs-queue-checkout-hpa-pre-warming-artil/
criteria.json
task.md

详细介绍

限时抢购扩容

概述

限时抢购和新品发售会在开售后的几秒内产生正常负载 50–100 倍的流量峰值。若不提前准备,结账服务会崩溃、库存会超卖,顾客会看到错误页面。本技能介绍应对极端流量所需的基础设施模式:预热、带背压的基于队列的订单接收、基于 Redis 的原子库存预留,以及在负载下平稳降级的熔断器。

何时使用本技能

  • 规划限时抢购、限量产品发售或大型促销活动时
  • 以往销售活动曾导致结账超时、超卖或数据库故障时
  • 正在筹备黑色星期五/网络星期一,需要审查基础设施时
  • 预计新品发布会带来突发的高需求流量时

核心说明

步骤 1:确定你的平台以及可控范围
平台限时抢购扩容策略关键操作
ShopifyShopify 会自动扩容,无需处理基础设施专注于主题速度(缓存页面、优化图片),为高需求发售启用 Shopify 的排队页面,使用 Launchpad(Shopify Plus)安排并自动执行销售活动
WooCommerce服务器由你负责,需要进行大量准备升级到可扩展的托管服务(Cloudways、Kinsta、WP Engine),启用 Redis Object Cache + WP Rocket 页面缓存,配置 Cloudflare,并提前 1–2 周进行负载测试
BigCommerceBigCommerce 会自动扩容使用 BigCommerce 的限时抢购功能(预览:显示预计等待时间);重点确保商品目录就绪并优化主题性能
自定义/无头架构需要全面控制基础设施应用以下所有模式:预热扩容、Redis 库存、基于队列的结账、熔断器
步骤 2:针对各平台的限时抢购准备工作

Shopify

Shopify 会自动处理扩容,几乎可以应对任何流量峰值。你需要做的准备工作如下:

  1. 启用 Shopify 的高需求结账队列(Shopify Plus):

    • 前往 在线商店 → 偏好设置 → 结账
    • 启用 Checkout concurrency——这会为高流量发售启用 Shopify 的虚拟等候室
    • 对于库存有限的商品(新品发售),使用内置的库存预留功能,为进入结账流程的顾客保留商品 10 分钟
  2. 使用 Launchpad(Shopify Plus)安排销售活动:

    • 从 Shopify App Store 安装 Launchpad(Plus 商家可免费使用)
    • 提前安排销售活动的开始/结束时间、价格变更和库存可用性
    • Launchpad 会在指定时间原子化地启用活动——避免在高负载下手动更改价格
  3. 预先测试商店性能(所有 Shopify 套餐):

    • 前往 在线商店 → 主题,点击 查看报告,查看商店的 Core Web Vitals
    • 使用 Google PageSpeed Insights 检查最关键的页面(商品页面、商品系列页面、结账页面)
    • 在促销开始前修复所有红色/橙色问题——最常见的修复方式是压缩图片
  4. 在大型发售前通知 Shopify 支持团队(Shopify Plus):

    • 通过 Plus 支持渠道提交限时抢购通知——Shopify 可以预先分配资源,并在活动期间监控你的商店

WooCommerce

WooCommerce 在高流量活动前需要进行大量基础设施准备:

升级托管服务(最重要):

  1. 确保托管方案能够扩展:使用 Cloudways(一键横向扩展)、Kinsta(自动扩展)或 WP Engine(自动扩展附加功能),不要使用共享托管
  2. 如果使用共享托管,请至少在促销活动前 1 周迁移到 VPS 或托管式 WordPress 主机,以便系统稳定下来
  3. 在 Cloudways 中:活动前前往 Servers → [server] → Vertical Scaling,选择更大的服务器规格;活动后再缩减规格

缓存栈:

  1. 安装并配置 WP Rocket(页面缓存)+ Redis Object Cache(数据库查询缓存)
  2. 在 WP Rocket 中:启用 Preload cache,在促销开始前预热页面
  3. 在 Redis Object Cache 中:确认 Redis 已启用(Settings → Redis 中显示绿色状态)
  4. 启用 Cloudflare 并将 Caching level 设为 Standard;将商店 URL 添加到 Cloudflare Page Rules,并为产品页和商店页设置 Cache Everything

防止库存超卖:

  1. 启用 WooCommerce 内置的库存管理:前往 WooCommerce → Settings → Products → Inventory → 勾选 Enable Stock Management
  2. 将 Hold stock(分钟)设为 60 — 这会将商品保留在客户购物车中 60 分钟,然后再将其放回库存
  3. 对于限量商品:将 Allow backorders 设为 Do not allow,这样库存数量恰好降至 0 时,商品就会显示为缺货

促销前进行负载测试:

  1. 使用 Loader.io(免费套餐:1 个目标、10K 个连接)模拟预期峰值流量,对暂存站点进行测试
  2. 专门测试结账流程 — 商品浏览通常有缓存;结账会访问数据库
  3. 在促销日期前修复所有故障或响应缓慢的问题

自定义 / Headless

预热基础设施(在促销开始前 30 分钟运行):

# Kubernetes: scale checkout deployment up before sale
kubectl scale deployment checkout-service --replicas=50

# Or schedule automatic scaling with a CronJob
# See the CronJob example below

使用 Redis 原子性预留库存(防止超卖):

// lib/inventory.js
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);

// Initialize inventory in Redis before the sale
export async function initializeInventory(productId, quantity) {
  await redis.set(`inventory:${productId}`, quantity);
}

// Atomic check-and-decrement using Lua script (runs on Redis server, no race conditions)
const LUA_RESERVE = `
  local current = tonumber(redis.call('GET', KEYS[1]))
  if current == nil then return -1 end
  if current < tonumber(ARGV[1]) then return 0 end
  redis.call('DECRBY', KEYS[1], tonumber(ARGV[1]))
  return 1
`;

export async function reserveInventory(productId, quantity) {
  const result = await redis.eval(LUA_RESERVE, 1, `inventory:${productId}`, quantity);
  if (result === 1) return 'reserved';
  if (result === 0) return 'out_of_stock';
  return 'not_found';
}

export async function releaseInventory(productId, quantity) {
  await redis.incrby(`inventory:${productId}`, quantity);
}

基于队列的订单接收(快速响应,异步处理):

// checkout API — responds instantly, processes in background
export async function POST(req) {
  const order = await req.json();

  // 1. Reserve inventory atomically
  const reservation = await reserveInventory(order.productId, order.quantity);
  if (reservation === 'out_of_stock') {
    return Response.json({ error: 'Sold out' }, { status: 409 });
  }

  // 2. Enqueue — responds to user in <100ms
  const orderId = crypto.randomUUID();
  await sqs.send(new SendMessageCommand({
    QueueUrl: process.env.ORDER_QUEUE_URL,
    MessageBody: JSON.stringify({ orderId, ...order }),
  }));

  return Response.json({
    orderId,
    status: 'queued',
    message: 'Your order is being processed. You will receive a confirmation email shortly.',
  });
}

// Background order processor (separate service consuming SQS)
export async function processOrder(message) {
  const order = JSON.parse(message.Body);
  try {
    await capturePayment(order);
    await db.orders.create(order);
    await sendOrderConfirmationEmail(order);
  } catch (err) {
    await releaseInventory(order.productId, order.quantity); // release on failure
    throw err; // let SQS retry
  }
}

支付处理器的熔断器:

import CircuitBreaker from 'opossum';

const paymentBreaker = new CircuitBreaker(captureStripePayment, {
  timeout: 5000,                 // 5s timeout per call
  errorThresholdPercentage: 30,  // open if 30% fail
  resetTimeout: 30000,           // try again after 30s
});

// Fallback: queue for retry instead of failing the customer
paymentBreaker.fallback(async (order) => {
  await sqs.send(new SendMessageCommand({
    QueueUrl: process.env.PAYMENT_RETRY_QUEUE_URL,
    MessageBody: JSON.stringify(order),
    DelaySeconds: 30,
  }));
  return { status: 'payment_queued' };
});

Kubernetes 预扩容 CronJob:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: flash-sale-prescale
spec:
  schedule: "30 11 * * 5"  # 30 min before noon Friday sale
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: scaler
              image: bitnami/kubectl
              command: [kubectl, scale, deployment/checkout-service, --replicas=100]
          restartPolicy: OnFailure

用于需求极高的限量发售活动的 Redis 等候室:

// Fair queue: customers get a position number when they arrive
export async function enterWaitingRoom(sessionId, productId) {
  const queueKey = `sale_queue:${productId}`;
  await redis.zadd(queueKey, Date.now(), sessionId); // sorted set, score = timestamp (FIFO)
  const position = await redis.zrank(queueKey, sessionId);
  return { position: (position ?? 0) + 1 };
}

// Periodically admit batches to checkout
export async function admitFromQueue(productId, batchSize) {
  const admitted = await redis.zpopmin(`sale_queue:${productId}`, batchSize);
  // Notify each admitted customer they can proceed to checkout
  for (let i = 0; i < admitted.length; i += 2) {
    await notifyCustomerAdmitted(admitted[i], productId);
  }
}

最佳实践

  • 在 Redis 中预留库存,而不是在数据库中 — Redis 原子操作每秒可处理数千个并发预留请求;在相同负载下,PostgreSQL 行锁会导致超时和死锁
  • 流量高峰期间将订单接收到队列中 — 即使在流量峰值,面向用户的结账流程也应在 100ms 内响应;将支付扣款、数据库写入和电子邮件发送延后至后台工作进程处理
  • 为每个外部调用设置严格的超时时间 — 负载下,Stripe 的 30 秒超时会累积成数千个被占用的连接;使用 5 秒超时,并在必要时触发熔断
  • 按预期峰值的 2–3 倍进行负载测试 — 仅按预期容量测试没有余量;按 3 倍容量规划,以应对流量分布不均
  • 向客户告知排队状态 — 在等候室显示实时排队位置;能看到进度的客户比盯着加载动画的客户耐心多得多

常见问题

问题解决方案
已检查库存但仍然超卖使用 Redis 原子 Lua 脚本执行检查并扣减;切勿在应用层检查库存,然后通过两次独立操作分别更新库存
自动扩展响应过慢在活动前 30 分钟预热至最低容量;将扩容冷却时间设为 30 秒,而不是默认的 5 分钟
短暂的延迟峰值导致熔断器开启谨慎调整 volumeThreshold 和 errorThresholdPercentage;限时促销期间优先使用 timeout 而非错误率作为主要触发条件
WooCommerce 在流量高峰期间超卖启用 WooCommerce 库存管理,并将 Hold stock 设为 60 分钟;升级到支持 Redis Object Cache 的主机,以减少数据库锁争用

Related Skills

  • @database-optimization-commerce
  • @ecommerce-caching
  • @monitoring-alerting-commerce
  • @load-testing-commerce
  • @edge-commerce

评论

支持 Markdown