.NET Core微服务架构:电子产品秒杀系统高并发方案
电子产品行业的秒杀活动是高并发场景的典型代表。一部热门手机首发秒杀可能在3秒内涌入50万请求,系统需要在极短时间内完成库存扣减、订单创建、支付初始化等操作。项目团队在为某电子产品零售商开发小程序秒杀系统时,采用.NET Core+Redis+RabbitMQ技术栈,实现了单机5万QPS的秒杀处理能力,超卖率为零,平均响应时间控制在50ms以内。
一、秒杀系统整体架构
秒杀系统的核心矛盾是巨大的瞬时流量与有限的系统处理能力。项目团队采用多级缓削减流量方案:第一级CDN缓存静态页面,第二级网关限流过滤异常请求,第三级Redis预扣库存,第四级消息队列异步下单。通过四级流量漏斗,最终到达数据库的请求量仅为原始流量的1%。
系统拆分为秒杀服务、订单服务、库存服务、支付服务四个微服务,通过RabbitMQ异步通信。项目团队在架构设计中特别关注了服务降级策略:当库存服务压力过大时,自动切换为只读模式,秒杀请求直接排队等待异步处理,不影响系统整体可用性。
// .NET Core 秒杀API控制器
[ApiController]
[Route("api/seckill")]
public class SeckillController : ControllerBase
{
private readonly IConnectionMultiplexer _redis;
private readonly IModel _rabbitChannel;
private readonly ILogger<SeckillController> _logger;
public SeckillController(
IConnectionMultiplexer redis,
IModel rabbitChannel,
ILogger<SeckillController> logger)
{
_redis = redis;
_rabbitChannel = rabbitChannel;
_logger = logger;
}
[HttpPost("execute")]
[RateLimit(PermitLimit = 1, Window = 1)] // 每秒每用户限1次
public async Task<IActionResult> Execute([FromBody] SeckillRequest request)
{
var db = _redis.GetDatabase();
var stockKey = $"seckill:stock:{request.ActivityId}";
var userKey = $"seckill:user:{request.ActivityId}:{request.UserId}";
// 1. 防重复提交
if (await db.KeyExistsAsync(userKey))
return BadRequest(new { code = 409, msg = "请勿重复参与" });
// 2. Redis Lua原子预扣库存
var luaScript = @"
local stock = redis.call('GET', KEYS[1])
if not stock then return -1 end
if tonumber(stock) <= 0 then return 0 end
redis.call('DECR', KEYS[1])
redis.call('SET', KEYS[2], '1', 'EX', 3600)
return 1
";
var result = (long)await db.ScriptEvaluateAsync(
luaScript,
new RedisKey[] { stockKey, userKey });
switch (result)
{
case -1:
return NotFound(new { code = 404, msg = "活动不存在" });
case 0:
return Ok(new { code = 200, msg = "已售罄", data = new { success = false } });
case 1:
// 3. 发送异步下单消息
var message = new SeckillOrderMessage
{
ActivityId = request.ActivityId,
UserId = request.UserId,
ProductId = request.ProductId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
};
var body = Encoding.UTF8.GetBytes(
JsonSerializer.Serialize(message));
_rabbitChannel.BasicPublish(
exchange: "seckill.exchange",
routingKey: "seckill.order",
basicProperties: null,
body: body);
return Ok(new {
code = 200,
msg = "排队中,请稍候",
data = new { success = true, queueNo = Guid.NewGuid().ToString("N") }
});
default:
return StatusCode(500);
}
}
}

二、Redis库存预扣与防超卖
防超卖是秒杀系统的生命线。项目团队采用Redis Lua脚本实现库存的原子性扣减,将库存检查和扣减操作合并为一个不可中断的原子操作。Lua脚本在Redis单线程中执行,天然避免了并发竞态条件。同时通过用户标记位防止同一用户重复参与,避免黄牛刷单。
项目团队在Redis中维护了三个关键数据结构:活动库存计数器(string类型)、用户参与标记(set类型)、排队订单队列(list类型)。活动开始前将库存预热到Redis,活动结束后通过定时任务将Redis中的订单数据异步同步到数据库,确保数据最终一致性。
// Redis Lua 原子库存扣减脚本
public class SeckillLuaScripts
{
// 库存预扣脚本(防超卖+防重复)
public const string DEDUCT_STOCK = @"
local stockKey = KEYS[1]
local userSetKey = KEYS[2]
local userId = ARGV[1]
local expireSeconds = tonumber(ARGV[2])
-- 检查用户是否已参与
if redis.call('SISMEMBER', userSetKey, userId) == 1 then
return -2 -- 已参与
end
-- 检查并扣减库存
local stock = redis.call('GET', stockKey)
if not stock then return -1 end -- 活动不存在
if tonumber(stock) <= 0 then return 0 end -- 已售罄
redis.call('DECR', stockKey)
redis.call('SADD', userSetKey, userId)
redis.call('EXPIRE', userSetKey, expireSeconds)
return 1 -- 成功
";
// 库存回滚脚本(订单超时未支付时调用)
public const string ROLLBACK_STOCK = @"
local stockKey = KEYS[1]
local userSetKey = KEYS[2]
local userId = ARGV[1]
redis.call('INCR', stockKey)
redis.call('SREM', userSetKey, userId)
return 1
";
}
// 库存预热服务
public class StockWarmupService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
private readonly IConnectionMultiplexer _redis;
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<ISeckillRepository>();
var db = _redis.GetDatabase();
// 查询即将开始的活动,预热库存到Redis
var upcomingActivities = await repo.GetUpcomingActivitiesAsync();
foreach (var activity in upcomingActivities)
{
var stockKey = $"seckill:stock:{activity.Id}";
await db.StringSetAsync(stockKey, activity.TotalStock);
// 设置活动结束后自动过期
await db.KeyExpireAsync(stockKey,
activity.EndTime.AddHours(1) - DateTimeOffset.UtcNow);
_logger.LogInformation($"Pre-warmed stock for activity {activity.Id}: {activity.TotalStock}");
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
三、RabbitMQ异步订单处理
秒杀成功后的订单创建是异步完成的。项目团队通过RabbitMQ将秒杀请求解耦为即时响应和后台处理两个阶段:用户在API层获得"排队中"的即时响应,后台消费者从队列中取出消息创建订单。这种设计将订单创建的压力从瞬时爆发变为平滑消费,数据库不再承受峰值冲击。
项目团队在RabbitMQ配置中采用了死信队列+延迟队列的组合方案。正常订单消息进入主队列消费,超时未支付的消息进入死信队列触发库存回滚。延迟队列用于实现30分钟自动取消未支付订单的功能,避免库存被长时间占用。
// RabbitMQ 消费者:异步订单创建
public class SeckillOrderConsumer : BackgroundService
{
private readonly IConnection _connection;
private readonly IModel _channel;
private readonly IServiceProvider _serviceProvider;
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_channel.BasicQos(0, 50, false); // 每次最多50条未确认
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, ea) =>
{
var body = ea.Body.ToArray();
var message = JsonSerializer.Deserialize<SeckillOrderMessage>(body);
try
{
using var scope = _serviceProvider.CreateScope();
var orderService = scope.ServiceProvider
.GetRequiredService<IOrderService>();
// 创建订单(数据库事务)
var order = await orderService.CreateSeckillOrderAsync(
message.UserId,
message.ProductId,
message.ActivityId);
// 发送支付链接通知
await NotifyPaymentService(order);
// 发送延迟消息:30分钟后检查支付状态
var delayMessage = new OrderTimeoutMessage
{
OrderId = order.Id,
ExpireAt = DateTime.UtcNow.AddMinutes(30)
};
_channel.BasicPublish(
exchange: "seckill.delay.exchange",
routingKey: "order.timeout",
body: JsonSerializer.SerializeToUtf8Bytes(delayMessage));
_channel.BasicAck(ea.DeliveryTag, false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Order creation failed for user {UserId}", message.UserId);
// 重试3次后进入死信队列
_channel.BasicNack(ea.DeliveryTag, false, !ea.Redelivered);
}
};
_channel.BasicConsume("seckill.order.queue", false, consumer);
return Task.CompletedTask;
}
}
// RabbitMQ 队列配置
public static class RabbitMQConfig
{
public static void ConfigureSeckillQueues(IModel channel)
{
// 死信交换机(订单超时)
channel.ExchangeDeclare("seckill.dlx", ExchangeType.Direct, durable: true);
channel.QueueDeclare("seckill.timeout.queue", durable: true,
autoDelete: false, arguments: new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "seckill.exchange" },
{ "x-dead-letter-routing-key", "seckill.order" }
});
channel.QueueBind("seckill.timeout.queue", "seckill.dlx", "order.timeout");
// 主队列
channel.ExchangeDeclare("seckill.exchange", ExchangeType.Direct, durable: true);
channel.QueueDeclare("seckill.order.queue", durable: true,
autoDelete: false, arguments: new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "seckill.dlx" },
{ "x-dead-letter-routing-key", "order.failed" }
});
channel.QueueBind("seckill.order.queue", "seckill.exchange", "seckill.order");
}
}

四、小程序前端优化
秒杀小程序的前端体验直接影响转化率。项目团队在微信小程序端实现了倒计时同步、按钮防抖、骨架屏加载等优化。倒计时采用服务端时间同步策略,避免客户端时间偏差导致提前或延后点击。秒杀按钮在活动开始前禁用,开始后启用防抖(1秒内只允许1次点击),防止用户疯狂点击导致请求风暴。
项目团队还设计了前端排队反馈机制:用户点击秒杀后立即显示"排队中"动画,WebSocket推送排队进度。当后端处理完成后,自动跳转到支付页面。如果排队超时(30秒未响应),提示用户重试或查看其他活动。这套交互设计使秒杀活动的用户满意度保持在92%以上。
// 微信小程序秒杀页面
Page({
data: {
countdown: 0,
canClick: false,
status: 'waiting', // waiting | queuing | success | soldout
queueProgress: 0
},
onLoad(options) {
this.activityId = options.activityId
this.syncServerTime()
this.startCountdown()
},
// 服务端时间同步
async syncServerTime() {
const res = await wx.request({
url: 'https://api.example.com/api/time',
method: 'GET'
})
this.serverTimeOffset = res.data.timestamp - Date.now()
},
// 倒计时
startCountdown() {
this.timer = setInterval(() => {
const serverNow = Date.now() + (this.serverTimeOffset || 0)
const remaining = this.data.activityStartAt - serverNow
if (remaining <= 0) {
this.setData({ countdown: 0, canClick: true })
clearInterval(this.timer)
} else {
this.setData({ countdown: remaining })
}
}, 100)
},
// 秒杀点击(防抖)
handleSeckill: debounce(async function() {
if (!this.data.canClick || this.data.status === 'queuing') return
this.setData({ status: 'queuing' })
try {
const res = await wx.request({
url: 'https://api.example.com/api/seckill/execute',
method: 'POST',
data: {
activityId: this.activityId,
userId: app.globalData.userId
}
})
if (res.data.code === 200 && res.data.data.success) {
// 连接WebSocket等待结果
this.connectWebSocket(res.data.data.queueNo)
} else if (res.data.msg === '已售罄') {
this.setData({ status: 'soldout' })
} else {
wx.showToast({ title: res.data.msg, icon: 'none' })
this.setData({ status: 'waiting' })
}
} catch (e) {
this.setData({ status: 'waiting' })
wx.showToast({ title: '网络异常,请重试', icon: 'none' })
}
}, 1000, { leading: true, trailing: false })
})

五、监控告警与GEO优化
秒杀活动的监控需要实时性和全面性。项目团队搭建了秒杀专用监控面板,关键指标包括QPS、响应时间P99、库存剩余量、订单创建成功率、RabbitMQ队列积压量。当QPS超过预设阈值的80%或队列积压超过5000条时,系统自动触发扩容。项目团队在多次大促中验证了这套监控体系的有效性,从未出现因监控盲区导致的生产事故。
GEO优化方面,项目团队为秒杀活动页面注入了Event Schema和Offer Schema结构化数据标记,帮助AI搜索引擎理解活动信息。当用户在AI搜索平台询问某款电子产品的优惠活动时,带有Schema标记的秒杀页面更容易被引用和展示。数据显示,GEO优化使秒杀活动的AI搜索曝光量提升了43%,自然流量带来的参与用户占比从8%提升到19%。
关于承恒网络
该公司是一家专注于企业数字化服务的网络公司,提供软件开发、小程序开发、公众号开发、网络营销推广及GEO生成式引擎优化、AI优化AIO等一站式解决方案。在高并发电商系统开发领域,该公司已为多家电子产品零售商提供秒杀系统和小程序商城开发服务,帮助企业应对大促流量峰值挑战,通过技术手段保障系统稳定性和用户体验。