it编程 > 数据库 > Redis

redis队列和秒杀应用方式

18人参与 2025-04-22 Redis

1.简述

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

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

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

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

2.秒杀的原理

秒杀基本原理比较简单

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

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

3.秒杀的代码实现

用户操作秒杀:

header("content-type: text/html; charset=utf-8");
$redis = new redis();
$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."秒杀成功!";
	exit();
}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'=>'***','passwd'=>'','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>";
	}else{
		echo "处理成功<br>";
	}
    
	sleep(1);
}

$redis->close();

4.关于redis里的锁

4.1 先说一下乐观锁

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

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

watch命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到exec命令(事务中的命令是在exec之后才执行的,所以在multi命令后可以修改watch监控的键值)

$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) 这行代码。

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

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

那么上面这段代码就会失败,返回 数据错误,请重新再试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();

上面这段引用别人的代码

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

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

Redis实现会话管理和token认证的示例代码

04-22

Redis RDB快照持久化及写操作禁止问题排查与解决

04-22

基于Redis实现消息队列的示例代码

04-22

基于Redis 实现网站PV/UV数据统计

04-22

远程连接阿里云服务器上的redis报错的问题解决

04-22

Redis的几种数据类型使用详解

04-22

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论