• 设为首页
  • 收藏本站
  • 积分充值
  • VIP赞助
  • 手机版
  • 微博
  • 微信
    微信公众号 添加方式:
    1:搜索微信号(888888
    2:扫描左侧二维码
  • 快捷导航
    福建二哥 门户 查看主题

    redis队列和秒杀应用方式

    发布者: 娅水9213 | 发布时间: 2025-6-19 12:37| 查看数: 108| 评论数: 0|帖子模式

    1.简述

    redis队列一般用于缓解数据库压力 ,诸如秒杀,邮件群发,消息推送等等
    redis的加入能很好的 帮助系统中 各个模块解耦。
    而Redis不仅可作为缓存服务器,还可用作消息队列。它的列表类型天生支持用作消息队列。如下图所示:

    对于服务器减少io 压力 有一定的帮助

    2.秒杀的原理


    秒杀基本原理比较简单
    用户点击抢购按钮 -> 把uid 和时间存入redis的队列中 -> 服务器中有一个入库程序不停轮询redis队列是否有数据 -> 如果有存入数据库
    这里面有2点需要注意一下:

    • 1. 插入队列的时候 ,需要判断库传,不能出现多插入
    • 2. 在入库的时候 如果出现数据插入失败的情况 需要进行回滚

    3.秒杀的代码实现

    用户操作秒杀:
    1. header("Content-type: text/html; charset=utf-8");
    2. $redis = new Redis();
    3. $redis->connect('127.0.0.1', 6379);

    4. $redis_name= "miaosha";
    5. //库存
    6. $nums = 10;
    7. //用户id
    8. $user_id = $_GET['uid'];
    9. if(($redis->llen($redis_name)) <  $nums ){
    10.         $redis->lpush($redis_name,json_encode(array('uid'=>$user_id,'time'=>microtime())));
    11.         echo $user_id."秒杀成功!";
    12.         exit();
    13. }else{
    14.         echo "秒杀失败!";
    15.         exit();
    16. }
    17. $redis->close();
    复制代码
    后台处理秒杀队列:
    1. header("Content-type: text/html; charset=utf-8");
    2. error_reporting(E_ALL);
    3. require_once './db.php';
    4. $redis = new Redis();
    5. $redis->connect('127.0.0.1', 6379);

    6. $redis_name = "miaosha";

    7. //数据库
    8. $configs =array('host'=>'127.0.0.1','port'=>'3306','user'=>'***','passwd'=>'','dbname'=>'test');
    9. $mysql = new MMysql($configs);

    10. //处理开始
    11. while ($count = $redis->lLen($redis_name)) {
    12.     $task = $redis->rpop($redis_name);
    13.        
    14.     $taskdata = json_decode($task, true);
    15.         $data = array(
    16.             'uid'=>$taskdata['uid'],
    17.             'time'=>$taskdata['time'],
    18.     );

    19.         $rs = $mysql->insert('redis',$data);
    20.         if(!$rs){
    21.                 //由于我们是在右边取,所以如果数据插入失败了要从左边放回去(重新排队),以免影响队列中其他元素的处理
    22.                 $redis->lpush($redis_name,$task);
    23.                 echo "处理失败<br>";
    24.         }else{
    25.                 echo "处理成功<br>";
    26.         }
    27.    
    28.         sleep(1);
    29. }

    30. $redis->close();
    复制代码
    4.关于redis里的锁


    4.1 先说一下乐观锁

    乐观锁,顾名思义,乐观的认为数据不会被修改,只有当更新时才去判断数据是否被修改过,通常用版本号或时间戳来实现。
    redis中的事务通过watch和multi来实现。
    WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)
    1. $redis = new Redis();
    2. $redis->connect('127.0.0.1', 6379, 60);

    3. //
    4. $tnums = $redis->get('goods_stock_nums');

    5. //设置商品的库存
    6. if($tnums==0){
    7.         echo "活动已经结束,明天请早end!";
    8.         exit();
    9. }

    10. //监视该key
    11. $redis->watch('goods_stock_nums');

    12. //sleep(5);
    13. //开启事务
    14. $redis->multi();

    15. //修改库存数
    16. $redis->decr('goods_stock_nums');

    17. //提交事务,如果在此期间有其他请求修改了该key,那么事务会失败
    18. if ($redis->exec()) {
    19.     echo '抢购成功suc';
    20. } else {
    21.     echo '数据错误,请重新再试fail';
    22. }
    复制代码
    可以看到我在程序中加入了sleep(5) 这行代码。
    这是方便我在客户端去实验

    如果我在运行上面这段代码过程中,我用客户端修改了这个值。
    那么上面这段代码就会失败,返回 数据错误,请重新再试fail

    4.2 再说一下 悲观锁
    1. function getRedis()
    2. {
    3.     $redis = new Redis();
    4.     $redis->connect('127.0.0.1', 6379, 60);
    5.     return $redis;
    6. }

    7. function lock($key, $random)
    8. {
    9.     $redis = getRedis();
    10.     return $redis->set($key, $random, ['nx', 'ex' => 3]);
    11. }

    12. function unlock($key, $random)
    13. {
    14.     $redis = getRedis();
    15.     //使用lua脚本保证原子性
    16.     $script = 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end';
    17.     return $redis->eval($script, [$key, $random], 1);
    18. }

    19. function decrGoodsStockNums()
    20. {
    21.     $redis = getRedis();

    22.     //获取商品库存数
    23.     $ret = $redis->get('goods_stock_nums');

    24.     if ($ret === false) {
    25.         return false;
    26.     }

    27.     if ($ret <= 0) {
    28.         return false;
    29.     }

    30.     $random = mt_rand();
    31.     //先获取锁
    32.     if (lock('goods_stock_nums_lock', $random)) {
    33.         //修改库存数
    34.         $redis->decr('goods_stock_nums');

    35.         //释放锁
    36.         unlock('goods_stock_nums_lock', $random);
    37.         return true;
    38.     } else {
    39.         usleep(100);
    40.         decrGoodsStockNums();
    41.     }
    42. }

    43. decrGoodsStockNums();
    复制代码
    上面这段引用别人的代码
    但是这种锁的机制还是不能保证事务的安全

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

    来源:https://www.jb51.net/database/340100e0h.htm
    免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    ×

    最新评论

    QQ Archiver 手机版 小黑屋 福建二哥 ( 闽ICP备2022004717号|闽公网安备35052402000345号 )

    Powered by Discuz! X3.5 © 2001-2023

    快速回复 返回顶部 返回列表