【翻译】Redis SET 以及 分布式锁设计代码实现
官网SET命令地址:SET命令官网地址
Set key to hold the string value.
设置key保存字符串value
If key already holds a value, it is overwritten, regardless of its type.
如果key已经存贮一个值,那么值将被覆盖,忽略之前的类型。
Any previous time to live associated with the key is discarded on successful SET operation.
之前的与之关联的存活时间,在set成功之后,将被丢弃。
Options
选项
Starting with Redis 2.6.12 SET supports a set of options that modify its
自Redis 2.6.12 版本起 SET 命令支持一组修订的选项。
behavior:
行为
EXseconds -- Set the specified expire time, in seconds.EX秒--设置指定的过期时间以秒为单位。PXmilliseconds -- Set the specified expire time, in milliseconds.PX毫秒 -- 设置指定的过期时间以毫秒为单位。NX-- Only set the key if it does not already exist.NX-- 只有key不存在值时设置XX-- Only set the key if it already exist.XX-- 只有key存在的时候设置
Note: Since the SET command options can replace SETNX, SETEX, PSETEX, it is possible that in future versions of Redis these three commands will be deprecated and finally removed.
注解:自此SET命令 选项能够取代SETNX, SETEX, PSETEX,在将来的Redis版本这三个命令会被弃用甚至被删除。
@return
@simple-string-reply: OK if SET was executed correctly.
@simple-string-reply: 如果 SET 被正确执行,返回 OK 。
@nil-reply: a Null Bulk Reply is returned if the SET operation was not performed because the user specified the NX or XX option but the condition was not met.
@nil-reply: 在条件不符合时,返回null
@examples
SET mykey "Hello"
GET mykey
Patterns
模式
Note: The following pattern is discouraged in favor of the Redlock algorithm which is only a bit more complex to implement, but offers better guarantees and is fault tolerant.
Note: 下面的模式是不建议的,更好的方法是使用the Redlock algorithm,虽然这种方式实现起来有点复杂,但是提供了更好的保证,以及更好的容错。
The command SET resource-name anystring NX EX max-lock-time is a simple way to implement a locking system with Redis.
通过命令SET 键 值 NX EX 最大锁定时间 是一种简单的方式,实现redis锁
A client can acquire the lock if the above command returns OK (or retry after some time if the command returns Nil), and remove the lock just using DEL.
一个客户端能获得锁如果上面的命令返回OK (或者尝试多次后这个命令返回null),且用 DEL 命令去删除锁
The lock will be auto-released after the expire time is reached.
锁会在达到过期时间的时自动释放。
It is possible to make this system more robust modifying the unlock schema as follows:
尽可能的让系统更加健壮,修改解锁模式如下:
-
Instead of setting a fixed string, set a non-guessable large random string, called token.
-
不是设置一个固定的字符串,而是用一个随机的字符串,叫做token
-
Instead of releasing the lock with
DEL, send a script that only removes the key if the value matches. -
不是用
DEL命令释放锁,而是执行一个脚本,如果值匹配的时候,把key删除。
This avoids that a client will try to release the lock after the expire time deleting the key created by another client that acquired the lock later.
这是避免一个客户端在设置的锁过期之后尝试释放锁,删除另外一个客户端设置的锁。
An example of unlock script would be similar to the following:
一个解锁脚本的示例大致如下:
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
The script should be called with EVAL ...script... 1 resource-name token-value
这个脚本应该通过EVAL ...script... 1 key名字 token的值调用
代码实现
通过set命令和Redisson实现分布式锁
官网提供了两种方式实现分布式锁
-
SET,DEL 组合命令 REDIS官网相关命令
-
通过使用Redisson Redisson的GITHUB地址
相关jar包
可以直接下载jar包
jedis
https://mvnrepository.com/artifact/redis.clients/jedis/2.9.0
redisson
https://mvnrepository.com/artifact/org.redisson/redisson/2.15.2
也可以使用maven
<!--redis jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- redis redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.15.2</version>
</dependency>
java代码
package com.util;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisUtil implements RedisBasic {
private static final AtomicLong ThreadRedisSimpleCount = new AtomicLong();
private static final AtomicLong ThreadRedisRedissonCount = new AtomicLong();
public static void main(String[] args) {
final RedisUtil redisUtil = new RedisUtil();
// SET命令方式实现lock
for (int i = 0; i < 10; i++) {
new Thread("【简单命令方式】" + ThreadRedisSimpleCount.incrementAndGet()) {
@Override
public void run() {
String token = UUID.randomUUID().toString();
if ("OK".equals(redisUtil.set("REDIS_SIMPLE_LOCK", token, RedisBasic.RedisType.JEDIS_SINGALE))) {
System.out.println("已获得锁*****************" + Thread.currentThread().getName());
redisUtil.delKey("REDIS_SIMPLE_LOCK", token, RedisBasic.RedisType.JEDIS_SINGALE);
System.out.println("已解除锁*****************" + Thread.currentThread().getName());
} else {
System.out.println("未获得锁*****************" + Thread.currentThread().getName());
}
}
}.start();
}
// REDISSON方式实现lock
// REDISSON的解锁只能解锁当前线程的锁
for (int i = 0; i < 10; i++) {
new Thread("【REDISSON方式】" + ThreadRedisRedissonCount.incrementAndGet()) {
@Override
public void run() {
RLock lock = redisUtil.getLock("REDIS_REDISSON_LOCK");
if (redisUtil.lock(lock, 0, 30)) {
System.out.println("已获得锁*****************" + Thread.currentThread().getName());
redisUtil.unlock(lock);
System.out.println("已解除锁*****************" + Thread.currentThread().getName());
} else {
System.out.println("未获得锁*****************" + Thread.currentThread().getName());
}
}
}.start();
}
}
@Override
public Jedis getJedisSingle() {
return new Jedis("127.0.0.1", 6378);
}
@Override
public JedisPool getJedisPool() {
return new JedisPool("127.0.0.1", 6378);
}
@Override
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6378");
return Redisson.create(config);
}
}
执行main方法结果
已获得锁【简单命令方式】6
未获得锁【简单命令方式】7
未获得锁【简单命令方式】5
未获得锁【简单命令方式】3
未获得锁【简单命令方式】9
未获得锁【简单命令方式】1
未获得锁【简单命令方式】10
未获得锁【简单命令方式】2
未获得锁【简单命令方式】8
未获得锁【简单命令方式】4
已解除锁【简单命令方式】6
未获得锁【REDISSON方式】1
未获得锁【REDISSON方式】5
未获得锁【REDISSON方式】3
未获得锁【REDISSON方式】7
未获得锁【REDISSON方式】4
已获得锁【REDISSON方式】6
未获得锁【REDISSON方式】10
未获得锁【REDISSON方式】8
未获得锁【REDISSON方式】2
未获得锁【REDISSON方式】9
已解除锁【REDISSON方式】6
相关lua脚本
组合命令del的脚本实现
public default void delKey(String key, String value, RedisType redisType) {
if (redisType == RedisType.JEDIS_SINGALE) {
List<String> keys = new ArrayList<>();
keys.add(key);
List<String> args = new ArrayList<>();
args.add(value);
getJedisSingle()
.eval("if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end",
keys, args);
}
}
Redisson获取锁的脚本源码
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
Redisson解锁的脚本源码
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
}
posted on 2019-02-14 13:38 seesea_bky 阅读(592) 评论(0) 收藏 举报
浙公网安备 33010602011771号