一、 背景
实际的项目开发过程中我们常常遇到高并发访问,容易导致数据不同步,例如:库存的增加, 故此我们可以通过Redis提供的特性实现分布式锁,以达到数据的同步性。本文通过Redis的客户端JedisLock来实现。
注:该文是本博主记录学习之用,没有太多详细的讲解,敬请谅解!
二、 Redis实现分布式锁的原理
1、选用Redis实现分布式锁原因:
(1)Redis有很高的性能;
(2)Redis命令对此支持较好,实现起来比较方便
2、使用命令介绍:
(1)SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
(3)delete
delete key:删除key
在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
3、实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
流程图:
三、添加Maven依赖
四、代码示例
package com.vcyber.user.util; import com.github.jedis.lock.JedisLock; import redis.clients.jedis.Jedis; public class JedisLockTest { public static void main(String[] args) throws InterruptedException { for (int i=0;i<10;i++){ int num = i; new Thread(new Runnable() { @Override public void run() { Jedis jedis=new Jedis("127.0.0.1",6379); lock(jedis,"key", num); } }).start(); } } public static void lock(Jedis jedis, String key,int i){ JedisLock jedisLock=new JedisLock(jedis,key,2000,2000); try { //获得锁 jedisLock.acquire(); System.out.println(i+"获得锁"); if(i==2){ //该代码块是为了验证效果 System.out.println(i+"睡眠2秒...."); Thread.sleep(2000); } }catch (Exception e){ e.printStackTrace(); }finally { System.out.println(i+"释放锁"); if(jedisLock!=null){ //释放锁 jedisLock.release(); } if(jedis!=null){ jedis.close(); } } } } 效果图:
对于JedisLock如何基于Redis来实现锁的效果,本文就不一一描述,各位可自行看源码理解!
以下贴上源码以供学习:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.github.jedis.lock;
import redis.clients.jedis.Jedis;
public class JedisLock {
Jedis jedis;
String lockKey;
int expireMsecs;
int timeoutMsecs;
boolean locked;
public JedisLock(Jedis jedis, String lockKey) {
this.expireMsecs = 60000;
this.timeoutMsecs = 10000;
this.locked = false;
this.jedis = jedis;
this.lockKey = lockKey;
}
public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs) {
this(jedis, lockKey);
this.timeoutMsecs = timeoutMsecs;
}
public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs, int expireMsecs) {
this(jedis, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
public JedisLock(String lockKey) {
this((Jedis)null, lockKey);
}
public JedisLock(String lockKey, int timeoutMsecs) {
this((Jedis)null, lockKey, timeoutMsecs);
}
public JedisLock(String lockKey, int timeoutMsecs, int expireMsecs) {
this((Jedis)null, lockKey, timeoutMsecs, expireMsecs);
}
public String getLockKey() {
return this.lockKey;
}
public synchronized boolean acquire() throws InterruptedException {
return this.acquire(this.jedis);
}
/**
* 获得 lock.
* 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.
* reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)
* 执行过程:
* 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
* 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
*
*/
public synchronized boolean acquire(Jedis jedis) throws InterruptedException {
int timeout = this.timeoutMsecs;
while(timeout >= 0) {
long expires = System.currentTimeMillis() + (long)this.expireMsecs + 1L;
String expiresStr = String.valueOf(expires); //锁到期时间
if (jedis.setnx(this.lockKey, expiresStr) == 1L) {
this.locked = true;
return true;
}
//判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
//获取上一个锁到期时间,并设置现在的锁到期时间,
//只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
String currentValueStr = jedis.get(this.lockKey);
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
String oldValueStr = jedis.getSet(this.lockKey, expiresStr);
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
this.locked = true;
return true;
}
}
timeout -= 100;
Thread.sleep(100L);
}
return false;
}
public synchronized void release() {
this.release(this.jedis);
}
public synchronized void release(Jedis jedis) {
if (this.locked) {
jedis.del(new String[]{this.lockKey});
this.locked = false;
}
}
}