Redis实现分布式锁
2017-10-16 本文已影响69人
刺風
一、分布式锁是啥?
- 单机锁的概念:我们正常跑的单机项目(也就是在tomcat下跑一个项目不配置集群)想要在高并发的时候加锁很容易就可以搞定,java提供了很多的机制例如:synchronized、volatile、ReentrantLock等锁的机制。
- 为啥需要分布式锁:当我们的项目比较庞大的时候,单机版的项目已经不能满足吞吐量的需求了,需要对项目做负载均衡,有可能还需要对项目进行解耦拆分成不同的服务,那么肯定是做成分布式的项目,分布式的项目因为是不同的程序控制,所以使用java提供的锁并不能完全保证并发需求,需要借助第三方的框架来实现对并发的阻塞控制,来满足实际业务的需要。
二、话不多说,直接上代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @ClassName RedisLock
* @Description
* @author 刺风
* @date 2017-09-30 18:07
*/
public class RedisLock {
private static Logger log = LoggerFactory.getLogger(RedisLock.class);
private String lockKey;
private int expireMsecs= 1000;
private volatile boolean locked = false;
private RedisManager redisManager;
public RedisLock( RedisManager redisManager,String lockKey) {
this.lockKey = lockKey;
this.redisManager = redisManager;
}
public RedisLock(RedisManager redisManager, String lockKey, int expireMsecs) {
this.expireMsecs=expireMsecs;
this.redisManager=redisManager;
this.lockKey = lockKey;
}
/**
* @Title lock
* @Description 获取锁
* 实现思路:在指定的重试次数中,客户端会去重试获取锁
* 如果当前没有客户端使用锁,则新创建一把锁;如果已经产生锁了,并且不在当前客户端手中
* 则去验证上把锁的到期时间是否过期,如果已过期则通过getset方式重新获取锁,
* 并设定新锁的到期时间;没有获取锁的线程则在指定的重试次数里去尝试获取锁,
* 直至上把锁的到期时间已过期(肯定会过期,因为设置的过期时间远小于等待重试的随机时间),
* 会再次选一个线程来去获取锁,再有其他客户端连进来还是这个规律。
* @author 刺风
* @return boolean
*/
public synchronized boolean lock() throws InterruptedException {
//如果在10次重试也没获取到锁,那么只能表示网络繁忙,请稍后再试了,不过这是不可能发生的事情,
// 循环会随机睡眠100~1000毫秒,必然会重新获取锁的
int cycle = 1;//循环次数
while (cycle <= 10) {
log.info("当前客户端尝试获取分布式锁,第"+cycle+"次");
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); //锁到期时间
if (redisManager.setnx(lockKey, expiresStr)) {
log.info("客户端获取分布式锁成功");
locked = true;
return true;
}
//获取上次上锁的时间
String currentValueStr = redisManager.get(lockKey);
//判断是否为空,不为空的情况下,当前时间大于上次的锁到期时间 表示已经过期 则需要重新获取锁,并设置新的锁到期时间
log.info("获取上把锁的过期时间:"+currentValueStr+",和当前时间进行比较,判断是否过期");
if (currentValueStr != null && System.currentTimeMillis()>Long.parseLong(currentValueStr) ) {
String oldValueStr = redisManager.getSet(lockKey, expiresStr);//获取上一个锁到期时间,并设置现在的锁到期时间
// 如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
log.info("上把锁的已过期,当前客户端已重新获取锁,并设置了到期时间");
locked = true;
return true;
}
}
//循环里面随机数是100~1000毫秒 循环10次等待时间就是10*(100~100)毫秒,所以肯定会获取到锁的,因为超时时间在外面就设置了1000毫秒
int num=(int)(100+Math.random()*(1000-1+1));
log.info("给当前线程随机生成睡眠时间:"+num+"毫秒");
Thread.sleep(num);
cycle++;
}
return false;
}
/**
* @Title unlock
* @Description 释放锁
* @author 刺风
*/
public synchronized void unlock() {
if (locked) {
log.info("当前客户端释放分布式锁");
redisManager.del(lockKey);
locked = false;
}
}
}
RedisManager:
package com.wtoip.platform.porsche.web.common;
import com.alibaba.fastjson.parser.ParserConfig;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;
import java.util.*;
@Repository
public class RedisManager {
@Autowired
JedisPool jedisPool;
public Long del(String key) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.del(key);
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
/**
* 判断key是否存在
* @param key
* @return
*/
public boolean containsKey(final String key) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.exists(key);
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 获取Map值
* @param key
* @param filed
* @return
*/
public String hget(String key,String filed) {
Jedis jedis = jedisPool.getResource();
String str = "";
try {
str = jedis.hget(key,filed);
return str;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return str;
}
public boolean hdel(String key,String filed) {
Jedis jedis = jedisPool.getResource();
try {
jedis.hdel(key,filed);
return true;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 获取Map值
* @param key
* @return
*/
public Map<String, String> hGetAll(String key) {
Jedis jedis = jedisPool.getResource();
Map<String, String> map = new HashMap<String,String>();
try {
map = jedis.hgetAll(key);
return map;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return map;
}
/**
* 获取Map值
* @param key
* @return
*/
public Map<String, Object> getHash(String key) {
Jedis jedis = jedisPool.getResource();
final byte[] rawKey = SerializerUtil.rawKey(key);
Map<String, Object> res = new HashMap<String, Object>();
try {
Map<byte[], byte[]> map = jedis.hgetAll(rawKey);
return SerializerUtil.deserializeHashMap(map);
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return res;
}
public boolean hexists(String key, String value){
Jedis jedis = jedisPool.getResource();
try {
return jedis.hexists(key,value);
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 设置Map值
* @param key
* @param value
* @return
*/
public boolean hset(String key,String filed, String value) {
Jedis jedis = jedisPool.getResource();
try {
jedis.hset(key,filed,value);
return true;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
public boolean hincrBy(String key,String filed, Integer value) {
Jedis jedis = jedisPool.getResource();
try {
jedis.hincrBy(key,filed,value);
return true;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 设置Map值
* @param key
* @param value
* @return
*/
public boolean hsetObject(String key,String filed, Object value) {
Jedis jedis = jedisPool.getResource();
final byte[] rawKey = SerializerUtil.rawKey(key);
final byte[] rawFiled = SerializerUtil.rawKey(filed);
final byte[] rawValue = SerializerUtil.rawValue(value);
try {
jedis.hset(rawKey,rawFiled,rawValue);
return true;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
public Object hgetObject(String key,String filed) {
Object obj=null;
Jedis jedis = jedisPool.getResource();
final byte[] rawKey = SerializerUtil.rawKey(key);
final byte[] rawFiled = SerializerUtil.rawKey(filed);
try {
obj=SerializerUtil.deserializeValue(jedis.hget(rawKey,rawFiled));
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return obj;
}
/**
* 删除map指定值
* @param key
* @param value
* @return
*/
public boolean hdelObject(String key,String filed) {
Jedis jedis = jedisPool.getResource();
final byte[] rawKey = SerializerUtil.rawKey(key);
final byte[] rawFiled = SerializerUtil.rawKey(filed);
try {
jedis.hdel(rawKey,rawFiled);
return true;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 设置HASH值(同时设置过期时间)
* @param key
* @param value
* @return
*/
public long hset(String key,String filed, String value,long unixTime) {
if (StringUtils.isBlank(key)) {
return 0;
}
Jedis jedis = jedisPool.getResource();
try {
jedis.hset(key,filed,value);
return jedis.expireAt(key, unixTime);
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return 0;
}
/**
* 获取值
* @param key
* @return
*/
public String get(String key) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.get(key);
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return "";
}
/**
* 判断是否存在该key
* @param key
* @return true|false
*/
public boolean exists(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.exists(key);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 设置值
* @param key
* @param value
* @return
*/
public boolean set(String key, String value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set(key, value);
return true;
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 设置值
* @param key
* @param value
* @return
*/
public boolean setObject(String key, Object value) {
final byte[] rawKey = SerializerUtil.rawKey(key);
final byte[] rawValue = SerializerUtil.rawValue(value);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set(rawKey, rawValue);
return true;
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
/**
* 获取值
* @param key
* @return
*/
public Object getObject(String key) {
final byte[] rawKey = SerializerUtil.rawKey(key);
Jedis jedis = jedisPool.getResource();
try {
return SerializerUtil.deserializeValue(jedis.get(rawKey));
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return "";
}
/**
* 移除有序集合中给定的排名区间的所有成员
* @param key
* @param start
* @param end
* @return
*/
public Long zremrangeByRank(String key, long start, long end) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.zremrangeByRank(key, start, end);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
/**
* 向有序集合添加一个成员
* @param key
* @param score
* @param member
* @return
*/
public Long zadd(String key, double score, String member) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.zadd(key, score, member);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
/**
* 向有序集合添加一个或多个成员,或者更新已存在成员的分数
* @param key
* @param scoreMembers
* @return
*/
public Long zadd(String key, Map<String, Double> scoreMembers) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.zadd(key, scoreMembers);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
/**
* 通过索引区间返回有序集合成指定区间内的成员
* @param key
* @param start
* @param end
* @return
*/
public Set<String> zrange(String key, long start, long end) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.zrange(key, start, end);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return null;
}
public Set<String> zrevrange(String key, long start, long end) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.zrevrange(key, start, end);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return null;
}
public Long lpush(String key, String string) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.lpush(key, string);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
public Long lPushObject(String key, Object value) {
final byte[] rawKey = SerializerUtil.rawKey(key);
final byte[] rawValue = SerializerUtil.rawValue(value);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.lpush(rawKey, rawValue);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
public Long rpush(String key, String string) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.rpush(key, string);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
public Long rPushObject(String key, Object value) {
final byte[] rawKey = SerializerUtil.rawKey(key);
final byte[] rawValue = SerializerUtil.rawValue(value);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.rpush(rawKey, rawValue);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
public List<String> lrange(String key, long start, long end) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.lrange(key, start, end);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return new ArrayList<>();
}
public List<Object> lRangeObject(String key, long start, long end) {
Jedis jedis = null;
try {
final byte[] rawKey = SerializerUtil.rawKey(key);
jedis = jedisPool.getResource();
List<byte[]> list= jedis.lrange(rawKey, start, end);
return deserializeValues(list, List.class);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return new ArrayList<>();
}
public boolean lset(String key, long index, Object value) {
Jedis jedis = null;
try {
final byte[] rawKey = SerializerUtil.rawKey(key);
final byte[] rawValue = SerializerUtil.rawValue(value);
jedis = jedisPool.getResource();
String res = jedis.lset(rawKey,index,rawValue);
if(StringUtils.isNotBlank(res)&&"OK".equals(res)){
return true;
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
public Long llen(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.llen(key);
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.returnResource(jedis);
}
return 0L;
}
/**
* 设置失效时间
* @param key
* @param second 失效时间 单位秒
* @return
*/
public Long expire(String key, Integer second) {
Jedis jedis = jedisPool.getResource();
Long status =0L;
try {
status = jedis.expire(key,second);
return status;
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return status;
}
private <T extends Collection<?>> T deserializeValues(Collection<byte[]> rawValues, Class<T> type) {
Collection<Object> values = List.class.isAssignableFrom(type) ? new ArrayList<Object>(rawValues.size()) : new LinkedHashSet<Object>(rawValues.size());
for (byte[] bs : rawValues) {
values.add(SerializerUtil.deserializeValue(bs));
}
return (T) values;
}
public boolean setnx(String key, String value) {
Jedis jedis = jedisPool.getResource();
try {
if (jedis.setnx(key, value) == 1) {
return true;
}
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return false;
}
public String getSet(String key, String value) {
String oldvalue="";
Jedis jedis = jedisPool.getResource();
try {
oldvalue=jedis.getSet(key,value);
} catch (JedisException e) {
} finally {
jedisPool.returnResource(jedis);
}
return oldvalue;
}
}
三、注意事项
在上面我贴出来RedisManager主要用了setnx和getSet方法,只要拿到了Jedis对象就可以调用了,具体根据自己的项目中Reids实现来去做就可以,不用拘泥于上述的RedisManager文件。