1.简述
redis队列一般用于缓解数据库压力 ,诸如秒杀,邮件群发,消息推送等等
redis的加入能很好的 帮助系统中 各个模块解耦。
而Redis不仅可作为缓存服务器,还可用作消息队列。它的列表类型天生支持用作消息队列。如下图所示:
对于服务器减少io 压力 有一定的帮助
2.秒杀的原理
秒杀基本原理比较简单
用户点击抢购按钮 -> 把uid 和时间存入redis的队列中 -> 服务器中有一个入库程序不停轮询redis队列是否有数据 -> 如果有存入数据库
这里面有2点需要注意一下:
- 1. 插入队列的时候 ,需要判断库传,不能出现多插入
- 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();
复制代码 上面这段引用别人的代码
但是这种锁的机制还是不能保证事务的安全
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
来源:https://www.jb51.net/database/340100e0h.htm
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |