redis 分布式锁
package com.mobile.web.portal.util.lock; import redis.clients.jedis.Jedis; public class JedisLock { private int timeoutMsecs; private String lockKey; private static long expireMsecs = 1000 * 60 * 1; // min 锁持有超时 public JedisLock(String lockKey, Integer timeoutMsecs) { this.timeoutMsecs = timeoutMsecs; this.lockKey = lockKey; } public synchronized boolean acquire(Jedis jedis) throws InterruptedException { int timeout = timeoutMsecs; while (timeout >= 0) { long expires = System.currentTimeMillis() + expireMsecs + 1; String expiresStr = String.valueOf(expires); // 锁到期时间 if (jedis.setnx(lockKey, expiresStr) == 1) { return true; } String currentValueStr = jedis.get(lockKey); // redis里的时间 // 表示已经锁失效,要重新设置锁 if (currentValueStr != null && Long.parseLong(currentValueStr) < System .currentTimeMillis()) { // 判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的 // lock is expired String oldValueStr = jedis.getSet(lockKey, expiresStr); // 获取上一个锁到期时间,并设置现在的锁到期时间,; // 只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的 if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // 如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁 return true; } } timeout -= 100; Thread.sleep(100); } return false; } public void unLock(Jedis jedis) { jedis.del(lockKey); } }
package com.mobile.web.portal.util.lock; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisShardInfo; import com.zhicall.care.system.basic.BeanFactory; public class RequestValidate { private RedisTemplate<Object, Object> redisTemplate = (RedisTemplate<Object, Object>) BeanFactory.getInstance().getBean("redisTemplate"); private static final String ERR_MSG = "www!"; private static final int TIMEOUTMSECS = 300; /** * 使用redis 分布式锁对请求验证 * * @return */ public String validate(long a, String b){ String lockKey = a+":"+b+"_lock"; int timeoutMsecs = TIMEOUTMSECS; JedisLock lock = new JedisLock(lockKey, timeoutMsecs); Jedis jedis = getJedis(); try { boolean flag = lock.acquire(jedis); if(!flag){ return ERR_MSG; } } catch (InterruptedException e) { return ERR_MSG; }finally { jedis.disconnect(); } return null; } private Jedis getJedis(){ JedisConnectionFactory factory = (JedisConnectionFactory)redisTemplate.getConnectionFactory(); JedisShardInfo shardInfo = factory.getShardInfo(); Jedis jedis = shardInfo.createResource(); return jedis; } public void unLock(long a, String b){ String lockKey = a+":"+b+"_lock"; int timeoutMsecs = TIMEOUTMSECS; JedisLock lock = new JedisLock(lockKey, timeoutMsecs); Jedis jedis = getJedis(); try { lock.unLock(jedis); }finally { jedis.disconnect(); } } }
-------------------------------------------------------------------------------------------------------------------------
更新
新的redis分布式锁,基于redis2.6版本以上
package com.mobile.web.util.redis; import java.util.Collections; import redis.clients.jedis.Jedis; /** * 新的Redis分布式锁实现 * 基于jedis版本2.8.0以上 * @author lsnBin * @date 2018/01/17 */ public class JedisTool { private static final String LOCK_SUCCESS = "OK"; /** 即当key不存在时,我们进行set操作 */ private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private static final Long RELEASE_SUCCESS = 1L; /** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { //Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行 //首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。 //eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
替换原因
1、释放锁存在:直接使用redis.del()方法删除锁,不限判断拥有者而直接解锁的方式,会导致任何客户端都可以随时解锁,即时锁不是它的。
2、获取锁存在:
2.1:由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步.
2.2:当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。
2.3:锁不具备拥有者标识,即任何客户端都可以解锁。