【PHP面试题22】PHP通过Redis和MySQL实现商品秒杀功能

文章目录


一、前言

本文已收录于PHP全栈系列专栏:PHP面试专区。-
计划将全覆盖PHP开发领域所有的面试题,对标资深工程师/架构师序列,欢迎大家提前关注锁定。

随着互联网的快速发展,电商行业也在不断地蓬勃发展。其中,电商秒杀活动已经成为了电商行业重要的促销方式之一。在电商秒杀活动中,用户可以以非常低的价格抢购到商品,从而带来了极高的转化率和客户满意度。但是,由于秒杀活动的特殊性,系统对性能和安全等方面的要求都很高。

本文将介绍一个基于PHP、Redis、MySQL的电商秒杀系统的实现方案,并给出核心关键代码。-
在这里插入图片描述

二、系统架构

整个秒杀系统分为四层,分别是Web层、应用层、数据层和存储层。其中:

  • Web层:负责接收用户的请求,处理请求参数和验证请求的合法性。
  • 应用层:负责对用户请求进行逻辑处理,包括判断该用户是否有抢购资格、判断商品库存是否充足等。
  • 数据层:负责操作数据库,通过读写分离提升数据库性能。
  • 存储层:负责缓存商品信息和用户信息,通过Redis集群提升系统性能。

三、技术栈

本系统使用的技术栈如下:

  • Nginx:Web服务器,负责接收用户请求并分发到应用层。
  • PHP:服务端编程语言,用于实现应用层。本系统使用最新的PHP 8版本。
  • Redis:内存缓存数据库,用于缓存商品和用户信息。
  • MySQL:关系型数据库,用于存储商品和订单信息。
  • Docker:容器化部署工具,用于统一环境和简化部署。

四、系统设计

4.1 商品设计

在秒杀系统中,商品是核心资源之一。本系统中,商品数据表结构如下:

CREATE TABLE `goods` (
    `id` INT UNSIGNED AUTO_INCREMENT COMMENT '商品ID',
    `name` VARCHAR(128) NOT NULL COMMENT '商品名称',
    `price` DECIMAL(10,2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '商品单价',
    `total` INT UNSIGNED NOT NULL COMMENT '商品库存',
    `sold` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '已售数量',
    `start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
  • id:商品ID,自增主键。
  • name:商品名称。
  • price:商品单价,精确到两位小数。
  • total:商品总库存。
  • sold:已经售出的数量。
  • start_time:秒杀开始时间,建议为具体时间点,如2023-07-01 12:00:00

4.2 用户设计

在秒杀系统中,用户需要进行身份验证和抢购资格判断。本系统中,用户数据表结构如下:

CREATE TABLE `user` (
    `id` INT UNSIGNED AUTO_INCREMENT COMMENT '用户ID',
    `name` VARCHAR(32) NOT NULL COMMENT '用户名',
    `mobile` VARCHAR(11) NOT NULL COMMENT '手机号',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
  • id:用户ID,自增主键。
  • name:用户名。
  • mobile:用户手机号,用于身份验证和资格判断。

需要注意的是,为了提高系统性能,建议使用Redis缓存用户信息,避免频繁地查询数据库。

4.3 抢单设计

在秒杀系统中,抢单是最核心的功能之一。抢单的处理流程如下:

  1. 用户发起请求,包含用户ID和商品ID。
  2. 系统首先从Redis缓存中获取该用户的购买记录,如果该用户已经购买过该商品,则直接返回“重复购买”错误。
  3. 如果该用户没有购买记录,则从Redis中获取该商品的库存数量。如果库存已经为0,则直接返回“商品已经抢光”错误。
  4. 如果该商品还有库存,则从Redis的队列中取出一个商品锁,如果队列已经空了,则直接返回“当前人数过多,请稍后再试”错误。
  5. 如果取到了商品锁,则进行事务操作,将商品库存减1,并在MySQL中添加订单记录。如果事务提交成功,则返回“抢购成功”,否则返回“系统繁忙,请稍后再试”错误。

商品锁是本系统的核心设计之一。为了保证系统的性能和可靠性,本系统采用Redis的队列实现商品锁。可以通过如下代码很容易地实现队列的获取和释放:

// 获取锁
$lock = $redis->blPop('goods_lock:' . $goodsId, 10);
if (!isset($lock[1])) {
    throw new RuntimeException('当前人数过多,请稍后再试');
}
// 处理业务逻辑
// ...
// 释放锁
$redis->rPush('goods_lock:' . $goodsId, 1);

4.5 并发控制

在秒杀活动中,高并发是不可避免的问题。因此,在系统设计中需要考虑如何处理高并发请求。本系统采用如下措施来处理高并发:

  1. 使用Redis缓存,减轻数据库的压力。
  2. 前端限流,避免过多的请求直接打到系统中。例如,可以设置每个用户每秒只能发起一次请求。
  3. 后端限流,避免请求过多导致系统崩溃。例如,可以使用semaphoreredis进行请求频率控制。
  4. 使用Redis的队列实现商品锁,避免并发造成的销量超额问题。

4.5 获取用户购买记录代码

// 缓存用户的购买记录
$key = 'user_buy:' . $userId . '_' . $goodsId;
$count = $redis->get($key);
if (false !== $count) {
    if ($count >= 1) {
        throw new RuntimeException('重复购买');
    }
}

4.7 扣减商品库存代码

// 检查库存是否足够
$stockKey = 'goods_stock:' . $goodsId;
$total = $redis->get($stockKey);
if ($total <= 0) {
    throw new RuntimeException('商品已经抢光');
}
// 将库存数减1
$newStock = $redis->decr($stockKey);
if ($newStock < 0) {
    // 库存不足
    $redis->incr($stockKey);
    throw new RuntimeException('商品已经抢光');
}

4.8 获取商品锁代码

// 获取锁
$lock = $redis->blPop('goods_lock:' . $goodsId, 10);
if (!isset($lock[1])) {
    throw new RuntimeException('当前人数过多,请稍后再试');
}
// 处理业务逻辑
// ...
// 释放锁
$redis->rPush('goods_lock:' . $goodsId, 1);

4.9 添加订单记录代码

// 添加订单记录
$data = [
    'order_no' => $orderNo,
    'user_id' => $userId,
    'goods_id' => $goodsId,
    'goods_name' => $goods['name'],
    'goods_price' => $goods['price'],
    'ctime' => time(),
];
$res = $this->db->table('order')->insert($data);
if (empty($res)) {
    throw new RuntimeException('添加订单失败');
}

总结

本文介绍了一个基于PHP、Redis、MySQL的电商秒杀系统的实现方案,并给出了核心代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值