基于Redis的分布式锁
使用常用命令
SETNX
SETNX key val当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
Expire
expire key timeout
为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
Delete
delete key
删除key
在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
实现原理
使用的是jedis来连接Redis。
实现思路
1.获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
2.获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
3.释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
public class RedisClientPool {
private static JedisPool jedisPool;
static{
JedisPoolConfig config = new JedisPoolConfig();
//允许最大空闲数
config.setMaxIdle(10);
//允许最大连接数
config.setMaxTotal(5000);
//设置最大等待时间
// config.setMaxWaitMillis(1000*100);
//在borrow一个Jedis实例时,是否需要验证,若为true,则素有jedis示例均是可以用的
config.setTestOnBorrow(true);
//服务连接参数
jedisPool = new JedisPool(config, "localhost",6379,30000);
}
//对外提供管道服务
public static Jedis getRedisClient(){
if(jedisPool!=null){
return jedisPool.getResource();
}
return null;
}
//释放到jedisPool
public static void releaseResource(JedisPool jedisPool,Jedis jedis){
if(jedisPool!=null && jedis!=null){
jedisPool.returnResource(jedis);
}
}
public static void main(String[] args) {
try {
Jedis jedis = RedisClientPool.getRedisClient();
jedis.set("abc", "abc");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public interface RedisLock {
//setNx(key) timeout 事务影响 锁
public String getLock(String lockName,int timeout,int expire);
//true false
public boolean release(String lockName,String value);
}
/**
* Redis 分布式悲观锁实现
* @author jetsen
*
*/
public class RedisDistributedLock implements RedisLock {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_EXIST = "NX";
private static final String SET_WITH_expire_TIME = "PX";
/**
* 同时多个客户端线程去setNx去创建一个数据项
*
* 成功1: watch key discard
* MULTI 命令 :客户端从非事务状态切换到事务状态 SET命令全部放进一个事务队列里, 然后返回 QUEUED
* 失败0:等待锁 唤醒 抢占
* 在没有上锁之前,获取锁的超时时间:timeout
*/
@Override
public String getLock(String lockName, int timeout, int expire) {
//获取Redis客户端 操作IO管道
Jedis client = null;
try {
client = RedisClientPool.getRedisClient();
//lockName=====> value描述锁状态唯一性
// uuid 128位字节
String value = UUID.randomUUID().toString();
//会话超时时间
long endtime = System.currentTimeMillis()+timeout;
while(System.currentTimeMillis() < endtime){
String result = client.set(lockName, value,SET_IF_EXIST,SET_WITH_expire_TIME,expire);
if(result!=null && result.equals(LOCK_SUCCESS)){
return value;
}
//尝试获取锁
if(client.setnx(lockName, value)==1){
//创建锁成功
//处理如果网络中断死锁问题 lua脚本--scrict->执行lUA脚本 原子性操作
if(client.ttl(lockName)==-1){
client.expire(lockName, expire);
}
return value;
}
//减少高竞争的状态
TimeUnit.MICROSECONDS.sleep(500);
}
} catch (Exception e) {
try {
throw e;
} catch (Exception e1) {
e1.printStackTrace();
}
} finally{
if(client !=null){
client.close();
}
}
//在规定的时间内没有获取锁
return null;
}
/**
* Redis 事务
*/
@Override
public boolean release(String lockName, String value) {
Jedis client = RedisClientPool.getRedisClient();
try {
while(client.exists(lockName)){
client.watch(lockName);
if(value.equals(client.get(lockName))){
//进入锁进行事务操作multi exec discard
//Queue
Transaction transaction = client.multi();
//ddl
transaction.del(lockName);
//多个命令会被入队到事务队列中,然后按先进先出(FIFO)的顺序执行
List<Object> execQueue = transaction.exec();
if(execQueue == null){
continue;
}
return true;
}
client.unwatch();
break;
}
} catch (Exception e) {
try {
throw e;
} catch (Exception e1) {
e1.printStackTrace();
}
} finally{
if(client !=null){
client.close();
}
}
return false;
}
}
public class RedisLockUtil {
public static final String LOCK_NAME="JET_LOCK_0309";
//在获取锁之前的超时时间---在尝试获取锁的时候,如果在规定时间内没有获取锁,直接放弃
public static final int TIMEOUT=1000;
//在获取锁之后的超时时间---当前获取锁成功后,对应key 有对应的有效期
public static final int EXPIRE=1000;
public RedisLockUtil() {
}
public static String idGenerator(IGenerateGlobalId iGenerateGlobalId,RedisLock lock) {
String idGenerator = null;
String value = lock.getLock(LOCK_NAME, TIMEOUT, EXPIRE);
try {
if(value!=null){
idGenerator = iGenerateGlobalId.idGenerator();
System.out.printf("线程名称:%s 订单号:%s \r\n", Thread.currentThread().getName(),idGenerator);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.release(LOCK_NAME, value);
}
return idGenerator;
}
public static void main(String[] args) {
RedisLock lock = new RedisDistributedLock();
idGenerator(new SequenceUnLockGenerateGlobalIdImpl(),lock);
// for (int i = 0; i < 10; i++) {
// idGenerator(new SequenceUnLockGenerateGlobalIdImpl(),lock);
// }
}
}
public class RedisTestTask implements Runnable{
private IGenerateGlobalId iGenerateGlobalId;
private RedisLock lock;
private CountDownLatch latch;
public RedisTestTask(CountDownLatch latch,RedisLock lock,IGenerateGlobalId iGenerateGlobalId) {
this.lock = lock;
this.latch = latch;
this.iGenerateGlobalId = iGenerateGlobalId;
}
@Override
public void run() {
try {
latch.await();
RedisLockUtil.idGenerator(iGenerateGlobalId, lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
IGenerateGlobalId entity = new SequenceUnLockGenerateGlobalIdImpl();
ExecutorService pool = Executors.newCachedThreadPool();
RedisLock lock = new RedisDistributedLock();
final CountDownLatch latch = new CountDownLatch(1);
for(int i=0;i<10;i++) {
pool.execute(new RedisTestTask(latch,lock,entity));
}
latch.countDown();
pool.shutdown();
}
}
三种分布式对比
上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。
从理解的难易程度角度(从低到高)
数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)
Zookeeper >= 缓存 > 数据库
从性能角度(从高到低)
缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低)
Zookeeper > 缓存 > 数据库
Redis实现分布式锁与Zookeeper实现分布式锁区别
使用redis实现分布式锁
redis中的set nx 命令,当key不存在时,才能在redis中将key添加成功,利用该属性可以实现分布式锁,并且redis对于key有失效时间,可以控制当某个客户端加锁成功之后挂掉,导致阻塞的问题。
使用Zookeeper实现分布式锁
多个客户端在Zookeeper上创建一个相同的临时节点,因为临时节点只能允许一个客户端创建成功,那么只要任意一个客户端创建节点成功,谁就成功的获取到锁,当释放锁后,其他客户端同样道理在Zookeeper节点。