欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Redis实现分布式锁

程序员文章站 2022-07-05 13:21:49
...

上次谈到了使用ZK实现分布式锁。话不多扯,下面是实现思路以及代码注释。

分布式锁解决方案

一共有一下几种解决方案

1.采用数据库
   不推荐 性能不好
2.基于Redis(setnx方法)
   setnx也可以存入key,如果存入成功,返回1,如果该key已经存在,返回0
   多个jvm同时在redis创建相同的一个key,使用的是setnx命令,因为redis中的key不允许重复,谁能创建key成功,就获取锁,否则进行等待。
 3.基于zk实现分布式锁
    zk是分布式协调工具,在分布式解决方案中
    当多个客户端(jvm)创建相同的一个锁,同时在zk上创建相同的一个临时节点,因为临时节点的路径保证唯一,只要谁能够创建节点成功,就可以获取锁
     没有创建成功,就是失败。当释放锁的时候,采用事件通知,通知给客户端。            
     核心思路:保证有一个jvm进行操作。

 下面是代码实现。

代码实现

LockRedis 

用于封装释放锁和获取锁的具体实现。

package com.burgess.net;

import java.util.UUID;

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

/**
 * 基于Redis实现分布式锁
 * @ClassName:   LockRedis  
 * @Description: TODO
 * @author       BurgessLee
 * @date         2019年7月1日  
 *
 */
public class LockRedis {
	//redis线程池
	private JedisPool jedisPool;
	
	//创建相同的key名称  value 随机数
	private String redisLockKey = "redisLock";
	
	public LockRedis(JedisPool jedisPool) {
		this.jedisPool = jedisPool;
	}

	//核心方法 释放锁和获取锁
	/**
	 * 获取锁
	 * 	使用循环方式重复的获取锁
	 * 	为什么获取锁之后还要设置锁的超时时间?
	 * 		为了防止死锁的产生
	 * 	zk也是需要防止死锁?
	 * 		当会话关闭,临时节点节点被删除
	 * 		通过设置链接zk的有效期
	 * @Title: 		getRedisLock  
	 * @Description: TODO
	 * @param 		@param acquireTime
	 * @param 		@param timeOut
	 * @return 		void
	 * @throws
	 */
	//获取锁
	public String getRedisLock(Long acquireTime, Long timeOut) {
		Jedis conn = null;
		try {
			//1.创建redis链接
			conn = this.jedisPool.getResource();
			//2.定义redis 对应key 的value(UUID)
			String identifierValue = UUID.randomUUID().toString();
			//3.redis实现分布式锁问题,有两个超时时间问题
			//	获取锁的超时时间 如果在规定的时间内获取不到锁,那么直接放弃
			//	上锁成功后,对应key有对应的有效期
			Long expireLock = timeOut /1000;
			//4.使用循环机制保证重复进行尝试获取锁
			//	如果没有获取到锁,要在规定的acquireTimeOt时间保证重复进行尝试获取锁
			long endTime = System.currentTimeMillis() + acquireTime;
			while(System.currentTimeMillis() < endTime) {//一直尝试获取锁
				//5.使用setnx命令插入对应的key,如果返回1,成功获取锁
				if(conn.setnx(redisLockKey, identifierValue) == 1) {
					//锁获取成功
					conn.expire(redisLockKey, expireLock.intValue());
					return identifierValue;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//关闭资源链接
			if(conn != null) {
				conn.close();
			}
		}
		//获取锁失败,或者超时
		return null;
	}
	
	//释放锁
	/**
	 * 释放锁过程
	 * @Title: 		unRedisLock  
	 * @Description: TODO
	 * @param 		@param identifierValue
	 * @return 		void
	 * @throws
	 */
	@SuppressWarnings("null")
	public void unRedisLock(String identifierValue) {
		//释放锁的时候,有两种方式,1.key自动有有效期 2.自动删除key
		Jedis conn = null;
		try {
			//获取链接
			conn = jedisPool.getResource();
			if(conn.get(redisLockKey) == identifierValue) {//保证删除的是自己的锁
				System.out.println(Thread.currentThread().getName() + "删除,或者释放锁,identifierValue:"+identifierValue);
				//删除key 缺点,有可能会出现问题是,自己创建的锁被别人删除了,所以需要保证确实是自己的锁
				conn.del(redisLockKey);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//释放资源
			if(conn == null) {
				conn.close();
			}
		}
	}
}

LockService

主要用于封装获取Redis链接,以及测试分布式锁的释放和获取锁的实现代码。当然后期可以处理成AOP方式,对整个项目需要上锁和释放锁的地方进行处理。

package com.burgess.net.service;

import com.burgess.net.LockRedis;

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

/**
 * 
 * @ClassName:   LockService  
 * @Description: TODO
 * @author       BurgessLee
 * @date         2019年7月1日  
 *
 */
public class LockService {
	
	@SuppressWarnings("unused")
	private static JedisPool pool = null;
	
	static {
		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
		//设置最大连接数
		jedisPoolConfig.setMaxTotal(200);
		//设置最大空闲数
		jedisPoolConfig.setMaxIdle(8);
		//设置最大等待时间
		jedisPoolConfig.setMaxWaitMillis(1000 * 100);
		//在borrow一个jedis实例时,是否验证,如果为true,则所有的jedis实例都是可用的
		jedisPoolConfig.setTestOnBorrow(true);
		pool = new JedisPool(jedisPoolConfig, "39.107.69.43", 6379, 3000);
	}
	
	private LockRedis lockRedis = new LockRedis(pool);
	
	//演示redis实现分布式锁
	public void seckill() {
		//获取锁
		String redisLockId = this.lockRedis.getRedisLock(5000l, 5000l);
		if(redisLockId == null) {
			System.out.println(Thread.currentThread().getName() +",获取锁失败,原因是获取锁超时");
		}
		System.out.println(Thread.currentThread().getName() +",获取锁成功,id为" + redisLockId +",执行正常业务逻辑");
		//释放锁
		this.lockRedis.unRedisLock(redisLockId);
		System.out.println(Thread.currentThread().getName() +"释放锁成功");
	}
	
	
}

ThreadRedis

创建多线程测试代码。 

package com.burgess.net;

import com.burgess.net.service.LockService;

/**
 * 
 * @ClassName:   ThreadRedis  
 * @Description: TODO
 * @author       BurgessLee
 * @date         2019年7月1日  
 *
 */
public class ThreadRedis extends Thread{
	
	private LockService lockService;
	
	public ThreadRedis(LockService lockService) {
		this.lockService = lockService;
	}

	//线程测试
	@Override
	public void run() {
		this.lockService.seckill();
	}
}

Test001

测试代码

package com.burgess.net;

import com.burgess.net.service.LockService;

/**
 * 基于Redis实现分布式锁
 * @ClassName:   Test001  
 * @Description: TODO
 * @author       BurgessLee
 * @date         2019年7月1日  
 *
 */
public class Test001 {
	
	public static void main(String[] args) {
		LockService lockService = new LockService();
		//启动50个线程测试
		for(int i = 0; i < 50; i++) {
			new ThreadRedis(lockService).start();
		}
	}
	
}

此处通过多个线程测试代码实现是否有问题。

重点总结

Redis实现分布式锁与Zk实现分布式锁区别:

相同点:集群环境下,保证允许只有一个JVM进行执行程序。

区别:
1.Redis是NoSQL数据库,主要特点是缓存
   zk是分布式协调工具,主要用于分布式解决方案
2.实现思路:
    核心都是通过获取锁,释放锁,死锁问题
    获取锁;
            zk:多个客户端(jvm)会在zk上创建同一个临时节点,只要谁能创建成功,那么谁可以获取到锁,因为zk节点命名保证路径唯一,不允许出现重复
            Redis:多个客户端(jvm)使用setnx命令会在redis上创建同一个key,只要谁能创建成功,那么谁可以获取到锁。
     释放锁:
            zk:使用直接关闭当前临时节点的会话链接
                    因为临时节点的生命周期,与会话绑定一起,如果会话链接关闭的话,该节点也会被删除
                     其他客户端链接,使用事件监听,如果该临时节点被删除的话,重新进入到获取锁的步骤
            Redis:
                     在释放锁的时候,为了确保锁的一致性问题,再删除Redis key的时候,需要判断同一个锁的ID,才可以删除
       共同特征:
              如何解决死锁现象
                  Zk:使用会话有效期方式避免死锁现象
                   Redis:是对key设置有效期解决死锁问题的
3.性能上:
       Redis会好一些,毕竟redis是缓存,直接在内存,zk是持久化硬盘
4.可靠性角度:
        zk更加可靠,因为Redis有效期不是很好控制,可能产生延迟(获取锁在设置有效期,所以会产生有效期延迟)。
                                zk自带有有效期的功能,因为带有临时节点,所以相对来说,zk比redis更好 。       

以上是大体上的一些总结,当然会不全,也可能存在一些遗漏或者错误,欢迎浏览补充。

总结

以上就是基于Redis实现分布式锁的整个过程以及代码演示,如果有不对的地方,还请留言。