PHP+Redis链表解决高并发下商品超卖问题(实现原理及步骤)

上一篇文章聊了一下使用Redis事务来解决高并发商品超卖问题,今天我们来聊一下使用Redis链表来解决高并发商品超卖问题。

实现原理

使用redis链表来做,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用。

实现步骤

第一步,先将商品库存入队列

/**
 * 添加商品数量到商品队列
 * @param int $couponId 优惠券ID
 */
function addCoupons($couponId)
{
 //1.初始化Redis连接
 $redis = new Redis();
 if (!$redis->connect('127.0.0.1', 6379)) {
  trigger_error('Redis连接出错!!!', E_USER_ERROR);
 } else {
  echo '连接正常<br>';
 }

 //根据优惠券ID从数据库中查询该优惠券的库存量
 //$sql = "select id, stock from coupon where id = {$couponId}";
 $stock = 10; //假设10就是我们从数据库中查询出的该优惠券在数据库中的库存量

 //我们现在将这10个库存放入到以该商品ID为key的redis链表中,有几件库存,就存入多少次1,链表长度代表商品库存数
 for($i = 0; $i < $stock; $i++) {
  $redis->lPush("secKill:".$couponId.":stock", 1);
 }

 $redis->close();
}
$couponId = 11211;
addCoupons($couponId);

我们调用该方法,然后查看redis,链表中已经添加了10个元素

第二步,抢购开始,设置库存的缓存周期

这一步根据自己的业务来定,如果业务规定,这个优惠券就放出2分钟给用户抢,那么就通过expire()方法给链表设置一个有效期,即使是在有效期内没有抢完仍然有库存也不让用户抢了(由于我们公司业务不对优惠券抢券设置有效期,所以这一步我不需要做)

//设置链表有效期是两分钟
$redis->expire('key', 120);

第三步,客户端执行瞬时抢购操作

/**
 * 抢优惠券(秒杀)
 * @param int $couponId 商品ID
 * @param int $uid 用户ID
 * @return bool
 */
function secKill($couponId, $uid)
{
 //1.初始化Redis连接
 $redis = new Redis();
 if (!$redis->connect('127.0.0.1', 6379)) {
  trigger_error('Redis连接出错!!!', E_USER_ERROR);
 } else {
  echo '连接正常<br>';
 }

 //将已经成功抢购的用户添加到该以该商品ID为key的集合(set)中
 //如果用户已经在集合中,说明用户已经成功秒杀过一次了,不允许再次参与秒杀
 if ($redis->sIsMember('secKill:'.$couponId.':uid', $uid)) {
  echo '秒杀失败';
  return false;
 }

 //秒杀商品的库存key
 $key = 'secKill:'.$couponId.':stock';

 //从以该优惠券ID为key的链表中弹出一个值,如果有值,证明优惠券还有库存
 $isSockNotEmpty = $redis->lPop($key);

 //判断库存,如果库存大于0,则减库存,将该成功秒杀用户加入哈希表,如果小于等于0,秒杀结束
 if ($isSockNotEmpty != 1) {
  echo '秒杀已结束';
  return false;
 }

 //抢券成功,将优惠券ID和UID放入到队列中,由一个单独的进程队列来消费队列里的数据,向用户推送抢到的优惠券
 $redis->lPush('couponOrder', $couponId.'+'.$uid);

 //将成功抢券的用户记录到集合中,防止被已抢过的用户再次秒杀
 $redis->sAdd('secKill:'.$couponId.':uid', $uid);
 $redis->close();
 return true;
}

$couponId = 11211;
$uid  = mt_rand(1, 100);
secKill($couponId, $uid);

第四步,将成功秒杀的用户入数据库持久化数据,对于并发量不是很大的抢购,我们可以在第三步成功抢购后直接将信息写入数据库,对于并发量比较大的可以放入RabbitMQ消息队列中消费(推荐使用RabbitMQ队列而不是redis是因为RabbitMQ可以保证消息百分之百的被消费,而redis就相对没有那么稳定与可靠)

//此处代码省略
//根据自己的业务场景看看是入数据库还是放入rabbitMQ消息队列中消费

现在我们使用ab工具模拟高并发下的抢券行为(2000次请求数,100并发量)

ab -n 2000 -c 100 www.test.com/

然后我们通过Redis Desktop Manager来查看Redis的结果

同样的,couponOrder队列里已经有了10份包含用户uid和优惠券id的信息了,这些信息可以由队列消费。

同时,用户抢券集合里也保存了10个用户的UID信息。

到此这篇关于PHP+Redis链表解决高并发下商品超卖问题(实现原理及步骤)的文章就介绍到这了,更多相关php redis解决高并发下商品超卖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • thinkPHP框架通过Redis实现增删改查操作的方法详解

    本文实例讲述了thinkPHP框架通过Redis实现增删改查操作的方法.分享给大家供大家参考,具体如下: 一.概述 Redis是一个NoSQL数据库,由于其数据类型的差异,所以要在MVC框架中实现CURD操作,比较繁锁.事实上在ThinkPHP框架中,只能实现简单的缓存应用.而不像MongoDB那样能够实现常见数据库的CURD操作.本文章将通过扩展的方式,实现Redis的CURD操作,这样我们就可以像操作普通的Mysql数据库那样实现Redis的编程了. 二.实现过程 接下为将以ThinkPHP

  • PHP Redis扩展无法加载的问题解决方法

    最近在工作中需要使用PHP访问Redis,从https://github.com/phpredis/phpredis下载了phpredis,并且按照官方的说明进行了安装 phpize ./configure [--enable-redis-igbinary] make && make install 但是在重启php-fpm的过程中,发生了如下的错误,redis.so无法载入 [root@brand009 modules]# /usr/sbin/php-fpm /usr/sbin/php-

  • PHP商品秒杀问题解决方案实例详解【mysql与redis】

    本文实例讲述了PHP商品秒杀问题解决方案.分享给大家供大家参考,具体如下: 引言 假设num是存储在数据库中的字段,保存了被秒杀产品的剩余数量. if($num > 0){ //用户抢购成功,记录用户信息 $num--; } 假设在一个并发量较高的场景,数据库中num的值为1时,可能同时会有多个进程读取到num为1,程序判断符合条件,抢购成功,num减一.这样会导致商品超发的情况,本来只有10件可以抢购的商品,可能会有超过10个人抢到,此时num在抢购完成之后为负值. 解决该问题的方案由很多,可

  • PHP+redis实现的限制抢购防止商品超发功能详解

    本文实例讲述了PHP+redis实现的限制抢购防止商品超发功能.分享给大家供大家参考,具体如下: redis不仅仅是单纯的缓存,它还有一些特殊的功能,在一些特殊场景上很好用.redis中key的原子自增incrby和判断key不存在再写入的setnx方法,可以有效的防止超发. 下面使用两个不同的方式来说明利用redis做商品购买库存数量限制. 业务场景很简单,就是限制抢购5个商品,模拟并发请求抢购商品,每抢购一次对应redis中的key值增加一次,通过判断限购的数量来限制抢购,抢购成功写入成功日

  • PHP+Redis链表解决高并发下商品超卖问题(实现原理及步骤)

    上一篇文章聊了一下使用Redis事务来解决高并发商品超卖问题,今天我们来聊一下使用Redis链表来解决高并发商品超卖问题. 实现原理 使用redis链表来做,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用. 实现步骤 第一步,先将商品库存入队列 /** * 添加商品数量到商品队列 * @param int $couponId 优惠券ID */ function addCoupons($couponId) { //1.初始化Redis连接 $redis = new Redi

  • PHP+Redis事务解决高并发下商品超卖问题(推荐)

    对于一些有一定用户量的电商网站,如果只是单纯的使用关系型数据库(如MySQL.Oracle)来做抢购,对数据库的压力是非常大的,而且如果不使用好数据库的锁机制,还会导致商品.优惠券超卖的问题.我所在的公司也遇到了同样的问题,问题发生在优惠券被超量抢购上,在问题发生后我们开始想办法解决问题,由于自己使用redis比较多,我准备使用redis来解决这个问题.利用redis的高性能和事务特性来解决线上优惠券被超库存抢购的问题,下面我给出我临时解决这个问题的第一版的伪代码,去掉了一些细节: /** *

  • Redis高并发防止秒杀超卖实战源码解决方案

    目录 1:解决思路 2:添加 redis 常量 3:添加 redis 配置类 4:修改业务层 1:秒杀业务逻辑层 2:添加需要抢购的代金券 3:抢购代金券 5:postman 测试 6:压力测试 8:配置Lua 9:修改业务层 1:抢购代金券 10:压力测试 1:解决思路 将活动写入 redis 中,通过 redis 自减指令扣除库存. 2:添加 redis 常量 commons/constant/RedisKeyConstant.java seckill_vouchers("seckill_v

  • thinkphp6使用mysql悲观锁解决商品超卖问题的实现

    悲观锁介绍(百科): 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态.悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据). 使用场景举例:以MySQL InnoDB为例 商品goods表,假设商品的id为1,购买数量为1,status为1表示上架中,2表示下架.现在用户购买

  • Redis中秒杀场景下超时与超卖问题的解决方案

    目录 超时 1.redis连接超时原因 2.解决方法 超卖 1.秒杀超卖现象 2.解决方案 (1)利用乐观锁淘汰用户,解决超卖问题 (2).使用reids的 watch + multi + setnx 指令实现 在开发过程中高并发问题是很棘手的一个问题(对于博主这样的小菜鸡来说),当我们学习redis之前,知道redis是单线程运行的所以任务不会出现线程不安全问题.当我们在linux中使用ab来模拟高并发秒杀时可能会遇到两种问题,“超时和超卖”. 超时 1.redis连接超时原因 (1)虚拟机中

  • 如何利用Redis锁解决高并发问题详解

    redis技术的使用: redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动... redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力. 为什么redis的地位越来越高,我们为何不选择memcache,这是因为memcache只能存储字符串,而redis存储类型很丰富(例如有字符串.LIST.SET等),memcache每个值最大只能存储1M,存储资源非常有限,十分消耗内

  • Redis高并发场景下秒杀超卖解决方案(秒杀场景)

    目录 1 什么是秒杀 2 为什么要防止超卖 3 单体架构常规秒杀 3.1 常规减库存代码 3.2 模拟高并发 3.3 超卖现象 3.4 分析原因 4 简单实现悲观乐观锁解决单体架构超卖 4.1 悲观锁 4.2 乐观锁 4.3 redis锁setnx 4.4 使用Redision 5 分布式锁的解决方案 6 采用缓存队列防止超卖 1 什么是秒杀 秒杀最直观的定义:在高并发场景下而下单某一个商品,这个过程就叫秒杀 [秒杀场景] 火车票抢票 双十一限购商品 热度高的明星演唱会门票 … 2 为什么要防止

  • Redis解决库存超卖问题实例讲解

    商品和订单服务间使用MQ 商品服务的库存变化时,通过 MQ 通知订单服务库存变化. 原始的同步流程 查询商品信息 (调用商品服务) 计算总价(生成订单详情) 商品服务扣库存(调用商品服务) 订单入库( 生成订单) // 原始的MySQL同步流程 // 判断此代金券是否加入抢购 SeckillVouchers seckillVouchers = seckillVouchersMapper.selectVoucher(voucherId); AssertUtil.isTrue(seckillVouc

  • 高并发下restTemplate的错误分析方式

    目录 高并发下restTemplate的错误分析 1. 问题现象和分析 2. 问题解决 使用restTemplate出现的异常 高并发下restTemplate的错误分析 1. 问题现象和分析 org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection 此问题很明显是连接等待超时,而且是从连接池中获取的连接. 那么就有一个很诧异的问题,这里哪来的连接池呢?然后我去跟踪restTemplat

随机推荐