Redis的分布式锁的设计思路+代码demo的实现

redis分布式锁的设计思路

  1. 获取锁

使用 Redis 的 setnx 命令来尝试获取锁。setnx 命令可以在 key 不存在的情况下,将 key 的值设置为指定的字符串,如果 key 已经存在,则不做任何操作。因此,我们可以将锁的 key 设置为一个唯一的字符串,然后使用 setnx 命令来尝试获取锁,如果获取成功,就说明当前线程或进程获取到了锁。

设置锁的过期时间。为了避免锁一直被占用,我们需要设置锁的过期时间,当锁的过期时间到了之后,Redis 会自动将锁删除,从而释放锁。在设置锁的过期时间时,需要根据业务逻辑的执行时间来合理设置,避免锁过期时间过短或过长。

使用唯一的值来标识锁的持有者。为了避免其他线程或进程误释放锁,我们需要使用一个唯一的值来标识锁的持有者。可以使用 UUID 或者当前线程或进程的 ID 来生成这个唯一的值。

获取锁的时候调用 setnx,如果返回 0,则该锁正在被别人使用,返回 1 则成功获取 锁。 还设置一个获取的超时时间,若超过这个时间则放弃获取 锁

  1. 释放锁

在释放锁的时候,我们可以使用 Lua 脚本来检查锁是否过期,并在不过期的情况下,使用 del 命令来释放锁。这样可以保证释放锁的操作是原子性的,避免在检查锁是否过期和释放锁之间出现并发问题。

如果锁已经过期,就不需要再释放锁了,避免误释放其他线程或进程获取的锁,当锁过期之后,其他线程就可以获取锁了

下面是Redis分布式锁的核心代码展示:

RedisUtil---本地redis客户端的连接配置

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {
    //protected static Logger logger = Logger.getLogger(RedisUtil.class);
    
    private static String IP = "127.0.0.1";

    //Redis的端口号
    private static int PORT = 6379;

    //可用连接实例的最大数目,默认值为8;
    //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = 100;

    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
    private static int MAX_IDLE = 20;

    //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
    private static int MAX_WAIT = 3000;

    private static int TIMEOUT = 3000;

    //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
    private static boolean TEST_ON_BORROW = true;

    //在return给pool时,是否提前进行validate操作;
    private static boolean TEST_ON_RETURN = true;

    private static JedisPool jedisPool = null;

    /**
     * redis过期时间,以秒为单位
     */
    public final static int EXRP_HOUR = 60 * 60; //一小时
    public final static int EXRP_DAY = 60 * 60 * 24; //一天
    public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一个月

    /**
     * 初始化Redis连接池
     */
    private static void initialPool() {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);

            jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, null);
        } catch (Exception e) {
            //logger.error("First create JedisPool error : "+e);
            e.getMessage();
        }
    }


    /**
     * 在多线程环境同步初始化
     */
    private static synchronized void poolInit() {
        if (jedisPool == null) {
            initialPool();
        }
    }


    /**
     * 同步获取Jedis实例
     *
     * @return Jedis
     */
    public synchronized static Jedis getJedis() {
        if (jedisPool == null) {
            poolInit();
        }
        Jedis jedis = null;
        try {
            if (jedisPool != null) {
                jedis = jedisPool.getResource();
            }
        } catch (Exception e) {
            e.getMessage();
            // logger.error("Get jedis error : "+e);
        }
        return jedis;
    }


    /**
     * 释放jedis资源
     *
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null && jedisPool != null) {
            jedisPool.returnResource(jedis);
        }
    }

    public static Long sadd(String key, String... members) {
        Jedis jedis = null;
        Long res = null;
        try {
            jedis = getJedis();
            res = jedis.sadd(key, members);
        } catch (Exception e) {
            //logger.error("sadd  error : "+e);
            e.getMessage();
        }
        return res;
    }
}

获取锁和释放锁的核心代码

import redis.clients.jedis.Jedis;

import java.util.UUID;

public class MayiktRedisLock {

    private static int lockSuccess = 1;

    /**
     * @param lockKey      在Redis中创建的key值
     * @param notLockTimie 尝试获取锁超时时间
     * @return 返回lock成功值
     */
    public String getLock(String lockKey, int notLockTimie, int timeOut) {
        //获取Redis连接
        Jedis jedis = RedisUtil.getJedis();
        // 计算我们尝试获取锁超时时间
        Long endTime = System.currentTimeMillis() + notLockTimie;
        //  当前系统时间小于endTime说明获取锁没有超时 继续循环 否则情况下推出循环
        while (System.currentTimeMillis() < endTime) {
            String lockValue = UUID.randomUUID().toString();
            // 当多个不同的jvm同时创建一个相同的rediskey 只要谁能够创建成功谁就能够获取锁
            if (jedis.setnx(lockKey, lockValue) == lockSuccess) {
                // 加上有效期
                jedis.expire(lockKey, timeOut / 1000);
                return lockValue;
                // 退出循环
            }
            // 否则情况下 继续循环
        }
        try {
            if (jedis != null) {
                jedis.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 释放锁
     *
     * @return
     */
    public boolean unLock(String locKey, String lockValue) {
        //获取Redis连接
        Jedis jedis = RedisUtil.getJedis();
        try {
            // 判断获取锁的时候保证自己删除自己
            if (lockValue.equals(jedis.get(locKey))) {
                return jedis.del(locKey) > 0 ? true : false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;
    }
}

RedisService demo服务的操作入口

import com.mayikt.utils.MayiktRedisLock;
import org.apache.commons.lang3.StringUtils;

public class RedisService {


    private static final String LOCKKEY = "mayikt_lock";

    public static void service() throws InterruptedException {
        // 1.获取锁
        MayiktRedisLock mayiktRedisLock = new MayiktRedisLock();
        String lockValue = mayiktRedisLock.getLock(LOCKKEY, 5000, 5000);
        if (StringUtils.isEmpty(lockValue)) {
            System.out.println(Thread.currentThread().getName() + ",获取锁失败了");
            return;
        }
        // 执行我们的业务逻辑
        System.out.println(Thread.currentThread().getName() + ",获取锁成功:lockValue:" + lockValue);
        // 3.do something  ---模拟程序执行任务
        Thread.sleep(4000);
        //Thread.sleep(10000);

        // 4.释放锁
        Boolean flag = mayiktRedisLock.unLock(LOCKKEY, lockValue);
        if (flag) {
            System.out.println(Thread.currentThread().getName() +",释放锁成功:lockValue:" +lockValue);
        }else {
            System.out.println(lockValue+"锁已经过期,不在需要释放!");
        }
    }

    public static void main(String[] args) throws InterruptedException {

        service();
    }
}

你可能感兴趣的:(redis,分布式,缓存)