⾃定义注解@RedisLock分布式锁⽤法及原理 前⾔
最近开发公司的项⽬,遇到了分布式的场景,即,同⼀条数据可能被多台服务器或者说多个线程同时修改,此时可能会出现分布式事务的问题,随即封装了redis分布式锁的注解。
场景分析
前提:我的银⾏卡有0元钱,现在有A,B两个⼈,想分别给我转10元钱
分析:
假如A,B通过读数据库,同时发现我的余额是0,这时,
线程A,会给我设置:
余额 = 10 + 0
线程B,会给我设置:
余额 = 10 + 0
最后,我的卡上收到了两个⼈的转账,但是最后⾦额居然只有10元!!这是怎么回事?
其实原因就在于多个线程,对⼀条数据同时进⾏了操作。如果我们可以设置⼀下,在修改的⽅法上⾯加⼀个锁,每次修改之前,(A)先拿到这个锁,再去做修改⽅法,此时,其他(B)线程想要修改的时候,看到锁已经不再,需要等待锁释放,然后再去执⾏,就保证了A,B先后依此执⾏,数据依此累加就没问题了。
解决办法
基于代码的可移植性,我将分布式锁做成了注解,⼤家如果有需要,可以直接将jar包拿过去做相应的修改即可,jar包下载地址(链接:
提取码:1msl):
注解使⽤说明:
1.在需要添加分布式锁的⽅法上⾯加上@RedisLock
如果key不添加,则默认锁⽅法第⼀个参数param的id字段,如果需要指定锁某个字段,则@RedisLock(key = "code")
2.如果⽅法没有参数,则不可使⽤RedisLock锁
@RedisLock
public void updateData( Data param){
}
下⾯详细分析⼀下封装的源码:
先看⼀下项⽬结构(总共就4个类):
//RedisLock注解类:没什么好解释的
/**
* Created by liuliang on 2018/10/15.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
//被锁的数据的id
String key() default "";
//唤醒时间
long acquireTimeout() default 6000L;
//超时时间
long timeout() default 6000L;
}
/
/----------------------类分割线---------------------
//RedisService ⼀个简单的操作redis的类,封装了加锁和释放锁的⽅法
/**
* Created by liuliang on 2018/10/15.
*/
@Service
public class RedisService {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name = "stringRedisTemplate")
@Autowired
ValueOperations valOpsStr;
@Autowired
RedisTemplate redisTemplate;
@Resource(name = "redisTemplate")
ValueOperations valOpsObj;
public String getStr(String key) {
return stringRedisTemplate.opsForValue().get(key);//获取对应key的value // (key);
}
public void setStr(String key, String val) {
stringRedisTemplate.opsForValue().set(key,val,1800, TimeUnit.SECONDS); // valOpsStr.set(key, val);
}
public void del(String key) {
stringRedisTemplate.delete(key);
}
/**
* 根据指定o获取Object
*
* @param o
* @return
*/
public Object getObj(Object o) {
(o);
(o);
}
/**
* * 设置obj缓存
* * @param o1
* * @param o2
*
*/
public void setObj(Object o1, Object o2) {
valOpsObj.set(o1, o2);
}
/**
* 删除Obj缓存
*
* @param o
*/
public void delObj(Object o) {
redisTemplate.delete(o);
}
private static JedisPool pool = null;
static {
JedisPoolConfig config = new JedisPoolConfig();
// 设置最⼤连接数
config.setMaxTotal(200);
// 设置最⼤空闲数
config.setMaxIdle(8);
// 设置最⼤等待时间
config.setMaxWaitMillis(1000 * 100);
// 在borrow⼀个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可⽤的 config.setTestOnBorrow(true);
pool = new JedisPool(config, "127.0.0.1", 6379, 3000);
}
DistributedLock lock = new DistributedLock(pool);
/**
* redis分布式加锁
* @param objectId
* @param acquireTimeout
* @param timeout
*/
public String redisLock(String objectId,Long acquireTimeout, Long timeout) {
// 对key为id加锁, 返回锁的value值,供释放锁时候进⾏判断
String lockValue = lock.lockWithTimeout(objectId, acquireTimeout, timeout);
System.out.println(Thread.currentThread().getName() + "获得了锁");
return lockValue;
}
/**
* 释放redis分布式锁
* @param objectId
* @param lockValue
*/
public Boolean releaseLock(String objectId,String lockValue){
boolean b = leaseLock(objectId, lockValue);
System.out.println(Thread.currentThread().getName() + "释放了锁");
return b;
}
/
/----------------------类分割线---------------------
group by的用法及原理详解/**
* Created by liuliang on 2018/10/15.
*
* 分布式锁的主要类,主要⽅法就是加锁和释放锁
*具体的逻辑在代码注释⾥⾯写的很清楚了
*/
@Slf4j
public class DistributedLock {
private final JedisPool jedisPool;
public DistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 加锁
* @param locaName 锁的key
* @param acquireTimeout 获取超时时间
* @param timeout 锁的超时时间
* @return 锁标识
*/
public String lockWithTimeout(String locaName,
long acquireTimeout, long timeout) {
Jedis conn = null;
String retIdentifier = null;
try {
// 获取连接
conn = Resource();
// 随机⽣成⼀个value
String identifier = UUID.randomUUID().toString();
// 锁名,即key值
String lockKey = "lock:" + locaName;
// 超时时间,上锁后超过此时间则⾃动释放锁
int lockExpire = (int)(timeout / 1000);
/
/ 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
log.info("");
if (conn.setnx(lockKey, identifier) == 1) {
log.info("==============lock success!=============");
// 返回value值,⽤于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
/
/ 返回-1代表key没有设置超时时间,为key设置⼀个超时时间
if (l(lockKey) == -1) {
}
try {
//这⾥sleep 10ms是为了防⽌线程饥饿,各位可以思考⼀下为什么 Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retIdentifier;
}
/**
* 释放锁
* @param lockName 锁的key
* @param identifier 释放锁的标识
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
Jedis conn = null;
String lockKey = "lock:" + lockName;
boolean retFlag = false;
try {
conn = Resource();
while (true) {
/
/ 监视lock,准备开始事务
conn.watch(lockKey);
//避免空指针
String lockKeyValue = (lockKey)==null?"":(lockKey); // 通过前⾯返回的value值判断是不是该锁,若是该锁,则删除,释放锁 if (lockKeyValue.equals(identifier)) {
Transaction transaction = conn.multi();
transaction.del(lockKey);
List results = ();
if (results == null) {
continue;
}
log.info("==============unlock success!=============");
retFlag = true;
}
conn.unwatch();
break;
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retFlag;
}
//----------------------类分割线---------------------
/**
* Created by liuliang on 2018/10/16.
这是⼀个,我们指定拦截RedisLock注解
*/
@Aspect
@Component
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论