redis分布式锁,无须设置有效期,自动检测hold锁的节点是否存活

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 基于redis实现分布式锁,无需设置有效期,自动检测hold锁的节点是否存活。

1.有一个独立的keeplive守护线程保证节点存活,频率是n。
2.节点存活信息由固定前置+mac+进程id+进程启动时间,保证节点重启问题。

  1. 锁的信息由固定前置+mac+进程id+进程启动时间。
  2. 具体锁的逻辑参考lock方法。
  3. six.com.crawler.common;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

import redis.clients.jedis.JedisCluster;

/**

  • @author 作者
  • @E-mail: 359852326@qq.com
  • @date 创建时间:2017年5月27日 下午3:13:14
  • 基于redis实现分布式锁,无需给锁key加上过期时间,程序会自动检测
    */

public class RedisLock {

private static long SYSTEM_START_TIME = System.currentTimeMillis();
private static String mac;
private static String pid;
private static String nodeKeepliveInfoPre;

static {
    String name = ManagementFactory.getRuntimeMXBean().getName();
    pid = name.split("@")[0];
    try {
        InetAddress ia = InetAddress.getLocalHost();
        byte[] macBytes = NetworkInterface.getByInetAddress(ia).getHardwareAddress();
        StringBuffer sb = new StringBuffer("");
        for (int i = 0; i < macBytes.length; i++) {
            int temp = macBytes[i] & 0xff;
            String str = Integer.toHexString(temp);
            if (str.length() == 1) {
                sb.append("0" + str);
            } else {
                sb.append(str);
            }
        }
        mac = sb.toString().toUpperCase();
    } catch (Exception e) {
    }
    nodeKeepliveInfoPre = mac + "_" + pid + "_" + SYSTEM_START_TIME + "_";
}

private Thread keepliveThread;
private JedisCluster jedisCluster;
private String nodeKeepliveInfoKeyPre;
private String nodeKeepliveInfoKey;
private long loopKeepliveInterval;
private int keepliveInfoExpire;
private long checkLockIntervalTime;

public RedisLock(JedisCluster jedisCluster, String nodeKeepLiveInfoKeyPre, long loopKeepliveInterval,
        long checkLockIntervalTime) {
    this.jedisCluster = jedisCluster;
    this.nodeKeepliveInfoKeyPre = nodeKeepLiveInfoKeyPre;
    this.nodeKeepliveInfoKey = getNodeKeepliveInfoKey(mac, pid, String.valueOf(SYSTEM_START_TIME));
    this.loopKeepliveInterval = loopKeepliveInterval;
    this.keepliveInfoExpire = (int) (loopKeepliveInterval) / 1000 * 2;
    this.checkLockIntervalTime = checkLockIntervalTime;
    initKeepLive();
}

/**
 * 节点mac+进程id+进程启动时间保证节点重启问题
 */
private void initKeepLive() {
    keepliveThread = new Thread(() -> {
        String nodeInfo = null;
        while (true) {
            nodeInfo = nodeKeepliveInfoPre + String.valueOf(System.currentTimeMillis());
            jedisCluster.set(nodeKeepliveInfoKey, nodeInfo);
            jedisCluster.expire(nodeKeepliveInfoKey, keepliveInfoExpire);
            try {
                Thread.sleep(loopKeepliveInterval);
            } catch (InterruptedException e) {
            }
        }

    }, "node-keeplive-thread");
    keepliveThread.setDaemon(true);
    keepliveThread.start();
}

public void lock(String lockKey) {
    while (true) {
        if (1 == jedisCluster.setnx(lockKey, getNodeLockInfo())) {
            break;
        }
        String nodeInfo = jedisCluster.get(lockKey);
        String nodeInfoKey = getNodeKeepliveInfoKey(nodeInfo);
        String lastKeepNodeInfo = jedisCluster.get(nodeInfoKey);
        do {
            try {
                Thread.sleep(checkLockIntervalTime);// 这个时间需要根据节点刷新时间取一个合适值
            } catch (InterruptedException e) {
            }
            String tempNodeInfo = jedisCluster.get(nodeInfoKey);
            if (isNotKeeplive(lastKeepNodeInfo, tempNodeInfo)) {
                // 证明节点挂了
                unlock(lockKey);
                break;
            } else {
                lastKeepNodeInfo = tempNodeInfo;
            }
        } while (true);
    }
}

/**
 * 判断目标节点是否还在线
 * 
 * @param lastKeepliveInfo
 * @param newKeepliveInfo
 * @return
 */
private boolean isNotKeeplive(String lastKeepliveInfo, String newKeepliveInfo) {
    String[] lastMeta = lastKeepliveInfo.split("_");
    String[] newMeta = newKeepliveInfo.split("_");
    // mac pid 启动时间 系统时间
    if (lastMeta[0] != newMeta[0]) {
        // 当前Hold key的节点已被其他节点占据
        return true;
    } else {
        if (lastMeta[1] != newMeta[1]) {
            // pid发生变化表示节点已经重启
            return true;
        } else {
            if (lastMeta[2] != newMeta[2]) {
                // 启动时间发生变化表示节点已经重启
                return true;
            } else {
                if (lastMeta[3] != newMeta[3]) {
                    // 系统时间发生变化表示节点正常存活
                    return false;
                } else {
                    return true;
                }
            }
        }
    }
}

public void unlock(String lockKey) {
    jedisCluster.del(lockKey);
}

private String getNodeLockInfo() {
    return mac + "_" + pid + "_" + SYSTEM_START_TIME + "_" + System.currentTimeMillis();
}

private String getNodeKeepliveInfoKey(String mac, String pid, String systemStartTime) {
    String nodeKeepLiveInfoKey = nodeKeepliveInfoKeyPre + mac + pid + systemStartTime;
    return nodeKeepLiveInfoKey;
}

private String getNodeKeepliveInfoKey(String nodeLockInfo) {
    String[] meta = nodeLockInfo.split("_");
    return getNodeKeepliveInfoKey(meta[0], meta[1], meta[2]);
}

}

目录
相关文章
|
4月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
341 2
|
4月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
273 6
|
5月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
3月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
217 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
3月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
5月前
|
NoSQL Redis
Lua脚本协助Redis分布式锁实现命令的原子性
利用Lua脚本确保Redis操作的原子性是分布式锁安全性的关键所在,可以大幅减少由于网络分区、客户端故障等导致的锁无法正确释放的情况,从而在分布式系统中保证数据操作的安全性和一致性。在将这些概念应用于生产环境前,建议深入理解Redis事务与Lua脚本的工作原理以及分布式锁的可能问题和解决方案。
208 8
|
4月前
|
NoSQL Redis
分布式锁设计吗,你是如何实现锁类型切换、锁策略切换基于限流的?
本方案基于自定义注解与AOP实现分布式锁,支持锁类型(如可重入锁、公平锁等)与加锁策略(如重试、抛异常等)的灵活切换,并结合Redisson实现可重入、自动续期等功能,通过LUA脚本保障原子性,兼顾扩展性与实用性。
88 0
|
7月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
2月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
|
3月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
194 1
Redis专题-实战篇二-商户查询缓存

热门文章

最新文章