文档章节

redis 设置分布式锁

chaun
 chaun
发布于 2016/11/22 20:47
字数 1561
阅读 73
收藏 3

 

一、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

 

© 著作权归作者所有

共有 人打赏支持
上一篇: svn中打tags
下一篇: jquery.cookie.js使用
chaun
粉丝 91
博文 269
码字总数 91059
作品 0
深圳
高级程序员
私信 提问
Spring-data-redis + redis 分布式锁(一)

分布式锁的解决方式 基于数据库表做乐观锁,用于分布式锁。(适用于小并发) 使用memcached的add()方法,用于分布式锁。 使用memcached的cas()方法,用于分布式锁。(不常用) 使用redis的setnx...

xiaolyuh
2017/11/15
0
0
如何在springcloud分布式系统中实现分布式锁?

转载请标明出处: http://blog.csdn.net/forezp/article/details/68957681 本文出自方志朋的博客 最近在看分布式锁的资料,看了 Josial L的《Redis in Action》的分布式锁的章节。实现思路是...

forezp
2017/04/03
0
0
Spring-data-redis + redis 分布式锁(二)

分布式锁的解决方式 基于数据库表做乐观锁,用于分布式锁。(适用于小并发) 使用memcached的add()方法,用于分布式锁。 使用memcached的cas()方法,用于分布式锁。(不常用) 使用redis的setnx...

xiaolyuh
2017/11/16
0
0
一文弄懂“分布式锁”,一直以来你的选择依据正确吗?

我们本文主要会关注的问题是“分布式锁”的问题。 多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就需要一个分布式锁服务。 常见的分布式...

向南
12/04
0
0
Redis与Zookeeper实现分布式锁的区别

简介 一般而言,大多数系统实现分布式锁服务都会优先使用Redis;但阅读Zookeeper时可知,Zookeeper的一个很重要应用方向就是分布式锁。那么两者实现分布式锁服务的区别是什么呢。 实现难度 ...

沈渊
2017/10/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Ubuntu16.04下安装docker

[TOC] 本文开发环境为Ubuntu 16.04 LTS 64位系统,通过apt的docker官方源安装最新的Docker CE(Community Edition),即Docker社区版,是开发人员和小型团队的理想选择。 1. 开始安装 1.1 由于...

豫华商
今天
9
0
使用XShell工具密钥认证登录Linux系统

如果你是一名Linux运维,那么Linux服务器的系统安全问题,可能是你要考虑的,而系统登录方式有两种,密码和密钥。哪一种更加安全呢? 无疑是后者! 这里我为大家分享用Xshell利器使用密钥的方...

dragon_tech
今天
7
0
day178-2018-12-15-英语流利阅读-待学习

“真蛛奶茶”了解一下?蜘蛛也会产奶了 Lala 2018-12-15 1.今日导读 “蛋白质含量是牛奶的 4 倍,并有着更低的脂肪和含糖量”,听起来诱人又美味的并不是羊奶或豆奶,而是你可能打死都想不到...

飞鱼说编程
今天
11
0
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents

场景重现 npm install --verbose 安装依赖的时,出现如下警告 强迫症患者表示不能接受 npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules\fsevents):npm WARN......

taadis
今天
2
0
OSChina 周六乱弹 —— 你一口我一口多咬一口是小狗

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @达尔文 :分享Roy Orbison的单曲《She's a Mystery to Me》 《She's a Mystery to Me》- Roy Orbison 手机党少年们想听歌,请使劲儿戳(这里...

小小编辑
今天
452
6

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部