springboot + redis 实现分布式锁记录

分布式锁: 一个集群中多台jvm 只有一台能够获取这个锁, 其他jvm则等待。 

 

redis 内存数据库, 性能好。

分布式锁的实现方式有很多种,推荐使用如下两种:

 1: redis 实现

 2: zookeeper 实现

 

redis 通过  setnx + expire 命令实现分布式锁, 乐观锁方式获取。 key对应的value值为 不重复的随机数。

   setnx 与 set命令的区别,  setnx 存在则返回失败,不能覆盖,  set直接覆盖。

    setnx 写入一个key, key不存在则保存,返回1 成功。. key存在则不保存, 返回0失败。 key不能重复

    expire  是key的存活时间。设置key的失效时间, 防止死锁。

    key失效或者操作完毕删除key释放锁, 记住, 要将创建的key对应的value值返回, 在删除时判断value是否相等, value相等     才能删除

   

zk 通过临时节点 + 事件通知实现分布式锁。乐观锁方式获取。节点不能重复。

     zk 中有 临时节点与持久节点。 实现分布式锁用的是 临时节点,

    哪台jvm 能够在zk创建成功 临时节点, 则获取锁,  其他则等待。 节点的创建与删除都有事件通知。

    断开会话则删除节点, 释放锁。设置 session的时间防止死锁。

 

性能对比:

 redis 比  zk 快, 毕竟 redis是内存数据库, zk在硬盘上。

稳定性:

 zk  比 redis好。 zk是事件通知,稳定性比redis好。

 

代码实现:

 pom 引入依赖:

     <dependencies>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>3.1.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>
	</dependencies>

 

代码:  

/** 
 * Project Name:servive-redislock 
 * File Name:Lock.java 
 * Package Name:com.tang.service 
 * Date:2019年11月7日 下午9:21:13 
 * Copyright (c) 2019, 
 * 
 */
package com.tang.service;

import java.time.LocalDateTime;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/** 
 * ClassName: Lock
 * Function: 获取redis锁与释放redis锁
 * date: 2019年11月7日 下午9:21:13
 * 
 * @author tangjiandong
 */
public class Lock {
	
		//redis 线程池
		private JedisPool jedisPool;
		
		//唯一key, 获取锁的条件就是看是否可以创建这个key
		private static final String redisKey = "redis_lock";
		
		public Lock(JedisPool jedisPool)
		{
			 this.jedisPool = jedisPool;
		}
	  
		/**
		 * getRedisLock
		 * (获取锁)
		 * 
		 * @param beginTime 获取锁之前的等待时间 (单位毫秒)
		 * @param endTime  redis Key的失效时间 (单位毫秒)
		 * @return
		 */
	    public String getRedisLock(Long beginTime, Long endTime)
	    {
	    	Jedis conn = null;
	    	
	    	//key对应的值, 在释放锁时判断是否是传入key的value的判断条件。
	    	String value = UUID.randomUUID().toString();
	    	
	    	int expireTime = (int)(endTime / 1000); //redis时间单位为秒, 这是redis Key的失效时间
	    	
	    	//循环方式获取锁, 乐观锁方式, 系统时间加等待时间为获取锁的等待时间,
	    	Long waitTimeOut = System.currentTimeMillis() + beginTime;
	    	
	    	try {
	    		conn = jedisPool.getResource();
	    		
	    		//系统时间小于等待时间,继续循环获取锁,超时退出返回
		    	while(System.currentTimeMillis() < waitTimeOut)
		    	{
		    		//setnx 方法key值唯一,成功保存返回1, 失败返回0, 成功代表key不存在进行 保存,失败代表key存在
		    		if(1 == conn.setnx(redisKey, value))
		    		{
		    			//设置key的失效时间, 防止死锁.
		    			conn.expire(redisKey, expireTime);
		    			
		    			//返回value, 在释放锁时进行判断,防止释放不是当前锁的错误。
		    			return value;
		    		}
		    	}
			} catch (Exception e) {
				e.printStackTrace();
				//异常可以自定义。
				throw new RuntimeException("获取redis锁时异常!");
			}finally {
				if (null != conn)
				{
					conn.close();
				}
			}
	    	
	    	//获取锁超时。
	    	return null;
	    }
	    
	    
	    /**
	     * unRedisLock
	     * 释放redis锁
	     * 
	     * @return
	     */
	    public void unRedisLock(String value)
	    {
	    	Jedis conn = null;
	    	try {
	    		// 1.建立redis连接
				conn = jedisPool.getResource();
				
				String  str = conn.get(redisKey);
	    		//判断是否为当前值的锁,是当前锁才能释放
	    		if(StringUtils.isNotBlank(str) && str.equals(value))
	    		{
	    			//释放锁
		    		conn.del(redisKey);
		    		System.out.println("2:释放锁...value:" + value);
	    		}else {
	    			System.out.println("2:释放锁失败... key: " +redisKey+ " : value: " + value);
	    		}
			} catch (Exception e) {
				e.printStackTrace();
				//异常可以自定义。
				throw new RuntimeException("释放redis锁时异常!");
			}finally {
				if (null != conn)
				{
					conn.close();
				}
			}
	    }
}

/**
 * 
 * ClassName: LockService
 * Function: 配置连接redis线程池 
 * date: 2019年11月9日 下午4:27:20
 * 
 * @author tangjiandong
 */
class LockService {
    //如果使用的jedispool来得到的jedis,那cloese操作不是关闭,而是归还给线程池
	private static JedisPool jedisPool = null;
	
	static {
		//redis线程池配置
		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
		//最大连接数
		jedisPoolConfig.setMaxTotal(200);
		//最大空闲数
		jedisPoolConfig.setMaxIdle(8);
		//最大等待时间
		jedisPoolConfig.setMaxWaitMillis(1000 * 100);
		
		//在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的
		//向资源池借用连接时是否做连接有效性检查(ping),无效连接会被移除,建议false,默认值false
		//jedisPoolConfig.setTestOnBorrow(true);
		
		//配置,地址, 端口, 超时时间, 客户端密码(没有密码则不填)
		jedisPool = new JedisPool(jedisPoolConfig, "redis服务端IP", 6379, 3000, "123456");
		
	}
	
	private Lock lock = new Lock(jedisPool);
	
	public void test()
	{
		String value = lock.getRedisLock(5000L, 5000L);
		
		if(null == value)
		{
			System.out.println(Thread.currentThread().getName() + " 锁获取失败");
			return;
		}
		
		System.out.println("1:获取锁...value:" + value);
		
		//释放锁
		lock.unRedisLock(value);
	}
}

 

测试:

/** 
 * Project Name:servive-redislock 
 * File Name:Tests.java 
 * Package Name:com.tang.service 
 * Date:2019年11月8日 下午10:40:36 
 * Copyright (c) 2019, 
 * 
 */
package com.tang.service;


/** 
 * ClassName: Tests
 * Function: TODO ADD FUNCTION. 
 * date: 2019年11月8日 下午10:40:36
 * 
 * @author tangjiandong
 */
public class Tests {
  
	public static void main(String[] args) {
		LockService LockService = new LockService();
		for (int i = 0; i < 10; i++)
		{
			new Thread(new Runnable()
			{
				@Override
				public void run() 
				{
					
					LockService.test();
				}
			}).start();
		}
		
		
   }
}

 结果:

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值