开发者

redis队列和秒杀应用方式

目录
  • 1.简述
  • 2.秒杀的原理
  • 3.秒杀的代码实现
  • 4.关于Redis里的锁
    • 4.1 先说一下乐观锁
    • 4.2 再说一下 悲观锁
  • 总结

    1.简述

    redis队列一般用于缓解数据库压力 ,诸如秒杀,邮件群发,消息推送等等

    redis的加入能很好的 帮助系统中 各个模块解耦。

    而Redis不仅可作为缓存服务器,还可用作消息队列。它的列表类型天生支持用作消息队列。如下图所示:

    redis队列和秒杀应用方式

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

    2.秒杀的原理

    redis队列和秒杀应用方式

    秒杀基本原理比较简单

    用户点击抢购按钮 -> 把uid 和时间存入redis的队列中 -> 服务器中有一个入库程序不停轮询redis队列是否有数据 -> 如果有存入数据库

    这里面有2点需要注意一下:

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

    3.秒杀的代码实现

    用户操作秒杀:

    header("Content-type: text/html; charset=utf-8");
    $redis = new Redishttp://www.devze.com();
    $redis->connect('127.0.0.1', 6379);
    
    $redis_name= "miaosha";
    //库存
    $nums = 10;
    //用户id
    $user_id = $_GET['uid'];
    if(($redis->llen($redis_name)) <  $nums ){
    	$redis->lpush($redis_name,json_encode(array('uid'=>$user_id,'time'=>microtime())));
    	echo $user_id."秒杀成功!";
    	pythonexit();
    }else{
    	echo "秒杀失败!";
    	exit();
    }
    $redis->close();

    后台处理秒杀队列:

    header("Content-type: text/html; charset=utf-8");
    error_reporting(E_ALL);
    require_once './db.php';
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    
    $redis_name = "miaosha";
    
    //数据库
    $configs =array('host'=>'127.0.0.1','port'=>'3306','user'=>'***','passhttp://www.devze.comwd'=>'','dbname'=>'test');
    $mysql = new MMysql($configs);
    
    //处理开始
    while ($count = $redis->lLen($redis_name)) {
        $task = $redis->rpop($redis_name);
    	
        $taskdata = json_decode($task, true);
    	$data = array(
        	'uid'=>$taskdata['uid'],
        	'time'=>$taskdata['time'],
        );
    
    	$rs = $mysql->insert('redis',$data);
    	if(!$rs){
    		//由于我们是在右边取,所以如果数据插入失败了要从左边放回去(重新排队),以免影响队列中其他元素的处理
    		$redis->lpush($redis_name,$task);
    		echo "处理失败<br>";php
    	}else{
    		echo "处理成功<br>";
    	}
        
    	sleep(1);
    }
    
    $redis->close();

    4.关于redis里的锁

    4.1 先说一下乐观锁

    乐观锁,顾名思义,乐观的认为数据不会被修改,只有当更新时才去判断数据是否被修改过,通常用版本号或时间戳来实现。

    redis中的事务通过watch和multi来实现。

    WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATChttp://www.devze.comH监控的键值)

    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379, 60);
     
     //
    $tnums = $redis->get('goods_stock_nums');
    
    //设置商品的库存
    if($tnums==0){
    	echo "活动已经结束,明天请早end!";
    	exit();
    }
    
    //监视该key
    $redis->watch('goods_stock_nums');
    
    //sleep(5);
    //开启事务
    $redis->multi();
     
    //修改库存数
    $redis->decr('goods_stock_nums');
     
    //提交事务,如果在此期间有其他请求修改了该key,那么事务会失败
    if ($redis->exec()) {
        echo '抢购成功suc';
    } else {
        echo '数据错误,请重新再试fail';
    }

    可以看到我在程序中加入了sleep(5) 这行代码。

    这是方便我在客户端去实验

    redis队列和秒杀应用方式

    如果我在运行上面这段代码过程中,我用客户端修改了这个值。

    那么上面这段代码就会失败,返回 数据错误,请重新再试fail

    4.2 再说一下 悲观锁

    function getRedis()
    {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379, 60);
        return $redis;
    }
     
    function lock($key, $random)
    {
        $redis = getRedis();
        return $redis->set($key, $random, ['nx', 'ex' => 3]);
    }
     
    function unlock($key, $random)
    {
        $redis = getRedis();
        //使用Lua脚本保证原子性
        $script = 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end';
        return $redis->eval($script, [$key, $random], 1);
    }
     
    function decrGoodsStockNums()
    {
        $redis = getRedis();
     
        //获取商品库存数
        $ret = $redis->get('goods_stock_nums');
     
        if ($ret === false) {
            return false;
        }
     
        if ($ret <= 0) {
            return false;
        }
     
        $random = mt_rand();
        //先获取锁
        if (lock('goods_stock_nums_lock', $random)) {
            //修改库存数
            $redis->decr('goods_stock_nums');
     
            //释放锁
            unlock('goods_stock_nums_lock', $random);
            return true;
        } else {
            usleep(100);
            decrGoodsStockNums();
        }
    }
     
    decrGoodsStockNums();

    上面这段引用别人的代码

    但是这种锁的机制还是不能保证事务的安全

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新数据库

    数据库排行榜