Redis SET 以及 分布式锁设计代码实现

转自:https://www.cnblogs.com/seeseabky/p/10374133.html

官网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:

行为

  • EX seconds -- Set the specified expire time, in seconds.
  • EX  --设置指定的过期时间以秒为单位。
  • PX milliseconds -- 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 SETNXSETEXPSETEX, it is possible that in future versions of Redis these three commands will be deprecated and finally removed.

注解:自此SET命令 选项能够取代SETNXSETEXPSETEX,在将来的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

<span style="color:#000000"><code><span style="color:#0000ff">SET</span> mykey <span style="color:#a31515">"Hello"</span>
<span style="color:#0000ff">GET</span> mykey</code></span>

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实现分布式锁

官网提供了两种方式实现分布式锁

相关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

<span style="color:#000000"><code>        <span style="color:green"><!--redis jedis --></span>  
        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>  
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>redis.clients<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>  
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>jedis<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>  
            <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>2.9.0<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>  
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>  
        
        <span style="color:green"><!-- redis redisson--></span>  
        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>  
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.redisson<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>  
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>redisson<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>  
            <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>2.15.2<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>  
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>   </code></span>

java代码

<span style="color:#000000"><code><span style="color:#0000ff">package</span> com.util;

<span style="color:#0000ff">import</span> java.util.UUID;
<span style="color:#0000ff">import</span> java.util.concurrent.atomic.AtomicLong;

<span style="color:#0000ff">import</span> org.redisson.Redisson;
<span style="color:#0000ff">import</span> org.redisson.api.RLock;
<span style="color:#0000ff">import</span> org.redisson.api.RedissonClient;
<span style="color:#0000ff">import</span> org.redisson.config.Config;

<span style="color:#0000ff">import</span> redis.clients.jedis.Jedis;
<span style="color:#0000ff">import</span> redis.clients.jedis.JedisPool;

<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">RedisUtil</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">RedisBasic</span> {

    <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> AtomicLong ThreadRedisSimpleCount = <span style="color:#0000ff">new</span> AtomicLong();
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> AtomicLong ThreadRedisRedissonCount = <span style="color:#0000ff">new</span> AtomicLong();

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">main</span>(String[] args) {
        
        <span style="color:#0000ff">final</span> RedisUtil redisUtil = <span style="color:#0000ff">new</span> RedisUtil();
        <span style="color:green">// SET命令方式实现lock</span>
        <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < 10; i++) {
            <span style="color:#0000ff">new</span> Thread(<span style="color:#a31515">"【简单命令方式】"</span> + ThreadRedisSimpleCount.incrementAndGet()) {
                <span style="color:#2b91af">@Override</span>
                <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">run</span>() {
                    String token = UUID.randomUUID().toString();
                    <span style="color:#0000ff">if</span> (<span style="color:#a31515">"OK"</span>.equals(redisUtil.set(<span style="color:#a31515">"REDIS_SIMPLE_LOCK"</span>, token, RedisBasic.RedisType.JEDIS_SINGALE))) {
                        System.out.println(<span style="color:#a31515">"已获得锁*****************"</span> + Thread.currentThread().getName());
                        redisUtil.delKey(<span style="color:#a31515">"REDIS_SIMPLE_LOCK"</span>, token, RedisBasic.RedisType.JEDIS_SINGALE);
                        System.out.println(<span style="color:#a31515">"已解除锁*****************"</span> + Thread.currentThread().getName());
                    } <span style="color:#0000ff">else</span> {
                        System.out.println(<span style="color:#a31515">"未获得锁*****************"</span> + Thread.currentThread().getName());
                    }
                }
            }.start();
        }

        <span style="color:green">// REDISSON方式实现lock</span>
        <span style="color:green">// REDISSON的解锁只能解锁当前线程的锁</span>
        <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < 10; i++) {
            <span style="color:#0000ff">new</span> Thread(<span style="color:#a31515">"【REDISSON方式】"</span> + ThreadRedisRedissonCount.incrementAndGet()) {
                <span style="color:#2b91af">@Override</span>
                <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">run</span>() {
                    RLock lock = redisUtil.getLock(<span style="color:#a31515">"REDIS_REDISSON_LOCK"</span>);
                    <span style="color:#0000ff">if</span> (redisUtil.lock(lock, 0, 30)) {
                        System.out.println(<span style="color:#a31515">"已获得锁*****************"</span> + Thread.currentThread().getName());
                        redisUtil.unlock(lock);
                        System.out.println(<span style="color:#a31515">"已解除锁*****************"</span> + Thread.currentThread().getName());
                    } <span style="color:#0000ff">else</span> {
                        System.out.println(<span style="color:#a31515">"未获得锁*****************"</span> + Thread.currentThread().getName());
                    }
                }
            }.start();
        }
    
    }

    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> Jedis <span style="color:#a31515">getJedisSingle</span>() {
        <span style="color:#0000ff">return</span> <span style="color:#0000ff">new</span> Jedis(<span style="color:#a31515">"127.0.0.1"</span>, 6378);
    }

    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> JedisPool <span style="color:#a31515">getJedisPool</span>() {
        <span style="color:#0000ff">return</span> <span style="color:#0000ff">new</span> JedisPool(<span style="color:#a31515">"127.0.0.1"</span>, 6378);
    }

    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> RedissonClient <span style="color:#a31515">getRedisson</span>() {
        Config config = <span style="color:#0000ff">new</span> Config();
        config.useSingleServer().setAddress(<span style="color:#a31515">"redis://127.0.0.1:6378"</span>);
        <span style="color:#0000ff">return</span> Redisson.create(config);
    }
}</code></span>

执行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的脚本实现

<span style="color:#000000"><code>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">default</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">delKey</span>(String key, String value, RedisType redisType) {

        <span style="color:#0000ff">if</span> (redisType == RedisType.JEDIS_SINGALE) {
            List<String> keys = <span style="color:#0000ff">new</span> ArrayList<>();
            keys.add(key);
            List<String> args = <span style="color:#0000ff">new</span> ArrayList<>();
            args.add(value);
            getJedisSingle()
                    .eval(<span style="color:#a31515">"if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"</span>,
                            keys, args);
        }
    }</code></span>

Redisson获取锁的脚本源码

<span style="color:#000000"><code>    <T> RFuture<T> <span style="color:#a31515">tryLockInnerAsync</span>(<span style="color:#0000ff">long</span> leaseTime, TimeUnit unit, <span style="color:#0000ff">long</span> threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        <span style="color:#0000ff">return</span> commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  <span style="color:#a31515">"if (redis.call('exists', KEYS[1]) == 0) then "</span> +
                      <span style="color:#a31515">"redis.call('hset', KEYS[1], ARGV[2], 1); "</span> +
                      <span style="color:#a31515">"redis.call('pexpire', KEYS[1], ARGV[1]); "</span> +
                      <span style="color:#a31515">"return nil; "</span> +
                  <span style="color:#a31515">"end; "</span> +
                  <span style="color:#a31515">"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then "</span> +
                      <span style="color:#a31515">"redis.call('hincrby', KEYS[1], ARGV[2], 1); "</span> +
                      <span style="color:#a31515">"redis.call('pexpire', KEYS[1], ARGV[1]); "</span> +
                      <span style="color:#a31515">"return nil; "</span> +
                  <span style="color:#a31515">"end; "</span> +
                  <span style="color:#a31515">"return redis.call('pttl', KEYS[1]);"</span>,
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }</code></span>

Redisson解锁的脚本源码

<span style="color:#000000"><code>
<span style="color:#0000ff">protected</span> RFuture<Boolean> <span style="color:#a31515">unlockInnerAsync</span>(<span style="color:#0000ff">long</span> threadId) {
        <span style="color:#0000ff">return</span> commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                <span style="color:#a31515">"if (redis.call('exists', KEYS[1]) == 0) then "</span> +
                    <span style="color:#a31515">"redis.call('publish', KEYS[2], ARGV[1]); "</span> +
                    <span style="color:#a31515">"return 1; "</span> +
                <span style="color:#a31515">"end;"</span> +
                <span style="color:#a31515">"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then "</span> +
                    <span style="color:#a31515">"return nil;"</span> +
                <span style="color:#a31515">"end; "</span> +
                <span style="color:#a31515">"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); "</span> +
                <span style="color:#a31515">"if (counter > 0) then "</span> +
                    <span style="color:#a31515">"redis.call('pexpire', KEYS[1], ARGV[2]); "</span> +
                    <span style="color:#a31515">"return 0; "</span> +
                <span style="color:#a31515">"else "</span> +
                    <span style="color:#a31515">"redis.call('del', KEYS[1]); "</span> +
                    <span style="color:#a31515">"redis.call('publish', KEYS[2], ARGV[1]); "</span> +
                    <span style="color:#a31515">"return 1; "</span>+
                <span style="color:#a31515">"end; "</span> +
                <span style="color:#a31515">"return nil;"</span>,
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));

    }</code></span>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值