redis 锁

redis 锁分类

  • INCR、SETNX、SET
1
2
3
4
5
6
7
8
9

$redis->incr($key);
$redis->expire($key, $ttl); //设置生成时间为1秒

1、 客户端A请求服务器获取key的值为1表示获取了锁
2、 客户端B也去请求服务器获取key的值为2表示获取锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求的时候获取key的值为1表示获取锁成功
5、 客户端B执行代码完成,删除锁
1
2
3
4
5
6
7
8
9

$redis->setNX($key, $value);
$redis->expire($key, $ttl);

1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功
2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求设置key的值,设置成功
5、 客户端B执行代码完成,删除锁
1
2
3
4
5
6
7
8

$redis->set($key, $value, array('nx', 'ex' => $ttl)); //ex表示秒

1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功
2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求设置key的值,设置成功
5、 客户端B执行代码完成,删除锁
  • 锁临界问题

    • 解决方案 【循环锁】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

do {
$timeout = 10;
$roomid = 10001;
$key = 'room_lock';
$value = 'room_'.$roomid; //分配一个随机的值针对问题3
$isLock = Redis::set($key, $value, 'ex', $timeout, 'nx');//ex 秒
if ($isLock) {
if (Redis::get($key) == $value) { //防止提前过期,误删其它请求创建的锁
//执行内部代码
Redis::del($key);
continue;//执行成功删除key并跳出循环
}
} else {
usleep(5000); //睡眠,降低抢锁频率,缓解redis压力,针对问题2
}
} while(!$isLock);

{———-}

封装spring-data-redis操作redis分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 操作redis获取全局锁
*
* @param redisLock 锁的名称
* @param timeout 获取的超时时间
* @param tryInterval 多少ms尝试一次
* @return true 获取成功,false获取失败
*/
public boolean getLock(RedisLock redisLock, long timeout, long tryInterval){
try{
if(StringUtils.isEmpty(redisLock.getKey()) || StringUtils.isEmpty(redisLock.getValue())){
return false;
}
while (true){
List<String> keys = new ArrayList<>(1);
keys.add(redisLock.getKey());
String result = this.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
return commands.set(redisLock.getKey(), redisLock.getValue(), "NX", "PX", redisLock.getLockExpireTime());
}
});
if (StringUtils.isNotEmpty(result)){
return true;
}
Thread.sleep(tryInterval);
}
}catch (Exception e){
e.printStackTrace();
return false;
}
}

this.execute(new RedisCallback() //封装 spring-data-redis 分布式锁
//获取redisnativeConnection 初始化为 JedisCommands返回操作类
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
//使用commands操作类设置数据锁
return commands.set(redisLock.getKey(), redisLock.getValue(), “NX”, “PX”, redisLock.getLockExpireTime());

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

/**
* 释放锁
*/
public void releaseLock(RedisLock redisLock) {
final List<String> keys = new ArrayList(1);
final List<String> args = new ArrayList(1);
keys.add(redisLock.getKey());
args.add(redisLock.getValue());
Long result = this.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return (Long) ((JedisCluster) nativeConnection).eval(EQ_DEL_STR, keys, args);
}

// 单机模式
else if (nativeConnection instanceof Jedis) {
return (Long) ((Jedis) nativeConnection).eval(EQ_DEL_STR, keys, args);
}
return 0L;
}
});
}

`

释放锁 调用spring-data-redis封装类 ResdTemplate.excute()

返回类型初始化为 JedisCluster 或者 Jedis操作类 调用 eval方法 解锁数据