【分布式锁】Redis实现分布式锁

在分布式应用中进行逻辑处理时常会遇到并发问题,例如:执行订单修改状态,需要查询订单并更新订单状态,这是非原子性操作,存在并发问题。防止一个用户在同一之间内创建多个订单;以上的问题需要通过分布式锁来解决。

下面来介绍一下redis分布式锁的实现和应用。

redis分布式锁

使用分布式锁的流程如下所示:
【分布式锁】Redis实现分布式锁_第1张图片
下面主要基于单节点的redis服务进行分析:

tryLock

通常使用setnx(set if not exit)指令为客户端上锁。

127.0.0.1:6379> set user timestamp nx ex 30 // 步骤1
OK
  1. 通常为value设置一个唯一的id或者是时间戳,尽量使value值保持唯一。
  2. nx 表示 set操作的key值是唯一的,如果redis中存在key,那么再次set的时候会返回nil
  3. ex 30 设置key的超时时间为30s。避免key值长期有效而导致客户端竞争锁导致死锁。

unlock

执行成功后执行del key value指令来释放锁。

127.0.0.1:6379> del user timestamp // 步骤2
(integer) 1

在java程序中,通常使用redis lua脚本来执行释放锁。

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

java代码实现分布式锁代码:

package com.hpu.lzl.cache.jedis;

import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
*   
* @author:awo  
* @time:2019/3/25  下午2:59 
* @Description: info
**/  
public class RedisDistributeTool {

    private Jedis jedis;
    // key的过期时间:s
    private Long expireTime = 5L;
    // 获取锁的超时时间:ms
    private Long timeOut = 15L * 1000;

    private boolean lock = false;

    private String OK = "ok";

    // 分布式锁的key值
    private String lockKey;
    // 分布式锁的value值
    private String lockValue;

    public static final String UNLOCK_LUA;

    private JedisPoolUtil jedisPoolUtil;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    public RedisDistributeTool(String keyLock){
        this.lockKey = keyLock;
        jedisPoolUtil = JedisPoolUtil.getInstance();
    }

    public RedisDistributeTool(Long expireTime,Long timeOut,String keyLock){
        this.expireTime = expireTime;
        this.timeOut = timeOut;
        this.lockKey = keyLock;
        jedisPoolUtil = JedisPoolUtil.getInstance();
    }

    /**
     * 使用setnx,保证set数据设置expire时间的原子性操作
     * 阻塞式,设置获取锁的超时时间
     * @return
     */
    public boolean tryLock(){
        Long currentTime = System.currentTimeMillis();
        lockValue = UUID.randomUUID().toString();
        while (System.currentTimeMillis() - currentTime < timeOut){
            jedis = jedisPoolUtil.getJedis();
            try {
                if (OK.equalsIgnoreCase(jedis.set(lockKey,lockValue,"nx","ex",expireTime))){
                    lock = true;
                    return lock;
                }
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                jedisPoolUtil.close();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 获取锁超时");
        return false;
    }

    /**
     * 只尝试获取一次
     * @return
     */
    public boolean tryGetLock(){
        try {
            lockValue = UUID.randomUUID().toString();
            jedis = jedisPoolUtil.getJedis();
            if (OK.equalsIgnoreCase(jedis.set(lockKey,lockValue,"nx","ex",expireTime))){
                lock = true;
                return lock;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            jedisPoolUtil.close();
        }
        return false;
    }

    /**
     * 使用lua脚本删除key:
     * 1. 校验value值的一致性
     * 2. 如果value一致,则删除key
     */
    public boolean unLock(){
        try {
            if (lock){
                List keys = new ArrayList<>();
                keys.add(lockKey);
                List values = new ArrayList<>();
                values.add(lockValue);
                jedis = jedisPoolUtil.getJedis();
                Long eval = (Long) jedis.eval(UNLOCK_LUA, keys, values);
                // eval == 0 表示锁没有释放
                lock = eval == 0;
                return eval != 0;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedisPoolUtil.close();
        }
        return false;
    }
}

完整代码见github地址:
https://github.com/hpulzl/learning_demo/blob/master/src/main/java/com/hpu/lzl/cache/jedis/RedisDistributeTool.java

RedLock

redlock锁是redis分布式锁实现较为经典的思路。主要解决了在redis集群环境下获取锁的方式。redisson的RedissonRedLock对象实现了redLock介绍的加锁算法。
这里介绍一下RedissonRedLock的使用

引入redisson的jar


      org.redisson
      redisson
      3.10.5
    

API的使用

在redis集群的环境下,将多个RLock来关联一个红锁,每个RLock对象实例来自于不同的Redisson实例。

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();

提供tryLock(long waitTime, long leaseTime, TimeUnit unit)方法,可以设置加锁的失效时间。

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁等待30秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(30, 10, TimeUnit.SECONDS);
...
lock.unlock();

具体介绍可以参考一下资料
relock算法描述: http://redis.cn/topics/distlock.html
redissonRedLock使用:https://github.com/redisson/redisson/wiki/8.-分布式锁和同步器

你可能感兴趣的:(Redis缓存)