redis 分布式锁的几种实现方式

本文介绍了三种分布式锁的实现方式:Jedis的setnx与expire配合、RedisTemplate的setIfAbsent操作以及Redission的lua脚本实现。通过示例代码详细阐述了每种方法的使用,并提出了分布式锁的优化技巧,如在集群环境中通过特定key设计减少锁占用的节点,提升锁性能。
摘要由CSDN通过智能技术生成

1、Jedis

    /**
     * 获取分布式锁,成功返回锁标识
     * @param lockName 竞争获取锁key
     * @param acquireTimeoutInMS 获取锁超时时间
     * @param lockTimeOut 锁的超时时间(秒)
     * @return 锁标识
     */
    public String acquireLockWithTimeout(String lockName, long acquireTimeoutInMS, int lockTimeOut) {
        Jedis conn = null;

        // 锁标识
        String retIdentifier = null;
        try {
            conn = jedisPool.getResource();
            // 锁的值
            String identifier = UUID.randomUUID().toString();
			// 锁的key
            String lockKey = "lock:" + lockName;

            // 获取锁超时时间
            long end = System.currentTimeMillis() + acquireTimeoutInMS;

            while (System.currentTimeMillis() < end) {
                // key存在的情况下,不操作,返回0,不存在的情况set identifier 返回1
                if (conn.setnx(lockKey, identifier) == 1) {
                    // 设置 lockKey 生存时间,当 lockKey 过期时,它会被自动删除
                    conn.expire(lockKey, lockTimeOut);
                    // 返回锁标识
                    retIdentifier = identifier;
                    // 获取到锁直接跳出循环,返回
                    break;
                }

                try {
                	// 轮训睡眠10毫秒
                    Thread.sleep(10);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retIdentifier;
    }


    /**
     * 获取分布式锁,成功则返回锁标识,失败返回空
     * @param lockName 竞争获取锁key
     * @param lockTimeOut 锁的超时时间(秒)
     * @return 锁标识
     */
    public String acquireLock(String lockName, int lockTimeOut) {
        Jedis conn = null;

        // 锁标识
        String retIdentifier = null;
        try {
            conn = jedisPool.getResource();
            String identifier = UUID.randomUUID().toString();

            String lockKey = "lock:" + lockName;

            // key存在的情况下,不操作,返回0,不存在的情况set identifier 返回1
            if (conn.setnx(lockKey, identifier) == 1) {
                // 设置 lockKey 生存时间,当 lockKey 过期时,它会被自动删除
                conn.expire(lockKey, lockTimeOut);
                // 返回锁标识
                retIdentifier = identifier;
                // 获取到锁直接跳出循环,返回
            }

        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retIdentifier;
    }

    /**
     * 释放锁
     * @param lockName 竞争获取锁key
     * @param identifier 释放锁标识
     * @return
     */
    public boolean releaseLock(String lockName, String identifier) {
        Jedis conn = null;
        // 锁key
        String lockKey = "lock:" + lockName;
        // 返回状态
        boolean retFlag = false;
        try {
            conn = jedisPool.getResource();
            while (true) {
                // 监视lock,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
                conn.watch(lockKey);
                if (identifier.equals(conn.get(lockKey))) {
                    //开启事务
                    Transaction trans = conn.multi();
                    // 删除key
                    trans.del(lockKey);
                    // 执行所有事务块内的命令 EXEC 命令原子性
                    List<Object> results = trans.exec();
                    if (results == null) {
                        continue;
                    }
                    retFlag = true;
                }
                // 取消 WATCH 命令对所有 key 的监视
                conn.unwatch();
                break;
            }

        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retFlag;
    }

2、RedisTemplate


	/**
	 * 原子性插入(互斥),成功返回true
	 * @param key 
	 * @param value
	 * @param timeout-seconds
	 * @return
	 */
	public boolean setNX(String key,String value,int timeout) {
    	return redisTemplate.opsForValue().setIfAbsent(key, value,timeout,TimeUnit.SECONDS);
    }

3、Redission

<!-- 分布式redis锁 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>

注入bean,仅供参考,真是环境中参数可以配的优雅一点

    @Bean
    public RedissonClient redisson(){
        // 单机模式
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6380").setDatabase(1);
        return Redisson.create(config);
    }

java示例

@RequestMapping(value = "/stock")
    public String deductStock(){
        // 加密的key
        String lockKey = "product:001";
        // 1.获取锁对象
        RLock redissonLock = redisson.getLock(lockKey);
        try{
            // 2.加锁  等价于 setIfAbsent,默认key过期时间30s
            redissonLock.lock();
            // 从redis 中拿当前库存的值
            int stock = Integer.parseInt(redisUtil.get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                boolean stock1 = redisUtil.set("stock", realStock+"");
                System.out.println("扣减成功,剩余库存:" + realStock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        }finally {
            // 3.释放锁
            redissonLock.unlock();
        }
        return"end";
    }

Redission中锁底层使用的是lua脚本的方式实现的

  1. exists 判断 key 是否存在
  2. 当判断不存在则以hash的方式设置 key
  3. 然后给设置的key追加过期时间
  4. 唤起一个后台运行的定时任务,每过设置锁过期时间的1/3时执行一次,给锁进行延期处理

在这里插入图片描述
在这里插入图片描述
释放锁的时候同样lua脚本的方式
在这里插入图片描述

4、分布式锁技巧

场景1:

  • 当redis实现抢购单一商品或者多个商品时(redis集群结构),商品分布式锁定义key=product:001时,锁会占用所有节点,如果key=product:001-100:001;key=product:101-200:101的方式将锁分散,这样加锁可以使锁落在不同的节点上,充分利用集群多节点方式,实现锁的水平扩容,提升锁的性能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值