Redis 分布式锁

分布式锁,多JVM,多线程,共享资源互斥访问

用codis,不能使用事务

极端场景:乱序,程序崩溃,网络延时等,key没有释放

 

 

http://www.redis.cn/commands/set.html

 

==================

package com.enterprise.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

/**
 * 基于REDIS的分布式锁
 * 
 * @author chen.kuan 2016年4月29日
 *
 */
public class RedisLock {
	public static final int DEFAULT_AVAILABLE_TIME = 600; // 默认有效期  单位S

	private static final String LOCK_SUCCESS_CODE = "OK";

	private JedisPool pool;

	private final int availableTime;

	private final String redisKey;

	public RedisLock(JedisPool pool, String redisKey) {
		this(pool, DEFAULT_AVAILABLE_TIME, redisKey);
	}

	/**
	 * @param pool
	 * @param availableTime
	 * @param redisKey
	 */
	public RedisLock(JedisPool pool, int availableTime, String redisKey) {
		this.pool = pool;
		this.availableTime = availableTime;
		this.redisKey = redisKey;
	}

	/**
	 * @return  true 锁定成功  false 锁定失败
	 */
	public boolean tryLock() {
		String code = lock();
		if (LOCK_SUCCESS_CODE.equals(code)) {
			return true;
		}
		return false;
	}

	/**
	 * 基于 set(final String key, final String value, final String nxxx, final String expx, final int time)上锁. </br>
	 * 上锁必须设置超时时间,避免因为JVM宕机,IO等原因造成的unlock失败出现死锁问题. </br>
	 * 如果业务执行时间过长,当指定的availableTime耗尽后,锁会被自动释放。 </br>
	 * 
	 * 
	 * @return  状态码
	 */
	private String lock() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			String code = jedis.set(redisKey, "1", "NX", "EX", availxableTime);
			return code;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			pool.returnResource(jedis);
		}
	}

	public void unlock() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			jedis.del(redisKey);
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

}

 

 

package com.enterprise.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

/**
 * 基于REDIS的分布式锁<br>
 * 
 * @author chen.kuan 2016年4月29日
 *
 */
public class RedisLock {
	public static final int DEFAULT_AVAILABLE_TIME = 600; // 默认有效期  单位S

	private JedisPool pool;

	private final int availableTime;

	private final String redisKey;

	public RedisLock(JedisPool pool, String redisKey) {
		this(pool, DEFAULT_AVAILABLE_TIME, redisKey);
	}

	public RedisLock(JedisPool pool, int availableTime, String redisKey) {
		this.pool = pool;
		this.availableTime = availableTime;
		this.redisKey = redisKey;
	}

	/**
	 * @return  true 锁定成功  false 锁定失败 
	 */
	public boolean tryLock() {
		Long statusCode = lockAndSetExpiredTime();
		if (statusCode == 1) {
			return true;
		}
		// 锁定失败 
		// 需要检查是是否发生了锁超时,锁超时是指锁没有被显示的释放,也就是REDIS中的键值对没有被删掉
		String expectedExpiredTime = get();
		if (expectedExpiredTime != null && System.currentTimeMillis() > Long.valueOf(expectedExpiredTime)) {
			// 锁超时: 多JVM,多线程,时间一致

			// 超时可能是下面的原因导致:
			// 1 代码设计疏忽,获得了lock后,在某些场景下不会执行unlock
			// 2 持有lock的线程,业务执行时间过长   --> 这是最糟糕的情况,因为它的unlock可能再未来的某一个时刻突然执行了
			// 3 持有lock的线程,执行unlock失败,REDIS connection fail可能会导致这种情况,分布式环境下必须考虑某一个REDIS客户端访问REDIS失败的问题
			// 4 进程还没有来得及执行unlock就直接被kill -9杀死了

			// 检测到了锁超时,需要释放锁,保证后续的业务能够锁定成功
			// 这个地方只能通过设置超时的方式保证锁被删除,而且超时时间必须为availableTime
			// 原因如下:
			// 1 在本线程尝试删除锁时,REDIS锁可能已经被删除了(unlock得到了执行,或者其它线程设置的setExired发挥作用了),此时其它线程可能已经获取了锁
			// 2 如果其它线程获取了锁,则我们必须保证竞争机制能够延续,所以要设置有效期和availableTime一致
			if (!expired()) {
				setExpired();
			}
		}
		return false;
	}

	private Long lockAndSetExpiredTime() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			Long reV = jedis.setnx(redisKey, (System.currentTimeMillis() + availableTime * 1000) + "");
			return reV;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			pool.returnResource(jedis);
		}
	}

	private String get() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			String value = jedis.get(redisKey);
			return value;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

	private boolean expired() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			Long ttl = jedis.pttl(redisKey); // 当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以毫秒为单位,返回 key 的剩余生存时间
			if (ttl != null && ttl >= 0) {
				return true;
			}
			return false;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

	private void setExpired() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			jedis.expire(redisKey, availableTime);
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

	/**
	 * 释放锁,持有锁的线程在业务操作完毕后,必须调用此方法释放持有的锁
	 */
	public void unlock() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			jedis.del(redisKey);
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

}

 

 

package com.enterprise.util;

/**
 * 基于ReidsLock,实现对业务实体的并发互斥访问
 * 
 * @author chen.kuan 2015年11月13日
 *
 */
public abstract class ConcurrentEntityVisitor {
	private RedisLock lock;

	public ConcurrentEntityVisitor() {
	}

	public ConcurrentEntityVisitor(RedisLock lock) {
		this.lock = lock;
	}

	public void setLock(RedisLock lock) {
		this.lock = lock;
	}

	public Result visit() {
		boolean locked = lock.tryLock();
		if (!locked) {
			return handleConflict();
		} else {
			try {
				return doVisit();
			} finally {
				lock.unlock();
			}
		}
	}

	/**
	 * 并发场景下,在检测到访问冲突时处理冲突
	 * 
	 * @return
	 */
	public abstract Result handleConflict();

	/**
	 * 访问实体,做业务
	 * 
	 * @return
	 */
	public abstract Result doVisit();

}

 

 

 

	private RedisLock createRedisLock(String merchantUsername) {
		String key = String.format(O2O_MERCHANT_CONCURRENT_LOCK_KEY, merchantUsername);
		RedisLock lock = new RedisLock(redisUtil.getPool(), key);
		return lock;
	}

	@Override
	@Transactional(value = "transactionManager_busi", rollbackFor = Exception.class)
	public Result addScanCodePayMerchant(final String username, final String auditUserName) {
		ConcurrentEntityVisitor visit = new ConcurrentEntityVisitor(createRedisLock(username)) {
			@Override
			public Result handleConflict() {
				return Result.failed("当前商家" + username + "正在被操作");
			}

			@Override
			public Result doVisit() {
				MerchantO2OConfig config = repository.getMerchantO2OConfig(username, null);
				if (config != null) {
					return Result.failed("当前商家" + username + "已经存在于扫码支付列表中");
				}
				MerchantEntity merchantEntity = merchantBasicService.getMerchantByUserName(username);
				if (merchantEntity == null) {
					return Result.failed("当前商家" + username + "不存在");
				}
				String shopName = merchantEntity.getShopName();
				config = new MerchantO2OConfig();
				config.setAuditUserName(auditUserName);
				config.setCreateTime(new Date());
				config.setMerchantType(merchantEntity.getMerchantType());
				config.setShopName(shopName);
				config.setState(MerchantO2OConstants.SCAN_CODE_PAY_GRANTED); // 状态设置为已赋权
				config.setUpdateTime(new Date());
				config.setUsername(username);
				config.setUserId(merchantEntity.getUserId());
				repository.addScanCodePayMerchant(config);

				supportUsingRedis(username);
				return Result.success();
			}
		};
		return visit.visit();
	}

 

 

 

 

猜你喜欢

转载自curious.iteye.com/blog/2294089