Java使用Redis实现分布式锁

来源:互联网 发布:淘宝店招怎么加链接 编辑:程序博客网 时间:2024/06/07 05:10

非分布式情况下, 遇到多线程需求,需要保证数据唯一性,一般会用到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 进行超时设置。如果未超时, 获取失败。




@Servicepublic class RedisLockImpl implements IRedisLock {private static Logger logger = Logger.getLogger(RedisLockImpl.class);private static final int timeout = 3600;@Autowiredprivate IRedisService redisService;@Overridepublic 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;}}@Overridepublic 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;}@Overridepublic 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");}




 
原创粉丝点击