redis 设置分布式锁
博客专区 > chaun 的博客 > 博客详情
redis 设置分布式锁
chaun 发表于1年前
redis 设置分布式锁
  • 发表于 1年前
  • 阅读 31
  • 收藏 3
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: redis 设置分布式锁

 

一、pom.xml

<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>2.8.0</version>
		<type>jar</type>
	</dependency>

<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
		<version>1.7.5</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
		<version>1.7.5</version>
	</dependency>

二、java文件

package com.commnon;

import java.util.ResourceBundle;

import org.apache.commons.lang3.StringUtils;

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

public class JedisPoolManager {
	private volatile static JedisPoolManager manager;
	private final JedisPool pool;

	private JedisPoolManager() {

		try {
			// 加载redis配置
			ResourceBundle bundle = ResourceBundle.getBundle("redis");
			if (bundle == null) {
				throw new IllegalArgumentException("[redis.properties] is not found!");
			}
			// 创建jedis池配置实例
			JedisPoolConfig config = new JedisPoolConfig();
			// 设置池配置项值
			String maxTotal = bundle.getString("redis.pool.maxTotal");
			config.setMaxTotal(Integer.parseInt(maxTotal));

			String maxIdle = bundle.getString("redis.pool.maxIdle"); 
			config.setMaxIdle(Integer.parseInt(maxIdle));

			String minIdle = bundle.getString("redis.pool.minIdle");  
			config.setMinIdle(Integer.parseInt(minIdle));

			String maxWaitMillis = bundle.getString("redis.pool.maxWaitMillis");  
			config.setMaxWaitMillis(Long.parseLong(maxWaitMillis));

			String testOnBorrow = bundle.getString("redis.pool.testOnBorrow"); 
			config.setTestOnBorrow("true".equals(testOnBorrow));

			String testOnReturn = bundle.getString("redis.pool.testOnReturn"); 
			config.setTestOnReturn("true".equals(testOnReturn));

			String server = bundle.getString("redis.server"); 
			
			if (StringUtils.isEmpty(server)) {
				throw new IllegalArgumentException("JedisPool redis.server is empty!");
			}

			String[] host_arr = server.split(",");
			if (host_arr.length > 1) {
				throw new IllegalArgumentException(
						"JedisPool redis.server length > 1");
			}

			String[] arr = host_arr[0].split(":");

			// 根据配置实例化jedis池
			System.out.println("***********init JedisPool***********");
			System.out.println("host->" + arr[0] + ",port->" + arr[1]);
			pool = new JedisPool(config, arr[0], Integer.parseInt(arr[1]));
		} catch (Exception e) {
			throw new IllegalArgumentException("init JedisPool error", e);
		}

	}

	public static JedisPoolManager getMgr() {
		if (manager == null) {
			synchronized (JedisPoolManager.class) {
				if (manager == null) {
					manager = new JedisPoolManager();
				}
			}
		}
		return manager;
	}

	public Jedis getResource() {
		return pool.getResource();
	}

	public void destroy() {
		pool.destroy();
	}

	public void close() {
		pool.close();
	}
}
package com.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;

import com.commnon.JedisPoolManager;

public class RedisLockUtil {

	private final static Logger LOG = LoggerFactory.getLogger(RedisLockUtil.class);
	
	public static boolean acquireLock(String lock, long expired) {
	    // 1. 通过SETNX试图获取一个lock
	    boolean success = false;
	    JedisPoolManager jedisPoolManager = JedisPoolManager.getMgr();
	    Jedis jedis = jedisPoolManager.getResource();
	    
	    long value = System.currentTimeMillis() + expired + 1;  //锁超时时间  
	    
	    LOG.info(value+"");    
	    
	    long acquired = jedis.setnx(lock, String.valueOf(value));
	    //SETNX成功,则成功获取一个锁
	    if (acquired == 1)      
	        success = true;
	    //SETNX失败,说明锁仍然被其他对象保持,检查其是否已经超时
	    else {
	        long oldValue = Long.valueOf(jedis.get(lock));
	        //超时
	        if (oldValue < System.currentTimeMillis()) {
                // 假设多个线程(非单jvm)同时走到这里  
	            String getValue = jedis.getSet(lock, String.valueOf(value));               
	            // 获取锁成功
                // 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)  
                // 假如拿到的oldValue依然是expired的,那么就说明拿到锁了
	            if (Long.valueOf(getValue) == oldValue) 
	                success = true;
	            // 已被其他进程捷足先登了
	            else 
	                success = false;
	        }
	        //未超时,则直接返回失败
	        else             
	            success = false;
	   }        
	   return success;      
	}
	 
	//释放锁
	public static void releaseLock(String lock) {
		JedisPoolManager jedisPoolManager = JedisPoolManager.getMgr();
	    Jedis jedis = jedisPoolManager.getResource();     
	    long current = System.currentTimeMillis();       
	    // 避免删除非自己获取得到的锁
	    if (current < Long.valueOf(jedis.get(lock)))
	        jedis.del(lock);      
	}
}

三、redis.properties

# Redis server ip and port
redis.server=192.168.1.111:6379

# Redis pool
redis.pool.maxTotal=20
redis.pool.maxIdle=10
redis.pool.minIdle=1
redis.pool.maxWaitMillis=60000
redis.pool.testOnBorrow=true
redis.pool.testOnReturn=true

使用redisTemplate

public Long acquireLock(final String lockName,final long expire){ 	
	    return redisTemplate.execute(new RedisCallback<Long>() {     
              public Long doInRedis(RedisConnection connection) {            
 	        byte[] lockBytes = redisTemplate.getStringSerializer().serialize(lockName); 
            	boolean locked = connection.setNX(lockBytes, lockBytes); 
            	connection.expire(lockBytes, expire);
 		   if(locked){ 
		      return 1L;
 		}       
       	            return 0L; 
                }
   		}); 
  	}
//原子操作
public String getAndSet(final String key,final String value){ 
		return  redisTemplate.execute(new RedisCallback<String>() {
 			@Override 
			public String doInRedis(RedisConnection connection) 
					throws DataAccessException { 
				byte[] result = connection.getSet(redisTemplate.getStringSerializer().serialize(key),
 				redisTemplate.getStringSerializer().serialize(value));
 				if(result!=null){ 					
                                    return new String(result); 	
			 } 
				return null; 		
	                } 	
    	    }); 
	}

 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis distributed lock implementation.
 *
 * @author zhengcanrui
 */
public class RedisLock {

    private static Logger logger = LoggerFactory.getLogger(RedisLock.class);

    private RedisTemplate redisTemplate;

    private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;

    /**
     * Lock key path.
     */
    private String lockKey;

    /**
     * 锁超时时间,防止线程在入锁以后,无限的执行等待
     */
    private int expireMsecs = 60 * 1000;

    /**
     * 锁等待时间,防止线程饥饿
     */
    private int timeoutMsecs = 10 * 1000;

    private volatile boolean locked = false;

    /**
     * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
     *
     * @param lockKey lock key (ex. account:1, ...)
     */
    public RedisLock(RedisTemplate redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey + "_lock";
    }

    /**
     * Detailed constructor with default lock expiration of 60000 msecs.
     *
     */
    public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs) {
        this(redisTemplate, lockKey);
        this.timeoutMsecs = timeoutMsecs;
    }

    /**
     * Detailed constructor.
     *
     */
    public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
        this(redisTemplate, lockKey, timeoutMsecs);
        this.expireMsecs = expireMsecs;
    }

    /**
     * @return lock key
     */
    public String getLockKey() {
        return lockKey;
    }

    private String get(final String key) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    byte[] data = connection.get(serializer.serialize(key));
                    connection.close();
                    if (data == null) {
                        return null;
                    }
                    return serializer.deserialize(data);
                }
            });
        } catch (Exception e) {
            logger.error("get redis error, key : {}", key);
        }
        return obj != null ? obj.toString() : null;
    }

    private boolean setNX(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
                    connection.close();
                    return success;
                }
            });
        } catch (Exception e) {
            logger.error("setNX redis error, key : {}", key);
        }
        return obj != null ? (Boolean) obj : false;
    }

    private String getSet(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
                    connection.close();
                    return serializer.deserialize(ret);
                }
            });
        } catch (Exception e) {
            logger.error("setNX redis error, key : {}", key);
        }
        return obj != null ? (String) obj : null;
    }

    /**
     * 获得 lock.
     * 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.
     * reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)
     * 执行过程:
     * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
     * 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
     *
     * @return true if lock is acquired, false acquire timeouted
     * @throws InterruptedException in case of thread interruption
     */
    public synchronized boolean lock() throws InterruptedException {
        int timeout = timeoutMsecs;
        while (timeout >= 0) {
            long expires = System.currentTimeMillis() + expireMsecs + 1;
            String expiresStr = String.valueOf(expires); //锁到期时间
            if (this.setNX(lockKey, expiresStr)) {
                // lock acquired
                locked = true;
                return true;
            }

            String currentValueStr = this.get(lockKey); //redis里的时间
            if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
                //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
                // lock is expired

                String oldValueStr = this.getSet(lockKey, expiresStr);
                //获取上一个锁到期时间,并设置现在的锁到期时间,
                //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
                if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                    //防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受

                    //[分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
                    // lock acquired
                    locked = true;
                    return true;
                }
            }
            timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;

            /*
                延迟100 毫秒,  这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,
                只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.
                使用随机的等待时间可以一定程度上保证公平性
             */
            Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);

        }
        return false;
    }


    /**
     * Acqurired lock release.
     */
    public synchronized void unlock() {
        if (locked) {
            redisTemplate.delete(lockKey);
            locked = false;
        }
    }

}

 

参考

http://www.blogjava.net/hello-yun/archive/2014/01/15/408988.html

http://www.58maisui.com/2016/10/11/708/ 

http://www.cnblogs.com/0201zcr/p/5942748.html

http://www.cnblogs.com/PurpleDream/p/5559352.html

http://blog.csdn.net/ugg/article/details/41894947

 

共有 人打赏支持
粉丝 80
博文 260
码字总数 95733
×
chaun
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: