redis红锁Redlock

redis红锁Redlock

文档

  1. redis单机安装
  2. redis常用的五种数据类型
  3. springboot整合redis-RedisTemplate单机模式
  4. 布隆过滤器 -Bloom Filter

官方文档

  1. 官网操作命令指南页面:https://redis.io/docs/latest/commands/?name=get&group=string
  2. Redis cluster specification
  3. Distributed Locks with Redis

说明

  1. redis版本:7.0.0
  2. springboot版本:3.2.0
  3. redisson的版本号:3.37.0

redis红锁Redlock

安装单机版redis
  1. 安装单机版redis参考文档:redis单机安装
Redlock官方文档
  1. Redlock官方文档地址:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/#the-redlock-algorithm

  2. 文档英文原文

    The Redlock Algorithm

    In the distributed version of the algorithm we assume we have N Redis masters. Those nodes are totally independent, so we don't use replication or any other implicit coordination system. We already described how to acquire and release the lock safely in a single instance. We take for granted that the algorithm will use this method to acquire and release the lock in a single instance. In our examples we set N=5, which is a reasonable value, so we need to run 5 Redis masters on different computers or virtual machines in order to ensure that they'll fail in a mostly independent way.

    In order to acquire the lock, the client performs the following operations:

    1. It gets the current time in milliseconds.
    2. It tries to acquire the lock in all the N instances in parallel, using the same key name and random value in all the instances. During step 2, when setting the lock in each instance, the client uses a timeout which is small compared to the total lock auto-release time in order to acquire it. For example if the auto-release time is 10 seconds, the timeout could be in the ~ 5-50 milliseconds range. This prevents the client from staying blocked too long when communicating with an unavailable Redis node, ensuring the connection attempt times out quickly.
    3. The client computes how much time elapsed in order to acquire the lock, by subtracting from the current time the timestamp obtained in step 1. If and only if the client was able to acquire the lock in the majority of the instances (at least 3), and the total time elapsed to acquire the lock is less than lock validity time, the lock is considered to be acquired.
    4. If the lock was acquired, its validity time is considered to be the initial validity time minus the time elapsed, as computed in step 3.
    5. If the client failed to acquire the lock for some reason (either it was not able to lock N/2+1 instances or the validity time is negative), it will try to unlock all the instances (even the instances it believed it was not able to lock).
  3. 翻译

    Redlock 算法

    在该算法的分布式版本中,我们假设有 N 个 Redis 主节点(master)。这些节点彼此完全独立,因此我们不使用主从复制或任何隐式协调系统。我们已经说明了在单实例中如何安全地获取和释放锁。这里默认分布式算法在每个单实例上都采用同样的方法来加锁和解锁。示例中取 N=5(这是一个合理值),因此需要在不同的机器或虚拟机上部署 5 个 Redis 主节点,以保证它们的故障大体上相互独立。

    为了获取锁,客户端执行以下操作:

    1. 获取当前时间(毫秒)。
    2. 在所有 N 个实例上并行尝试加锁,使用相同的 key 名和相同的随机值。在这一步里,客户端给每个实例设置锁时,都会使用一个相对于锁自动释放时间来说较小的超时时间。例如,若锁自动释放时间是 10 秒,则该超时可以在约 5~50 毫秒范围内。这样可以避免客户端在与不可用 Redis 节点通信时阻塞过久,确保连接尝试能快速超时。
    3. 客户端计算获取锁所花费的时间:用当前时间减去步骤 1 的时间戳。只有当客户端在大多数实例(至少 3 个)上成功加锁,且总耗时小于锁的有效期时,才认为加锁成功。
    4. 如果加锁成功,锁的有效期应视为:初始有效期 - 步骤 3 计算出的耗时。
    5. 如果客户端因为任何原因加锁失败(要么成功实例数不足 N/2 + 1,要么剩余有效期为负),它会尝试在所有实例上执行解锁(包括那些它认为自己可能没加锁成功的实例)。
Redisson实现Redlock
  1. 在Springboot中,为了 RedLock 配置多个独立的RedissonClient

  2. 修改配置文件application.yml,配redis连接信息,注意redlock使用的redis节点,应彼此完全独立

    yaml 复制代码
    app:
      redisson:
        nodes:
          n1:
            address: redis://127.0.0.1:6379
            password:
            database: 0
            timeout: 3000
          n2:
            address: redis://127.0.0.1:6380
            password:
            database: 0
            timeout: 3000
          n3:
            address: redis://127.0.0.1:6381
            password:
            database: 0
            timeout: 3000
    • address 必须带协议: redis:// rediss://
  3. 配置属性类(读取配置文件)

    java 复制代码
    @Configuration
    @ConfigurationProperties(prefix = "app.redisson")
    public class MultiRedissonProperties {
    
        private Map<String, Node> nodes = new HashMap<>();
    
        public Map<String, Node> getNodes() {
            return nodes;
        }
    
        public void setNodes(Map<String, Node> nodes) {
            this.nodes = nodes;
        }
    
        public static class Node {
            private String address;
            private String password;
            private int database = 0;
            private int timeout = 3000;
    
            public String getAddress() {
                return address;
            }
    
            public void setAddress(String address) {
                this.address = address;
            }
    
            public String getPassword() {
                return password;
            }
    
            public void setPassword(String password) {
                this.password = password;
            }
    
            public int getDatabase() {
                return database;
            }
    
            public void setDatabase(int database) {
                this.database = database;
            }
    
            public int getTimeout() {
                return timeout;
            }
    
            public void setTimeout(int timeout) {
                this.timeout = timeout;
            }
        }
    }
  4. 配置类:生成多个独立 RedissonClient

    java 复制代码
    @Configuration
    @EnableConfigurationProperties(MultiRedissonProperties.class)
    public class MultiRedissonConfig {
        
        private final Map<String, RedissonClient> clientHolder = new LinkedHashMap<>();
    
        @Bean
        public Map<String, RedissonClient> redissonClientMap(MultiRedissonProperties props) {
            if (props.getNodes() == null || props.getNodes().isEmpty()) {
                throw new IllegalArgumentException("app.redisson.nodes is empty");
            }
            for (Map.Entry<String, MultiRedissonProperties.Node> e : props.getNodes().entrySet()) {
                String name = e.getKey();
                MultiRedissonProperties.Node n = e.getValue();
                Config config = new Config();
                config.useSingleServer().setAddress(n.getAddress()).setDatabase(n.getDatabase()).setTimeout(n.getTimeout());
                if (n.getPassword() != null && !n.getPassword().isBlank()) {
                    config.useSingleServer().setPassword(n.getPassword());
                }
                RedissonClient client = Redisson.create(config);
                clientHolder.put(name, client);
            }
            return clientHolder;
        }
    
        @PreDestroy
        public void shutdownAll() {
            for (RedissonClient c : clientHolder.values()) {
                try {
                    c.shutdown();
                } catch (Exception ignored) {
                }
            }
        }
    }
  5. 业务类中使用(RedLock)

    java 复制代码
    @Service
    public class RedLockService {
        private final RedissonClient c1;
        private final RedissonClient c2;
        private final RedissonClient c3;
    
        public RedLockService(Map<String, RedissonClient> redissonClientMap) {
            this.c1 = required(redissonClientMap, "n1");
            this.c2 = required(redissonClientMap, "n2");
            this.c3 = required(redissonClientMap, "n3");
        }
    
        /**
         * RedLock:多数派成功即可
         */
        public boolean doWithRedLock(String businessKey, Runnable task) {
            RLock l1 = c1.getLock("lock:" + businessKey);
            RLock l2 = c2.getLock("lock:" + businessKey);
            RLock l3 = c3.getLock("lock:" + businessKey);
            RedissonRedLock redLock = new RedissonRedLock(l1, l2, l3);
            boolean locked = false;
            try {
                locked = redLock.tryLock(2, 30, TimeUnit.SECONDS);
                if (!locked) return false;
                task.run();
                return true;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } finally {
                if (locked) {
                    try {
                        redLock.unlock();
                    } catch (Exception ignored) {
                    }
                }
            }
        }
    
        /**
         * MultiLock:所有子锁都拿到才成功
         */
        public boolean doWithMultiLock(String businessKey, Runnable task) {
            RLock l1 = c1.getLock("lock:" + businessKey);
            RLock l2 = c2.getLock("lock:" + businessKey);
            RLock l3 = c3.getLock("lock:" + businessKey);
            RLock multiLock = new RedissonMultiLock(l1, l2, l3);
            boolean locked = false;
            try {
                locked = multiLock.tryLock(2, 30, TimeUnit.SECONDS);
                if (!locked) return false;
                task.run();
                return true;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } finally {
                if (locked) {
                    try {
                        multiLock.unlock();
                    } catch (Exception ignored) {
                    }
                }
            }
        }
    
        private static RedissonClient required(Map<String, RedissonClient> map, String key) {
            RedissonClient c = map.get(key);
            if (c == null) {
                throw new IllegalArgumentException("missing redisson node config: " + key);
            }
            return c;
        }
    }

参考资料

  1. https://www.bilibili.com/video/BV13R4y1v7sP

注意事项

  1. 部分内容由AI生成
  2. 如有不对,欢迎指正!!!
相关推荐
运维行者_17 小时前
企业无线网络监控的挑战与智能化演进趋势
大数据·运维·服务器·网络·数据库
国强_dev17 小时前
技术探讨:使用 stunnel 加密转发数据库连接时,如何获取客户端真实 IP?
数据库·网络协议·tcp/ip
@insist12317 小时前
系统规划与管理师-信息系统规划核心工作要点解析
数据库·软考·系统规划与管理师·软件水平考试·系统规划与管理工程师
超级数据查看器17 小时前
超级数据查看器 v10.0 发布
java·大数据·数据库·sqlite·安卓
数安3000天18 小时前
增量数据如何自动分类分级,避免目录“过期“?
大数据·数据库
南墙上的石头19 小时前
麒麟 V10 重装人大金仓 V8R6 踩坑实录(含 MySQL 兼容模式)
数据库·mysql
画中有画20 小时前
论向量数据库在项目中的应用
数据库
spider_xcxc20 小时前
Redis 数据库高质量实践指南(一)
运维·数据库·redis·oracle·云计算
l1t21 小时前
在linux和windows中解决duckdb 1.6dev版本输出执行计划报错问题
linux·运维·数据库·windows·duckdb
执子手 吹散苍茫茫烟波21 小时前
RC 隔离级别下 MySQL InnoDB 死锁典型案例
数据库·mysql