JAVA秒会技术之分布式锁玩转Redis分布式锁

Posted Ethan_LiYan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA秒会技术之分布式锁玩转Redis分布式锁相关的知识,希望对你有一定的参考价值。

玩转Redis分布式锁

 

不要一看到“分布式”三个字就就得有多难,多高大上,其实简单的很。先从官方的角度解释一下,什么叫“分布式锁”:

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。


听懂了吗?没听懂举个实际中很容易碰到的例子:

Quartz定时任务应该用过吧,那么问题来了,基于分布式,假如一个应用部署在3台服务器上,同一时间点,定时任务会同时执行3次,怎么办?

有人说很简单,写入数据库之前查一下就行了。别忘了,你怎么能保证,在你查的瞬间,其他服务器的线程执行到哪里了呢?再回顾一下这个问题的关键信息:同一时间,不同服务器的不同线程,同时并行执行同一任务。此时,你就需要用到“分布式锁”了。用Redis做分布式锁只是其中的一种实现方式,相对来说,比较简单。


在此之前,先介绍几个Redis的几个小命令

①【setnx命令】是 SET if Not eXists的简写。将 key的值设为 value,当且仅当 key不存在。若给定的 key已经存在,则 SETNX不做任何动作。返回整数: 1-设置成功  ;0-设置失败。

②【ttl命令】用于获取键到期的剩余时间(秒)。返回-1代表key没有设置超时时间。

③【watch命令】可用于提供CAS(check-and-set)功能。假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null 。

④【multi命令】标志着一个事务块的开始。

⑤【transaction.exec()】事务开始执行。

⑥【unwatch命令】结束监听。


前面的铺垫说完了,直接上代码:

package com.***.cache;
import java.util.List;
import java.util.UUID;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;

public class JedisManager 

	private JedisPool jedisPool;

	public JedisPool getJedisPool() 
		return jedisPool;
	
	
	public void setJedisPool(JedisPool jedisPool) 
		this.jedisPool = jedisPool;
	
	
	public Jedis getJedis() 
		Jedis jedis = null;
		try 
			jedis = getJedisPool().getResource();
		 catch (Exception e) 
			throw new JedisConnectionException(e);
		
		return jedis;
	

	public void returnResource(Jedis jedis, boolean isBroken) 
		if (jedis == null)
			return;
		jedis.close();
	

	
	/**
	 * 获取Redis分布式锁
	 * <p>Title: lockWithTimeout</p>  
	 * @author Liyan  
	 * @date   2017年10月26日 下午3:27:53  
	 * @param locaName 锁的key
	 * @param acquireTimeout 获取锁的超时时间,毫秒
	 * @param timeout 锁本身的超时时间,毫秒
	 * @return 锁标识
	 */
    public String getLock(String locaName, long acquireTimeout, long timeout) 
        Jedis jedis = null;
        String retIdentifier = null;
        try 
            //获取连接
            jedis = jedisPool.getResource();
            //随机生成一个value
            String identifier = UUID.randomUUID().toString();
            //锁名,即key值
            String lockKey = "lock:" + locaName;
            //超时时间,上锁后超过此时间则自动释放锁
            int lockExpire = (int)(timeout / 1000);
            //获取锁的超时时间,超过这个时间则放弃获取锁
            long end = System.currentTimeMillis() + acquireTimeout;
            
            while (System.currentTimeMillis() < end) 
            	//【SETNX命令】是 SET if Not eXists的简写。 
            	//将 key的值设为 value,当且仅当 key不存在。若给定的 key已经存在,则 SETNX不做任何动作。
            	//返回整数: 1-设置成功  ;0-设置失败.
                Long setnx = jedis.setnx(lockKey, identifier);
				if (setnx == 1) 
					//设置锁的有效时间
                    jedis.expire(lockKey, lockExpire);
                    //返回value值,用于释放锁时间确认
                    retIdentifier = identifier;
                    return retIdentifier;
                
				
                //【TTL命令】用于获取键到期的剩余时间(秒)。
				//返回-1代表key没有设置超时时间,为key设置一个超时时间
                if (jedis.ttl(lockKey) == -1) 
                    jedis.expire(lockKey, lockExpire);
                

                try 
                	//防止分布式线程并发
                    Thread.sleep(10);
                 catch (InterruptedException e) 
                    Thread.currentThread().interrupt();
                
            
         catch (JedisException e) 
            e.printStackTrace();
         finally 
            if (jedis != null) 
                jedis.close();
            
        
        return retIdentifier;
    

    /**
     * 释放锁
     * <p>Title: releaseLock</p>  
     * @author Liyan  
     * @date   2017年10月26日 下午3:40:25  
     * @param lockName 锁的key
     * @param identifier 释放锁的标识
     * @return  boolean 是否释放成功
     */
    public boolean releaseLock(String lockName, String identifier) 
        Jedis jedis = null;
        String lockKey = "lock:" + lockName;
        boolean retFlag = false;
        try 
            jedis = jedisPool.getResource();
            while (true) 
                //对lockKey开始监控
            	
            	//【watch命令】可用于提供CAS(check-and-set)功能。
            	//假设我们通过WATCH命令在事务执行之前监控了多个Keys,
            	//倘若在WATCH之后有任何Key的值发生了变化,
            	//EXEC命令执行的事务都将被放弃,同时返回Null 
                jedis.watch(lockKey);
                
                //通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
                if (identifier.equals(jedis.get(lockKey))) 
                	//开启一个事务
                	
                	//【multi命令】标志着一个事务块的开始。
                    Transaction transaction = jedis.multi();
                    //删除lockKey
                    transaction.del(lockKey);
                    //执行事务,进行删除
                    List<Object> results = transaction.exec();
                    if (results == null) 
                        continue;
                    
                    
                    //标记成功结果
                    retFlag = true;
                
                
                //结束监听
                jedis.unwatch();
                break;
            
         catch (JedisException e) 
            e.printStackTrace();
         finally 
            if (jedis != null) 
                jedis.close();
            
        
        return retFlag;
    
 



以上是关于JAVA秒会技术之分布式锁玩转Redis分布式锁的主要内容,如果未能解决你的问题,请参考以下文章

分布式缓存技术之Redis_04Redis的应用实战

JAVA秒会技术之秒杀面试官秒杀Java面试官——集合篇

玩转Redis的高可用(主从、哨兵、集群)

玩转Redis-老板带你深入理解分布式锁

死磕 java同步系列之redis分布式锁进化史

死磕 java同步系列之redis分布式锁进化史