(四)分布式锁解决方案-Redis实现方式(Jedis实现)

基于Redis的分布式锁

 

使用常用命令

SETNX

SETNX key val当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。

Expire

expire key timeout

为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。

Delete

delete key

删除key

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

实现原理

使用的是jedis来连接Redis。

实现思路

1.获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

2.获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。

3.释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

public class RedisClientPool {

	private static JedisPool jedisPool;
	
	static{
		JedisPoolConfig config = new JedisPoolConfig();
		//允许最大空闲数
		config.setMaxIdle(10);
		//允许最大连接数
		config.setMaxTotal(5000);
		//设置最大等待时间
//		config.setMaxWaitMillis(1000*100);
		//在borrow一个Jedis实例时,是否需要验证,若为true,则素有jedis示例均是可以用的
		config.setTestOnBorrow(true);
		
		//服务连接参数
		jedisPool = new JedisPool(config, "localhost",6379,30000);
	}
	
	//对外提供管道服务
	public static Jedis getRedisClient(){
		if(jedisPool!=null){
			return jedisPool.getResource();
		}
		return null;
	}
	
	//释放到jedisPool
	public static void releaseResource(JedisPool jedisPool,Jedis jedis){
		if(jedisPool!=null && jedis!=null){
			jedisPool.returnResource(jedis);
		}
	}
	
	public static void main(String[] args) {
		try {
			Jedis jedis = RedisClientPool.getRedisClient();
			jedis.set("abc", "abc");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public interface RedisLock {
	//setNx(key) timeout 事务影响 锁
	public String getLock(String lockName,int timeout,int expire);
	
	//true false  
	public boolean release(String lockName,String value);
}
  
/**
 * Redis 分布式悲观锁实现
 * @author jetsen
 *
 */
public class RedisDistributedLock implements RedisLock {
	private static final String LOCK_SUCCESS = "OK";
	private static final String SET_IF_EXIST = "NX";
	private static final String SET_WITH_expire_TIME = "PX";
	

	/**
	 * 同时多个客户端线程去setNx去创建一个数据项
	 * 
	 * 成功1: watch key discard
	 * MULTI 命令 :客户端从非事务状态切换到事务状态 SET命令全部放进一个事务队列里, 然后返回 QUEUED
	 * 失败0:等待锁  唤醒 抢占
	 *	 在没有上锁之前,获取锁的超时时间:timeout
	 */
	@Override
	public String getLock(String lockName, int timeout, int expire) {
		//获取Redis客户端 操作IO管道
		Jedis client = null;
		try {
			client = RedisClientPool.getRedisClient();
			//lockName=====> value描述锁状态唯一性
			// uuid 128位字节
			String value = UUID.randomUUID().toString();
			//会话超时时间
			long endtime = System.currentTimeMillis()+timeout;
			while(System.currentTimeMillis() < endtime){
				
				String result = client.set(lockName, value,SET_IF_EXIST,SET_WITH_expire_TIME,expire);
				if(result!=null && result.equals(LOCK_SUCCESS)){
					return value;
				}
				
				//尝试获取锁
				if(client.setnx(lockName, value)==1){
					//创建锁成功
					//处理如果网络中断死锁问题 lua脚本--scrict->执行lUA脚本 原子性操作
					if(client.ttl(lockName)==-1){
						client.expire(lockName, expire);
					}
					return value;
				}
				//减少高竞争的状态
				TimeUnit.MICROSECONDS.sleep(500);
			}
				
		} catch (Exception e) {
			try {
				throw e;
			} catch (Exception e1) {
				e1.printStackTrace();
			}
		} finally{
			if(client !=null){
				client.close();
			}
		}
		//在规定的时间内没有获取锁
		return null;
	}

	/**
	 * Redis 事务
	 */
	@Override
	public boolean release(String lockName, String value) {
		Jedis client = RedisClientPool.getRedisClient();
		try {
			while(client.exists(lockName)){
				client.watch(lockName);
				if(value.equals(client.get(lockName))){
					//进入锁进行事务操作multi exec discard
					//Queue
					Transaction transaction = client.multi();
					//ddl
					transaction.del(lockName);
					//多个命令会被入队到事务队列中,然后按先进先出(FIFO)的顺序执行
					List<Object> execQueue = transaction.exec();
					if(execQueue == null){
						continue;
					}
					return true;
				}
				client.unwatch();
				break;
			}
			
		} catch (Exception e) {
			try {
				throw e;
			} catch (Exception e1) {
				e1.printStackTrace();
			}
		} finally{
			if(client !=null){
				client.close();
			}
		}
		return false;
	}

}
public class RedisLockUtil {

	public static final String LOCK_NAME="JET_LOCK_0309";
	
	//在获取锁之前的超时时间---在尝试获取锁的时候,如果在规定时间内没有获取锁,直接放弃
	public static final int TIMEOUT=1000;
	
	//在获取锁之后的超时时间---当前获取锁成功后,对应key 有对应的有效期
	public static final int EXPIRE=1000;
	
	public RedisLockUtil() {
	}
	
	public static String idGenerator(IGenerateGlobalId iGenerateGlobalId,RedisLock lock) {
		String idGenerator = null;
		String value = lock.getLock(LOCK_NAME, TIMEOUT, EXPIRE);
		try {
			if(value!=null){
				idGenerator = iGenerateGlobalId.idGenerator();
				System.out.printf("线程名称:%s 订单号:%s \r\n", Thread.currentThread().getName(),idGenerator);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.release(LOCK_NAME, value);
		}
		return idGenerator;
		
	}
	
	
	public static void main(String[] args) {
		RedisLock lock = new RedisDistributedLock();
		idGenerator(new SequenceUnLockGenerateGlobalIdImpl(),lock);
//		for (int i = 0; i < 10; i++) {
//			idGenerator(new SequenceUnLockGenerateGlobalIdImpl(),lock);
//		}
	}
}
public class RedisTestTask implements Runnable{
	
	private IGenerateGlobalId iGenerateGlobalId;
	private RedisLock lock;
	private CountDownLatch latch;
	
	public RedisTestTask(CountDownLatch latch,RedisLock lock,IGenerateGlobalId iGenerateGlobalId) {
		this.lock = lock;
		this.latch = latch;
		this.iGenerateGlobalId = iGenerateGlobalId;
	}
	

	@Override
	public void run() {
		try {
			latch.await();
			RedisLockUtil.idGenerator(iGenerateGlobalId, lock);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} 
	}
	
	public static void main(String[] args) {
		IGenerateGlobalId entity = new SequenceUnLockGenerateGlobalIdImpl();
		ExecutorService pool = Executors.newCachedThreadPool();
		RedisLock lock = new RedisDistributedLock();
		final CountDownLatch latch = new CountDownLatch(1);
		for(int i=0;i<10;i++) {
			pool.execute(new RedisTestTask(latch,lock,entity));
		}
		latch.countDown();
		pool.shutdown();
	}

}

三种分布式对比

 

上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

从理解的难易程度角度(从低到高)

数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)

缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

Zookeeper > 缓存 > 数据库

Redis实现分布式锁与Zookeeper实现分布式锁区别

使用redis实现分布式锁

redis中的set  nx 命令,当key不存在时,才能在redis中将key添加成功,利用该属性可以实现分布式锁,并且redis对于key有失效时间,可以控制当某个客户端加锁成功之后挂掉,导致阻塞的问题。

使用Zookeeper实现分布式锁

多个客户端在Zookeeper上创建一个相同的临时节点,因为临时节点只能允许一个客户端创建成功,那么只要任意一个客户端创建节点成功,谁就成功的获取到锁,当释放锁后,其他客户端同样道理在Zookeeper节点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值