基于Redis实现分布式锁

简介: 基于Redis实现分布式锁

正文


一、Redis分布式锁原理


分布式锁需要满足以下几点要求


在分布式系统环境下,一个方法在同一时间只能被一个机器的的一个线程执行 即独享(相互排斥);


高可用的获取锁与释放锁 ;


高性能的获取锁与释放锁;


具备可重入特性;


具备锁失效机制,防止死锁;


具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。


目前市面上普遍使用的分布式锁实现的方式主要有三种一种是基于数据库,一种是基于Zookeeper(点击链接可查看基于Zk实现的分布式锁),还有一种就是现在说的基于Redis实现的分布式锁。本文只说单机状态下Redis的分布式锁。如果在主从复制或者集群模式下(如果你能允许下面这些小概率的事件发生,同样也可以用在集群或者主从复制的情况下),如果主节点数据还没有同步到从节点的时候,突然宕机了,然后从节点变为主节点,获取到了锁,就违背了分布式锁的相互排斥原则。


那么Redis实现分布式锁的原理是什么?


Redis实现分布式锁基于SetNx命令,因为在Redis中key是保证是唯一的。所以当多个线程同时的创建setNx时,只要谁能够创建成功谁就能够获取到锁。SetNx命令 每次SetNx检查该 key是否已经存在,如果已经存在的话不会执行任何操作,返回为0。 如果不存在的话直接新增该key,新增成功返回1。


二、Redis实现分布式锁


       基于Jedis实现


工具类

package com.xiaojie.utils;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
 * @ClassRedisPoolUtil
 * @Description jedispool连接工具
 * @AuthorAdministrator
 * @Date {Date}{Time}
 * @Version 1.0
 **/
@Component
public class RedisPoolUtil {
    private static String IP = "192.168.139.154";
    //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, "xiaojie");
        } 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;
    }
}


获取锁


package com.xiaojie.lock;
import com.xiaojie.utils.RedisPoolUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.util.UUID;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 基于redis实现分布式锁
 * @date 2021/9/19 21:17
 */
@Component
public class RedisDistributeLock {
    @Autowired
    private RedisPoolUtil redisPoolUtil;
    /**
     * @description:
     * @param: key 键值
     * @param: notLockTimeOut 为获取锁的等待时间
     * @param: timeOut 键值过期时间
     * @return: java.lang.String
     * @author xiaojie
     * @date: 2021/9/19 21:15
     */
    public String getLock(String key, Integer notLockTimeOut, Long timeOut) {
        Jedis jedis = redisPoolUtil.getJedis();
        //计算锁的超时时间
        Long endTime = System.currentTimeMillis() + notLockTimeOut;
        //当前时间比锁的超时时间小,证明获取锁时间没有超时,继续获取
        while (System.currentTimeMillis() < endTime) {
            String lockValue = UUID.randomUUID().toString();
            //如果设置成功返回1 获取到锁,如果返回0 获取锁失败,继续获取
            if (1 == jedis.setnx(key, lockValue)) {
                //设置键值过期时间,防止死锁
                jedis.expire(key, timeOut);
                return lockValue;
            }
        }
        //关闭连接
        try {
            if (jedis != null) {
                jedis.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * @description: 释放锁
     * @param: key
     * @return: boolean
     * @author xiaojie
     * @date: 2021/9/19 21:25
     */
    public boolean unLock(String key, String lockValue) {
        //获取Redis连接
        Jedis jedis = redisPoolUtil.getJedis();
        try {
            // 判断获取锁的时候保证自己删除自己
            if (lockValue.equals(jedis.get(key))) {
                return jedis.del(key) > 0 ? true : false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;
    }
}


基于RedisTemplate实现


package com.xiaojie.lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 基于RedisTemplate实现分布式锁
 * @date 2021/9/19 21:57
 */
@Component
public class RedisTemplateDistributeLock {
    private static final Long SUCCESS = 1L;
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * @description: 获取锁
     * @param: lockKey 键值
     * @param: value 唯一的值 区别那个获取到的锁
     * @param: expireTime 过期时间
     * @return: boolean
     * @author xiaojie
     * @date: 2021/9/19 22:15
     */
    public boolean getLock(String lockKey, String value, Long expireTime) {
        try {
            //lua脚本
            String script = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end else return 0 end";
            Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), value, expireTime);
            return SUCCESS.equals(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    /**
     * @description: 释放锁
     * @param:
     * @param: lockKey
     * @param: value
     * @return: boolean
     * @author xiaojie
     * @date: 2021/9/19 22:18
     */
    public boolean releaseLock(String lockKey, String value) {
        try {
            //lua脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
            Long result = (Long) redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
            return SUCCESS.equals(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}


三、测试


分布式锁在分布式定时任务的测试


package com.xiaojie.schedule;
import com.xiaojie.entity.User;
import com.xiaojie.lock.RedisDistributeLock;
import com.xiaojie.lock.RedisTemplateDistributeLock;
import com.xiaojie.mapper.UserMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.UUID;
/**
 * @ClassMyTask
 * @Description 模拟定时任务
 * @AuthorAdministrator
 * @Date {Date}{Time}
 * @Version 1.0
 **/
@Component
public class MyTask {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisDistributeLock  redisDistributeLock;
    private static final String REDISKEY = "xiaojie_redis_lock";
    private static final Integer NOLOCK_TIMEOUT=5;
    private static final Long TIME_OUT=5L;
    private static final String value= UUID.randomUUID().toString();
     int i;
    @Scheduled(cron = "0/5 * * * * ? ")
    public void scheduledTask(){
        String lockValue = redisDistributeLock.getLock(REDISKEY, NOLOCK_TIMEOUT, TIME_OUT);
        if(StringUtils.isBlank(lockValue)){
            System.out.println("获取锁失败");
            return;
        }
        List<User> users = userMapper.selectAll();
        for (User user:users){
            user.setNum(user.getNum()+1);
            int updateNum = userMapper.updateNum(user.getNum(),user.getId());
            System.out.println(updateNum);
        }
        i++;
        System.out.println("执行了"+i+"次");
        //释放锁
        redisDistributeLock.unLock(REDISKEY,lockValue);
    }
}


在不加锁的情况下,开启两台机器


222.png

111.png

444.png


可见一个机器上运行了5次一次运行7次理论上应该是7+5=12,但是数据库结果却是8。

加上锁之后


333.png

222.png

111.png


一个执行了7次一个执行了2次 7+2=9,正好符合我们测试需要的结果。


完整代码:spring-boot: Springboot整合redis、消息中间件等相关代码


参考:springboot的RedisTemplate实现分布式锁_long2010110的专栏-CSDN博客_redistemplate实现分布式锁

相关文章
|
7月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
7月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
634 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
8月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
547 2
|
8月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
546 6
|
9月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
9月前
|
NoSQL Redis
Lua脚本协助Redis分布式锁实现命令的原子性
利用Lua脚本确保Redis操作的原子性是分布式锁安全性的关键所在,可以大幅减少由于网络分区、客户端故障等导致的锁无法正确释放的情况,从而在分布式系统中保证数据操作的安全性和一致性。在将这些概念应用于生产环境前,建议深入理解Redis事务与Lua脚本的工作原理以及分布式锁的可能问题和解决方案。
327 8
|
10月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
2778 7
|
6月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
659 25
|
6月前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
1114 3
|
7月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
319 1
Redis专题-实战篇二-商户查询缓存
下一篇
开通oss服务