[置顶] spring boot项目实战:分布式锁
2017-10-04 19:35
561 查看
在部分情况下,要保证操作在整个集群内是同步的,以操作库存为例,多个减操作需要同步,常见的有两种方式:
1. 采用类CAS的方式,先查询库存,然后使用update xxx set num=num-1 where num=:num;这样可保证库在本次修改之前未被修改;
2. 使用分布式锁,保证同时只有一个地方在修改库存。
这里向大家展示一个基于redis的分布式锁。主要涉及三个类:
1. DistributedLockUtil对外提供获取分布式锁的方法;
2. DistributedLock 分布式锁接口,定义分布式锁支持的方法,主要有acquire和release;
3. JedisLock实现DistributedLock接口,是基于redis的分布锁实现 ;
需要使用StringRedisTemplate,如对spring boot整合redis不熟悉,请参考spring boot项目实战:redis.
《基于Redis的分布式锁到底安全吗(上)?》
《基于Redis的分布式锁到底安全吗(下)?》
需要保证redis节点的高可用,建议使用哨兵机制;
在使用分布式锁之前,考虑是否可以通过乐观锁或无锁解决并发同步问题,毕竟使用锁的代价很是比较高昂的;
本人搭建好的spring boot web后端开发框架已上传至GitHub,欢迎吐槽!
https://github.com/q7322068/rest-base,已用于多个正式项目,当前可能因为版本问题不是很完善,后续持续优化,希望你能有所收获!
1. 采用类CAS的方式,先查询库存,然后使用update xxx set num=num-1 where num=:num;这样可保证库在本次修改之前未被修改;
2. 使用分布式锁,保证同时只有一个地方在修改库存。
这里向大家展示一个基于redis的分布式锁。主要涉及三个类:
1. DistributedLockUtil对外提供获取分布式锁的方法;
2. DistributedLock 分布式锁接口,定义分布式锁支持的方法,主要有acquire和release;
3. JedisLock实现DistributedLock接口,是基于redis的分布锁实现 ;
需要使用StringRedisTemplate,如对spring boot整合redis不熟悉,请参考spring boot项目实战:redis.
DistributedLock接口
public interface DistributedLock { /** * 获取锁 * @author yangwenkui * @time 2016年5月6日 上午11:02:54 * @return * @throws InterruptedException */ public boolean acquire(); /** * 释放锁 * @author yangwenkui * @time 2016年5月6日 上午11:02:59 */ public void release(); }
JedisLock基于redis的分布式锁实现
public class JedisLock implements DistributedLock{ private static Logger logger = LoggerFactory.getLogger(JedisLock.class); private static StringRedisTemplate redisTemplate; /** * 分布式锁的键值 */ String lockKey; //锁的键值 int expireMsecs = 10 * 1000; //锁超时,防止线程在入锁以后,无限的执行等待 int timeoutMsecs = 10 * 1000; //锁等待,防止线程饥饿 boolean locked = false; //是否已经获取锁 /** * 获取指定键值的锁 * @param lockKey 锁的键值 */ public JedisLock(String lockKey) { this.lockKey = lockKey; } /** * 获取指定键值的锁,同时设置获取锁超时时间 * @param lockKey 锁的键值 * @param timeoutMsecs 获取锁超时时间 */ public JedisLock(String lockKey, int timeoutMsecs) { this.lockKey = lockKey; this.timeoutMsecs = timeoutMsecs; } /** * 获取指定键值的锁,同时设置获取锁超时时间和锁过期时间 * @param lockKey 锁的键值 * @param timeoutMsecs 获取锁超时时间 * @param expireMsecs 锁失效时间 */ public JedisLock(String lockKey, int timeoutMsecs, int expireMsecs) { 4000 this.lockKey = lockKey; this.timeoutMsecs = timeoutMsecs; this.expireMsecs = expireMsecs; } public String getLockKey() { return lockKey; } /** * * @return true if lock is acquired, false acquire timeouted * @throws InterruptedException * in case of thread interruption */ public synchronized boolean acquire() { int timeout = timeoutMsecs; if(redisTemplate == null){ redisTemplate = SpringContextUtil.getBean(StringRedisTemplate.class); } try { while (timeout >= 0) { long expires = System.currentTimeMillis() + expireMsecs + 1; String expiresStr = String.valueOf(expires); //锁到期时间 if (redisTemplate.opsForValue().setIfAbsent(lockKey, expiresStr)) { // lock acquired locked = true; return true; } String currentValueStr = redisTemplate.opsForValue().get(lockKey); //redis里的时间 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的 // lock is expired String oldValueStr = redisTemplate.opsForValue().getAndSet(lockKey, expiresStr); //获取上一个锁到期时间,并设置现在的锁到期时间, //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的 if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { //如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁 // lock acquired locked = true; return true; } } timeout -= 100; Thread.sleep(100); } } catch (Exception e) { logger.error("release lock due to error",e); } return false; } /** * 释放锁 */ public synchronized void release() { if(redisTemplate == null){ redisTemplate = SpringContextUtil.getBean(StringRedisTemplate.class); } try { if (locked) { String currentValueStr = redisTemplate.opsForValue().get(lockKey); //redis里的时间 //校验是否超过有效期,如果不在有效期内,那说明当前锁已经失效,不能进行删除锁操作 if (currentValueStr != null && Long.parseLong(currentValueStr) > System.currentTimeMillis()) { redisTemplate.delete(lockKey); locked = false; } } } catch (Exception e) { logger.error("release lock due to error",e); } } }
DistributedLockUtil
public class DistributedLockUtil{ /** * 获取分布式锁 * 默认获取锁10s超时,锁过期时间60s * @author yangwenkui * @time 2016年5月6日 下午1:30:46 * @return */ public static DistributedLock getDistributedLock(String lockKey){ lockKey = assembleKey(lockKey); JedisLock lock = new JedisLock(lockKey); return lock; } /** * 正式环境、测试环境共用一个redis时,避免key相同造成影响 * @author yangwenkui * @param lockKey * @return */ private static String assembleKey(String lockKey) { return String.format("lock_%s",lockKey ); } /** * 获取分布式锁 * 默认获取锁10s超时,锁过期时间60s * @author yangwenkui * @time 2016年5月6日 下午1:38:32 * @param lockKey * @param timeoutMsecs 指定获取锁超时时间 * @return */ public static DistributedLock getDistributedLock(String lockKey,int timeoutMsecs){ lockKey = assembleKey(lockKey); JedisLock lock = new JedisLock(lockKey,timeoutMsecs); return lock; } /** * 获取分布式锁 * 默认获取锁10s超时,锁过期时间60s * @author yangwenkui * @time 2016年5月6日 下午1:40:04 * @param lockKey 锁的key * @param timeoutMsecs 指定获取锁超时时间 * @param expireMsecs 指定锁过期时间 * @return */ public static DistributedLock getDistributedLock(String lockKey,int timeoutMsecs,int expireMsecs){ lockKey = assembleKey(lockKey); JedisLock lock = new JedisLock(lockKey,expireMsecs,timeoutMsecs); return lock; } }
使用示例
DistributedLock lock = DistributedLockUtil.getDistributedLock(key); try { if (lock.acquire()) { //获取锁成功业务代码 } else { // 获取锁失败 //获取锁失败业务代码 } finally { if (lock != null) { lock.release(); } }
实现原理简析
主要是依赖redis的setnx和getset命令对时间进行操作,从而实现锁的功能。以下两个文章对分布式锁进行了极其明细的分析,会让你对分布式锁的认识更加清晰。《基于Redis的分布式锁到底安全吗(上)?》
《基于Redis的分布式锁到底安全吗(下)?》
注意事项
基于redis的分布式锁依赖于系统时钟,需要保证各个竞争者的时钟的一致性,否则会出现一个参与者获得锁,而另一个参与者的时钟判断其已过期,导致分布式锁失效;需要保证redis节点的高可用,建议使用哨兵机制;
在使用分布式锁之前,考虑是否可以通过乐观锁或无锁解决并发同步问题,毕竟使用锁的代价很是比较高昂的;
本人搭建好的spring boot web后端开发框架已上传至GitHub,欢迎吐槽!
https://github.com/q7322068/rest-base,已用于多个正式项目,当前可能因为版本问题不是很完善,后续持续优化,希望你能有所收获!
相关文章推荐
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:redis
- [置顶] spring boot项目实战:redis
- [置顶] spring boot项目实战-集合操作