非分布式情况下, 遇到多线程需求,需要保证数据唯一性,一般会用到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");
}





Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐