首页 > 数据库 >redis分布式锁探讨

redis分布式锁探讨

时间:2022-09-29 16:36:47浏览次数:61  
标签:lockName String redis 探讨 expire 线程 result return 分布式

 

基于jedis的能力,探讨了分布式锁的一些初级实现

public class DistributeLock {

    private JedisCluster jedisCluster;

    public DistributeLock() {
        Set<HostAndPort> hostAndPorts = new HashSet<>();
        hostAndPorts.add(new HostAndPort("xx.xx.xx.xx", 6379));
        jedisCluster = new JedisCluster(hostAndPorts, null, "password");
    }

    /*
    方案1:setnx + expire

    存在问题:
    1、setnx和expire操作两步不是原子操作,如果两步之间服务挂了,或者机器宕机,那么锁将永远无法获取
    2、若超时时间内(30s),业务(38s)无法执行完。锁被释放后,被其他线程获取,业务会出现并发问题
     */
    boolean getDistributeLock_1(String lockName) {
        long result = jedisCluster.setnx(lockName, "1");
        if (result == 1) {
            // 加锁成功
            jedisCluster.expire(lockName, 30);
            return true;
        }
        // 加锁失败
        return false;
    }

    /*
    方案2:lua脚本合并 setnx + expire

    解决问题:
    1、通过lua脚本将setnx和expire实现为原子操作
    存在问题:
    1、若超时时间内(30s),业务(38s)无法执行完。锁被释放后,被其他线程获取,业务会出现并发问题
     */
    boolean getDistributeLock_2(String lockName, String value) {
        String lusScript = "local result1 = redis.call('setnx', KEYS[1], ARGV[2])\n" +
                "if result1 == 1 then\n" +
                "redis.call('expire', KEYS[1], ARGV[1])\n" +
                "return 1\n" +
                "else return 0\n" +
                "end;";
        Long result = (Long) jedisCluster.eval(lusScript, Arrays.asList(lockName), Arrays.asList("30", value));
        return result == 1L;
    }

    /*
    以上两个方案,都是基于并发量不高的场景,比如分布式的任务。也并没有提供解锁的相关操作,
    1、如果锁的ttl设置长了,那其他抢锁的线程就会等待
    2、如果锁的ttl设置短了,可能会有并发问题
     */

    /*
    方案3:lua(setnx + expire) + del锁

    解决问题:
    1、线程执行完了,及时释放锁
    存在问题:
    1、若超时时间内(30s),业务(38s)无法执行完。锁被释放后,被其他线程获取,业务会出现并发问题
    2、在问题1的基础上,比如线程a先拿到锁,业务实际需要执行35s。30s后ttl失效,线程b抢到锁,5s后,线程a删除锁,a把b的锁删掉了。然后线程c抢到锁,业务混乱,锁失效
     */
    void doBusiness_3() {
        String lockName = "wzl_lock";
        // 抢锁
        boolean result = getDistributeLock_2(lockName, "1");

        if (!result) {
            return;
        }

        try {
            // 业务逻辑
            // 1、解放宝岛
            // 2、宝岛炒房
            // ...

        } finally {
            // 解锁
            jedisCluster.del(lockName);
        }
    }

    /*
    方案4:lua(setnx + expire) + del锁前判断是否是自己的锁

    解决问题:
    1、解决方案3的问题2(删除别人的锁)
    存在问题:
    1、若超时时间内(30s),业务(38s)无法执行完。锁被释放后,被其他线程获取,业务会出现并发问题
    2、解锁获取和删除两步是非原子操作
     */
    void doBusiness_4() {
        String lockName = "wzl_lock";
        String uuid = UUID.randomUUID().toString();
        // 抢锁
        boolean result = getDistributeLock_2(lockName, uuid);

        if (!result) {
            return;
        }

        try {
            // 业务逻辑
            // 1、解放宝岛
            // 2、宝岛炒房
            // ...

        } finally {
            // 解锁
            if (uuid.equals(jedisCluster.get(lockName))) {
                jedisCluster.del(lockName);
            }
        }
    }

    /*
    方案5:lua(setnx + expire) + lua(get + del)

    解决问题:
    1、解决方案4的问题2
    存在问题:
    1、若超时时间内(30s),业务(38s)无法执行完。锁被释放后,被其他线程获取,业务会出现并发问题
     */
    void doBusiness_5() {
        String lockName = "wzl_lock";
        String uuid = UUID.randomUUID().toString();
        // 抢锁
        boolean result = getDistributeLock_2(lockName, uuid);

        if (!result) {
            return;
        }

        try {
            // 业务逻辑
            // 1、解放宝岛
            // 2、宝岛炒房
            // ...

        } finally {
            // 解锁
            getAndDel(lockName, uuid);
        }
    }

    boolean getAndDel(String key, String value) {
        String lusScript = "local result1 = redis.call('get', KEYS[1])\n" +
                "if result1 == ARGV[1] then\n" +
                "redis.call('del', KEYS[1])\n" +
                "return 1\n" +
                "else return 0\n" +
                "end;";
        Long result = (Long) jedisCluster.eval(lusScript, Arrays.asList(key), Arrays.asList(value));
        return result == 1L;
    }

    public static void main(String[] args) {
        DistributeLock distributeLock = new DistributeLock();
        distributeLock.doBusiness_5();
    }
}

 

以上实现都不完善,即使是方案5,还是没有解决锁的TTL小于业务真实执行时间,导致的并发问题。

业界对于redis分布式锁的实现,目前所了解的还要数Redisson的实现。

对于方案5的遗留问题,可以通过启动一个看门狗线程,每隔一段时间,查看下锁是否过期,如果没有过期,就重置锁的ttl。

 

public class RedissonDistributeLock {

    private RedissonClient redissonClient;

    public RedissonDistributeLock() {
        Config config  =new Config();
        config.useSingleServer().setAddress("redis://xx.xx.xx.xx:6379");
        config.useSingleServer().setPassword("password");

        redissonClient = Redisson.create(config);
    }

    public void doBusiness_6() {
        String lockName = "wzl_lock";

        RLock lock = redissonClient.getLock(lockName);

        try {
            // 加锁
            lock.lock();
            // 业务逻辑
            // 1、解放世界无产阶级
            // 2、无产阶级万岁
            // ...
        } finally {
            // 解锁
            lock.unlock();
        }
    }
}

 

遗留:redisson分布式锁源码分析

标签:lockName,String,redis,探讨,expire,线程,result,return,分布式
From: https://www.cnblogs.com/wzllzw/p/16741460.html

相关文章

  • redis基础
    一、五大数据类型1、Redis键(key)①、首先创建一些key,并赋上对应值:redis127.0.0.1:6379>SETw3c1redisOKredis127.0.0.1:6379>SETw3c2mysqlOKredis127.0......
  • Redis(四)持久化
    官网介绍:http://www.redis.ioRedis提供了2个不同形式的持久化方式。RDB(RedisDataBase)AOF(AppendOfFile)第一章RDB(RedisDataBase)1.1官网介绍1.2RDB是什么?......
  • Lua 脚本在 Redis 事务中的应用实践
    Lua脚本在Redis事务中的应用实践使用过Redis事务的应该清楚,Redis事务实现是通过打包多条命令,单独的隔离操作,事务中的所有命令都会按顺序地执行。事务在执行的过程中,......
  • Lua 脚本在 Redis 事务中的应用实践
    Lua脚本在Redis事务中的应用实践使用过Redis事务的应该清楚,Redis事务实现是通过打包多条命令,单独的隔离操作,事务中的所有命令都会按顺序地执行。事务在执行的过程中......
  • 原生 Redis 跨数据中心双向同步优化实践
    原生Redis跨数据中心双向同步优化实践一、背景公司基于业务发展以及战略部署,需要实现在多个数据中心单元化部署,一方面可以实现多数据中心容灾,另外可以提升用户请求访问......
  • 原生 Redis 跨数据中心双向同步优化实践
    原生Redis跨数据中心双向同步优化实践一、背景公司基于业务发展以及战略部署,需要实现在多个数据中心单元化部署,一方面可以实现多数据中心容灾,另外可以提升用户请求访问......
  • Redis Stream实现全部节点机器推送消息
    背景有时候,在微服务时代,我们需要对全部的机器节点进行通知。在常规情况下,一个请求经过负载均衡只有一个机器可以收到。那么,如何能让全部的机器都收到同样的请求呢?需要借助......
  • linux 安装Redis以及使用
    安装redisyuminstallredis 安装完毕后,使用下面的命令启动redis服务设置redis为开机自动启动chkconfigredison 进入redis服务#进入本机redisredis-cli......
  • .Net下的分布式唯一ID
    分布式唯一ID,顾名思义,是指在全世界任何一台计算机上都不会重复的唯一Id。在单机/单服务器/单数据库的小型应用中,不需要用到这类东西。但在高并发、海量数据、大型分布式应......
  • 【Redis】缓存穿透、缓存击穿、缓存雪崩
     缓存雪崩缓存雪崩是指缓存同一时间大面积失效,所以后面的请求都会落在数据库上,造成数据库短时间内承受大量请求而崩掉。目前电商首页以及热点数据都会缓存,一般缓存都......