Java使用Redis实现分布式锁
非分布式情况下, 遇到多线程需求,需要保证数据唯一性,一般会用到JVM的线程安全机制,当然这种情况仅限于在同一个JVM环境下有效,而分布式环境下并无法保证线程安全和数据安全,所以就需要在分布式APP共用的中间件上实现,如redis,zookeeper,都拥有类似JVM线程的安全机制(并非一定是lock,只要是某些功能或者特效拥有原子性且排他性都可以用来实现)。当前分享记录自己编写的基于
非分布式情况下, 遇到多线程需求,需要保证数据唯一性,一般会用到JVM的线程安全机制,当然这种情况仅限于在同一个JVM环境下有效,而分布式环境下并无法保证线程安全和数据安全,所以就需要在分布式APP共用的中间件上实现,如redis,zookeeper,都拥有类似JVM线程的安全机制(并非一定是lock,只要是某些功能或者特效拥有原子性且排他性都可以用来实现)。
当前分享记录自己编写的基于Redis锁,ZK的后续会补上,Redis的锁主要依赖于SETNX命令:
SETNX: 如果键不存在,则设置键来保存字符串值, 返回1 ,当键已保存值时,不执行任何操作,返回0。
1. 使用SETNX命令,对指定KEY 进行加锁
2. 使用DEL 删除命令, 解锁
3. 防止锁意外无法解锁,使用EXPIRE对KEY进行超时设置,实现锁超时
定义锁接口ILock, 定义 锁,和解锁2个接口方法,考虑到锁的种类,存在JVM锁,分布式锁,增加IJvmLock(后续会提供JVM实现)和IDistributionLock接口,继承ILock:
ILock基础接口:
public interface ILock {
/***
* 尝试锁定,锁定成功返回true
* 锁定失败返回false
* 锁定失败表示当前key正在执行中
* @param key 需要锁定的key
* @param exprie 过期时间 秒
* @return
*/
boolean lock(String key, int exprie);
/***
* 释放锁
* @param key
*/
void unlock(String key);
}
IJvmLock,IDistributionLock接口,目前只继承ILock,无其他拓展:
public interface IJvmLock extends ILock {
}
/** * 分佈式鎖 * @author victor * */ public interface IDistributedLock extends ILock{ }
IRedisLock接口, 继承IDistributionLock接口, 增加拓展接口方法,getLockKeyPrev(),用于返回 锁的key存在于redis中的前缀,避免与业务为key冲突重合:public interface IRedisLock extends ILock{ public String getLockKeyPrev(); }
考虑到Redis既然设置了过期时间,和锁的设置时间存储,定义了一个model类如下:
public class LockMeta implements Serializable{ private static final long serialVersionUID = 140085406085415951L; /*** * 锁定时间 */ private Long lockTime; /*** * 过期时间 */ private int expireTime; public Long getLockTime() { return lockTime; } public void setLockTime(Long lockTime) { this.lockTime = lockTime; } public int getExpireTime() { return expireTime; } public void setExpireTime(int expireTime) { this.expireTime = expireTime; } public boolean isLose(){ return this.getExpireTime()*1000 + this.getLockTime() <= System.currentTimeMillis(); } }
接下来就是IRedisLock的实现:
key为前缀 + lockKey,value为LockMeta序列化的JSON,如果SETNX执行失败,此时获取该KEY 中的lockMeta对象,判断是否超时: 如果超时,删除并重新设置,记录锁的时间和超时时间,使用EXPIRE对KEY 进行超时设置。如果未超时, 获取失败。
@Service public class RedisLockImpl implements IRedisLock { private static Logger logger = Logger.getLogger(RedisLockImpl.class); private static final int timeout = 3600; @Autowired private IRedisService redisService; @Override public boolean lock(String key, int exprie) { try { exprie = exprie <= 0 ? timeout : exprie; String value = JSONUtils.toJson(createMeta(exprie)); String lockKey = this.getLockKeyPrev() + key; if (redisService.setnx(lockKey, value) == 1) { logger.info("Get redis lock success, key =" + lockKey); return true; } value = redisService.get(lockKey); if (StringUtils.isNullOrEmpty(value)) { redisService.del(lockKey); logger.info("Redis unlock success ,key = " + lockKey); Thread.sleep(1000); value = JSONUtils.toJson(createMeta(exprie)); if (redisService.setnx(lockKey, value) == 1) { redisService.expire(lockKey, exprie); logger.info("Get redis lock success, key =" + lockKey); return true; } else { logger.warn("Get redis lock fail, key =" + lockKey); return false; } } LockMeta meta = JSONUtils.toBean(value, LockMeta.class); if (meta.isLose()) {// 已经超时 redisService.del(lockKey); value = JSONUtils.toJson(createMeta(exprie)); if (redisService.setnx(lockKey, value) == 1) { redisService.expire(lockKey, exprie); logger.info("Get redis lock success, key =" + lockKey); return true; } else { logger.warn("Get redis lock fail, key =" + lockKey); return false; } } logger.warn("Get redis lock fail, key =" + lockKey); return false; } catch (Exception ex) { ex.printStackTrace(); logger.error(ex.getMessage()); return true; } } @Override public void unlock(String key) { String lockKey = this.getLockKeyPrev() + key; try { redisService.del(lockKey); } catch (Exception ex) { logger.error(ex.getMessage()); } logger.info("Redis unlock success ,key = " + lockKey); } private LockMeta createMeta(int exprie) { LockMeta meta = new LockMeta(); meta.setExpireTime(exprie); meta.setLockTime(System.currentTimeMillis()); return meta; } @Override public String getLockKeyPrev() { return "lock:"; } public IRedisService getRedisService() { return redisService; } public void setRedisService(IRedisService redisService) { this.redisService = redisService; } }
IRedisService为封装的REDIS操作命令,不比纠结,可以将IRedisService对象改为自己项目中的实现,对应修改redis操作方法实现。
使用示例:
if(redisLock.lock(eventId, 300)){//must executed in 300S 成功获取锁 logger.info("Handle event :" + eventId + " end!"); }else{// event was handled or is handling 获取失败,正在被其他线程或者业务使用 logger.debug("Event is handled"); }
更多推荐
所有评论(0)