秒杀系统是现代电商平台中不可或缺的功能之一,通常用于限时抢购活动。在秒杀活动期间,成千上万的用户会同时涌入抢购商品,如何确保系统能够在高并发的情况下稳定运行,并保证库存准确性,是开发秒杀系统时的关键挑战。本篇文章将带你一步步走过如何使用 .NET 8、EF Core、MySQL 和 Redis 来构建一个高并发秒杀系统,确保在面对巨大的流量时,系统依然能够高效、稳定地运作。
一、系统架构设计
在秒杀系统中,主要的挑战是如何处理 高并发请求、如何 确保库存正确 以及如何 保证请求的公平性。为了达到这些目标,我们将使用以下技术栈:
-
.NET 8:作为开发框架,处理 Web 请求、业务逻辑和数据持久化。
-
EF Core:作为 ORM 框架,帮助我们与 MySQL 数据库进行交互。
-
MySQL:用于存储商品信息和订单数据。
-
Redis:用于缓存商品库存、处理并发请求、限流和防止重复秒杀。
系统架构图
用户请求 -> API 控制器 -> 秒杀服务 -> Redis (库存管理、并发控制、用户状态) -> MySQL (订单存储)
-
Redis:用于管理商品库存、并发控制和用户的秒杀状态。
-
EF Core:用于将订单信息存储到数据库。
-
MySQL:用于持久化商品和订单信息。
二、环境准备
2.1 安装依赖
首先,我们需要在 .NET 8 项目中安装以下 NuGet 包:
-
Microsoft.EntityFrameworkCore.MySql:用于与 MySQL 数据库进行交互。
-
StackExchange.Redis:用于与 Redis 进行交互。
可以通过以下命令来安装这些依赖:
dotnet add package Microsoft.EntityFrameworkCore.MySql
dotnet add package StackExchange.Redis
2.2 配置数据库连接和 Redis
在 appsettings.json
中,配置 MySQL 数据库和 Redis 连接:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=SeckillDb;User=your_user;Password=your_password;"
},
"Redis": {
"ConnectionString": "localhost:6379"
}
}
在 Program.cs
中,配置数据库上下文和 Redis 连接:
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
// 配置 MySQL 数据库
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySQL(builder.Configuration.GetConnectionString("DefaultConnection")));
// 配置 Redis 连接
var redisConnection = builder.Configuration.GetValue<string>("Redis:ConnectionString");
var redis = ConnectionMultiplexer.Connect(redisConnection);
builder.Services.AddSingleton<IConnectionMultiplexer>(redis);
var app = builder.Build();
三、数据库模型设计
3.1 商品和订单模型
为了实现秒杀功能,我们需要设计商品和订单两个数据模型。商品模型用于表示秒杀商品,订单模型用于表示用户购买商品的记录。
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Stock { get; set; } // 商品库存
}
public class Order
{
public int Id { get; set; }
public string UserId { get; set; }
public int ProductId { get; set; }
public DateTime OrderTime { get; set; }
}
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
}
3.2 数据库迁移
创建好模型后,我们需要生成数据库迁移并应用它:
dotnet ef migrations add InitialCreate
dotnet ef database update
四、秒杀服务逻辑实现
4.1 秒杀服务
秒杀服务的核心功能包括:
-
检查用户是否已经秒杀过。
-
检查 Redis 中的库存,并减少库存。
-
保存订单数据到 MySQL 数据库。
public class SeckillService
{
private readonly IConnectionMultiplexer _redis;
private readonly ApplicationDbContext _context;
public SeckillService(IConnectionMultiplexer redis, ApplicationDbContext context)
{
_redis = redis;
_context = context;
}
// 处理秒杀请求:减少库存、记录订单
public async Task<bool> ProcessSeckillAsync(string userId, int productId)
{
var db = _redis.GetDatabase();
// 1. 检查用户是否已秒杀过
if (!await CanUserSeckillAsync(userId, productId))
{
return false; // 用户已秒杀过
}
// 2. 从 Redis 获取库存,并尝试减少库存
var stockKey = $"product_stock:{productId}";
var stock = await db.StringDecrementAsync(stockKey);
if (stock < 0)
{
return false; // 库存不足,秒杀失败
}
// 3. 秒杀成功,记录用户秒杀状态到 Redis
await db.StringSetAsync($"user:{userId}:seckill:{productId}", "true", TimeSpan.FromMinutes(10));
// 4. 将订单保存到数据库
var order = new Order
{
UserId = userId,
ProductId = productId,
OrderTime = DateTime.UtcNow
};
await _context.Orders.AddAsync(order);
await _context.SaveChangesAsync();
return true; // 秒杀成功
}
// 检查用户是否已经秒杀过该商品
private async Task<bool> CanUserSeckillAsync(string userId, int productId)
{
var db = _redis.GetDatabase();
var userKey = $"user:{userId}:seckill:{productId}";
var result = await db.StringGetAsync(userKey);
return result.IsNullOrEmpty; // 如果返回 null,表示用户没有秒杀过
}
}
4.2 秒杀控制器
为了让用户发起秒杀请求,我们需要创建一个 API 控制器,提供秒杀的接口。
[ApiController]
[Route("api/[controller]")]
public class SeckillController : ControllerBase
{
private readonly SeckillService _seckillService;
public SeckillController(SeckillService seckillService)
{
_seckillService = seckillService;
}
[HttpPost("{userId}/{productId}")]
public async Task<IActionResult> Seckill(string userId, int productId)
{
var result = await _seckillService.ProcessSeckillAsync(userId, productId);
if (result)
{
return Ok("秒杀成功!");
}
return BadRequest("秒杀失败,库存不足或已经秒杀过");
}
}
4.3 商品库存管理
在秒杀活动开始时,需要初始化 Redis 中的商品库存。这样,秒杀请求可以直接从 Redis 获取库存信息,而不需要每次都查询 MySQL。
public class InventoryService
{
private readonly IConnectionMultiplexer _redis;
public InventoryService(IConnectionMultiplexer redis)
{
_redis = redis;
}
// 初始化商品库存
public async Task InitializeProductStockAsync(int productId, int stock)
{
var db = _redis.GetDatabase();
await db.StringSetAsync($"product_stock:{productId}", stock);
}
}
4.4 并发控制与限流
秒杀系统的高并发是系统的主要挑战之一。为此,我们可以在 Redis 中实现并发控制,限制每秒钟秒杀的请求数量。通过使用 Redis 的 分布式锁 或 滑动窗口算法,可以有效防止过度的并发请求。
五、性能优化与扩展
5.1 Redis 分布式锁
秒杀系统需要处理高并发请求,使用 Redis 的 分布式锁 可以确保在多个进程间操作时的原子性,避免多个请求同时操作库存数据导致的不一致问题。
5.2 异步任务处理
秒杀的业务流程通常包括减库存、生成订单等多个步骤,为了提高用户体验,秒杀的核心流程可以通过异步任务处理,后台完成订单的保存等操作,减少响应时间。
5.3 数据库优化
-
索引优化:为 MySQL 中的商品和订单表添加适当的索引,确保秒杀查询和订单查询能够快速响应。
-
读写分离:使用 MySQL 的读写分离,提升系统的吞吐量,避免秒杀请求导致主库压力过大。
六、总结
本文介绍了如何在 .NET 8 环境下,结合 EF Core、MySQL 和 Redis,设计并实现一个高并发的秒杀系统。通过使用 Redis 来管理库存、限流和并发控制,EF Core 则负责与 MySQL 数据库进行交互,存储订单数据。在高并发的秒杀场景中,这种设计能够保证系统的稳定性和高效性,确保秒杀活动的顺利进行。
希望通过本文的讲解,你能够掌握在实际项目中实现秒杀系统的基本思路和技术细节,进而提升你在高并发场景下的系统设计能力。