当前位置:网站首页 >  教程

基于Python+Redis实现高并发秒杀防超卖技术实操

时间:2026年06月12日 12:50:45 来源:易频IT社区

技术背景与核心逻辑

在电商运营活动中,秒杀是典型的“收割”流量与转化的场景。高并发下的库存超卖是系统最大的技术痛点。本指南将直接构建一套基于Python Flask + Redis + Lua的高并发秒杀系统。核心思路是将库存扣减操作前置到Redis中执行,利用Redis的单线程特性和Lua脚本的原子性,彻底杜绝数据库层面的超卖问题,同时极大减轻数据库压力。

一、环境准备与依赖安装

在开始编码前,需要确保服务器或本地环境已安装基础组件。请严格按照以下步骤执行,不要跳过任何环节。

1. 安装Redis服务

Redis是核心组件,用于存储热点库存数据。以Ubuntu系统为例,执行以下命令直接安装并启动:

  • 安装命令:sudo apt-get update && sudo apt-get install redis-server -y
  • 启动服务:sudo service redis-server start
  • 验证连接:redis-cli ping,若返回PONG则表示正常。

2. 安装Python依赖库

我们需要Flask作为Web框架,redis-py作为客户端驱动。执行以下命令安装所需依赖:

pip install flask redis pymysql

注意:建议使用Python 3.8及以上版本以确保库的兼容性。

3. 安装并配置MySQL

虽然主要逻辑在Redis,但MySQL仍用于最终订单落库。确保MySQL服务已启动,并创建专用数据库。

  • 登录MySQL:mysql -u root -p
  • 创建数据库:CREATE DATABASE seckill_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  • 使用数据库:USE seckill_demo;

二、数据库表结构设计

我们需要两张核心表:一张是商品库存表,一张是订单表。请在MySQL命令行中直接执行以下SQL语句,确保表结构完整无误。

1. 创建商品库存表

```sql CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL COMMENT '商品名称', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量', `version` int(11) NOT NULL DEFAULT '0' COMMENT '乐观锁版本号', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ```

2. 创建订单记录表

```sql CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '用户ID', `goods_id` int(11) NOT NULL COMMENT '商品ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ```

3. 初始化测试数据

插入一个测试商品,库存设置为100,用于后续压测验证。

```sql INSERT INTO `goods` (`name`, `stock`, `version`) VALUES ('iPhone 15 Pro', 100, 0); ```

三、Redis库存预热脚本

系统启动前,必须将MySQL中的库存数据同步加载到Redis中。这一步称为“库存预热”。创建一个名为init_stock.py的文件,并写入以下代码。

```python import redis import pymysql 数据库连接配置 DB_CONFIG = { 'host': 'localhost', 'user': 'root', 'password': 'your_password', 请修改为你的MySQL密码 'db': 'seckill_demo', 'charset': 'utf8mb4' } Redis连接配置 REDIS_POOL = redis.ConnectionPool(host='localhost', port=6379, db=0) def sync_stock_to_redis(goods_id=1): """ 将MySQL中的库存同步到Redis """ conn = pymysql.connect(DB_CONFIG) cursor = conn.cursor() try: 查询当前库存 sql = "SELECT stock FROM goods WHERE id = %s" cursor.execute(sql, (goods_id,)) result = cursor.fetchone() if not result: print("商品不存在") return stock = result[0] r = redis.Redis(connection_pool=REDIS_POOL) 将库存写入Redis,Key命名为 goods_stock_{id} r.set(f"goods_stock_{goods_id}", stock) print(f"商品ID {goods_id} 库存预热成功,当前库存: {stock}") finally: cursor.close() conn.close() if __name__ == "__main__": sync_stock_to_redis(goods_id=1) ```

执行命令python init_stock.py。如果输出“库存预热成功”,则说明数据已成功加载至Redis。

四、核心秒杀逻辑实现(Lua原子操作)

这是本系统的核心。我们将库存判断、扣减和订单资格检查封装在一个Lua脚本中。Redis保证Lua脚本的执行是原子性的,即在同一时刻不会有其他命令插入执行,从而避免了并发冲突。

基于Python+Redis实现高并发秒杀防超卖技术实操

创建主程序文件app.py,完整代码如下。请务必修改数据库密码部分。

```python import time import redis import pymysql from flask import Flask, request, jsonify app = Flask(__name__) 配置信息 DB_CONFIG = { 'host': 'localhost', 'user': 'root', 'password': 'your_password', 修改为真实密码 'db': 'seckill_demo', 'charset': 'utf8mb4' } 创建Redis连接池,设置超时时间防止阻塞 REDIS_POOL = redis.ConnectionPool( host='localhost', port=6379, db=0, socket_timeout=5, socket_connect_timeout=5, retry_on_timeout=True ) Lua脚本:获取库存 -> 判断是否大于0 -> 扣减库存 -> 返回结果 这段脚本逻辑不可分割,是防超卖的关键 SECKILL_SCRIPT = """ local goods_id = KEYS[1] local user_id = ARGV[1] local stock_key = "goods_stock_" .. goods_id local bought_key = "user_bought_" .. goods_id .. "_" .. user_id -- 1. 检查是否重复购买 if redis.call('exists', bought_key) == 1 then return -1 -- 已购买 end -- 2. 检查并扣减库存 local stock = tonumber(redis.call('get', stock_key)) if stock == nil then return -2 -- 商品不存在或未预热 end if stock > 0 then redis.call('decr', stock_key) redis.call('set', bought_key, 1, 'EX', 3600) -- 标记已购买,防止一人多单 return 1 -- 抢购成功 else return 0 -- 库存不足 end """ def get_db_connection(): """获取数据库连接""" return pymysql.connect(DB_CONFIG) @app.route('/seckill', methods=['POST']) def seckill(): """ 秒杀接口 参数: goods_id, user_id """ goods_id = request.form.get('goods_id', '1') user_id = request.form.get('user_id') if not user_id: return jsonify({'code': 400, 'msg': '缺少用户ID'}) r = redis.Redis(connection_pool=REDIS_POOL) try: 执行Lua脚本 KEYS[1] = goods_id ARGV[1] = user_id result = r.eval(SECKILL_SCRIPT, 1, goods_id, user_id) if result == 1: Redis扣减成功,开始写入MySQL 在生产环境中,这里建议使用消息队列异步解耦 为保证本指南的零门槛可运行性,此处采用同步写入 conn = get_db_connection() try: with conn.cursor() as cursor: insert_sql = "INSERT INTO orders (user_id, goods_id) VALUES (%s, %s)" cursor.execute(insert_sql, (user_id, goods_id)) 更新MySQL库存(仅作数据一致性最终校准,不参与并发锁) update_sql = "UPDATE goods SET stock = stock - 1 WHERE id = %s" cursor.execute(update_sql, (goods_id,)) conn.commit() return jsonify({'code': 200, 'msg': '抢购成功'}) except Exception as e: conn.rollback() 记录日志,此处需配合补偿机制将库存加回Redis print(f"数据库写入失败: {e}") return jsonify({'code': 500, 'msg': '系统繁忙,请稍后重试'}) finally: conn.close() elif result == 0: return jsonify({'code': 201, 'msg': '库存不足'}) elif result == -1: return jsonify({'code': 202, 'msg': '重复购买'}) else: return jsonify({'code': 500, 'msg': '系统异常'}) except Exception as e: print(f"Redis执行异常: {e}") return jsonify({'code': 500, 'msg': '服务繁忙'}) if __name__ == '__main__': 启动Flask服务 app.run(host='0.0.0.0', port=5000, threaded=True) ```

五、系统启动与压力测试

代码编写完毕后,我们需要模拟高并发场景来验证系统是否真正防超卖。

1. 启动Web服务

在终端执行:

python app.py

看到输出Running on http://0.0.0.0:5000即表示服务启动成功。

2. 使用Apache Bench进行压测

我们使用Linux自带的ab工具模拟1000个并发请求,总共发送1000个请求。如果系统逻辑正确,数据库中的最终订单数和剩余库存应完美对应。

  • 安装ab工具(如果没有):sudo apt-get install apache2-utils

执行压测命令:

ab -n 1000 -c 100 -p user.txt -T application/x-www-form-urlencoded http://127.0.0.1:5000/seckill

注意:ab工具需要post数据文件。创建一个名为user.txt的文件,内容如下。为了模拟不同用户,我们使用简单的固定参数(本例主要验证库存原子性,用户ID参数主要用于去重逻辑演示)。

```text goods_id=1&user_id=10001 ```

六、结果验证与排错

压测完成后,执行以下SQL验证数据一致性。

1. 查看最终订单数

```sql SELECT COUNT() as total_orders FROM orders WHERE goods_id = 1; ``>

2. 查看商品剩余库存

```sql SELECT stock FROM goods WHERE id = 1; ``>

3. 结果分析

如果初始库存为100,压测发送了1000个请求:

  • 正常情况:订单数应为100,库存应为0。即便请求量远超库存,也不会出现-1或-2的情况。
  • 异常情况:如果发现库存为负数,说明Redis脚本未生效或数据库逻辑有误;如果订单数大于100,说明Redis的原子锁失效。

通过上述步骤,我们成功构建了一套零门槛、可落地的防超卖秒杀系统。该方案利用Redis的原子操作拦截了99%的无效流量,保护了脆弱的数据库层,是运营收割流量时必须掌握的核心技术。

标签 运营收割

相关推荐

最新

热门

推荐

精选

标签

易频IT社区是综合性互联网IT技术门户网站,专注分享网络技术、服务器运维、网络安全、编程开发、系统架构、云计算、大数据等行业干货,实时更新IT行业资讯、零基础教程、实战案例,为IT从业者、技术爱好者提供专业的学习交流平台。

Copyright © 2021-2026 易频IT社区. All Rights Reserved. 备案号:闽ICP备2023013482号 网站地图