redis锁实现
转载
前言
当今的站点,大部分都会使用缓存,无论是用Memcache又或Redis。
其中一种用法:去拿热数据时,我们一般会加一层缓存,并且设定过期时间。但是,这里如果不注意,容易导致一个问题的产生:当并发很大时,在缓存过期的瞬间,如果有大量的请求,那么这些请求会直接打到数据库,去拿数据库的数据,这就是所谓的"缓存雪崩"。
例如,本博客中的php代码实现Memcache缓存的实例的代码,在高并发下,就会存在这种问题。
下面,就通过一些简单的代码来演示一下。
<?php
/*
测试没有使用锁,高并发下会产生的问题
*/
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$value = $redis->get('a');
//判断有无,如果有,不保存数据。
//任何情况a都是1,才是正确的值。如果在多个客户端请求时,如果a不是1,说明出问题了
if (!$value) {
//进到if,查询数据库
$id = $redis->incr('id');//模拟查询数据库
$redis->setEx('a', 100, $id);
}
上面的代码,当并发200时,发现a键的值是10。这就说明,并发200时,其中有10个请求同时去查询了数据库。这就好像,同时200个人去抢一个厕所,结果又10个人抢到了。那结果就很悲剧了,因为我们不能让10个人同时上一个厕所。
加锁
$result = $redis->setnx($key, $val);//1行
if ($result == 1) {
$redis->expire($key, 100);//2行
}
但是这种方式也是有问题的,1行与2行是两次对Redis的操作,不具备原子性。有可能在expire时,redis挂了或者expire失败了,这样的结果就是$key没有成功设置时间。
$redis->set($key, $value, Array('nx', 'ex'=>10));
SET命令从2.6.12就包含了设置过期时间功能。
nx表示当$key不存在时我们才进行set操作,ex是有效期。10单位是秒
正确写法(仅供参考)
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$value = $redis->get('aa3');
if (!$value) {
//这是模拟数据库查询,给数据库查询上锁,保证同时只能一个查询在进行
if (getLock($redis, 'test', uniqid(), 10)) {
$id = $redis->incr('idid3');//这行代码模拟的是数据库查询
$redis->setEx('aa3', 10, $id);//将数据库中查询出的数据保存到Redis的aa3键
$redis->setTimeout('idid3', 10);
}
}
//加锁
function getLock($redis, $lockName, $lockValue, $expire)
{
//px 为毫秒
return $redis->set('lock:'.$lockName, $lockValue, array('nx', 'px' => $expire));
}
参考
- https://redis.io/topics/distlock