Redis 锁设计

最近同事做一个微信支付的功能,其中微信回调,用到了MQ 防止重复消费的问题,我们做了多重判断,第一重使用redis 锁防止数据被微信的回调多次修改数据库,保证一条数据只修改一次,并配合支付状态来保证消息不会被重复消费,我们的支付状态存在以下几个状态:

  1. 未支付
  2. 已支付
  3. 未消费
  4. 已入列

当微信进行回调的时候,我们先通过redis 进行锁住,来保证数据在同一时间只有一个请求在修改,如果当前支付状态为未消费(默认为未消费),修改为已入列并放入消息队列,消费成功后状态改为已支付,来保证MQ 不会被重复消费,其中在Redis 锁的部分我们遇到了一些问题:

  • 保证锁的有效性,一旦当前的锁被持有,当其他锁再次获取时不能被获取
  • 锁超时问题:假如锁超时了,程序还没有处理完成,如何保证程序的安全性,我们这里并没有直接做处理,通过超时机制释放锁,而是只打印了一行日志,并且返回,等待任务执行完成,后,进行锁的删除,之后通过日志来排查导致超时的原因,因为数据超时是存在一定的原因的。
  • 锁获取问题:这里我们使用自旋的方式来获取锁
  • 锁的并发性:为了保证锁的并发性这里锁定的是当前支付订单的订单号,每个订单号可以获取不同的锁

 Redis 锁的设计代码如下:

@Component
@Slf4j
public class RedisLockUtil {

	@Autowired
	private StringRedisTemplate redisTemplate;


	/**
	 * 等待redis锁
	 *
	 * @param redisKey    锁
	 * @param maxWaitTime 时间 毫秒
	 * @return true or false
	 */
	public boolean waitRedisLock(String redisKey, Long maxWaitTime) {
		try {
			int haveWaitTime = 0;
			String value = getLock(redisKey, maxWaitTime);
			int unit = 5;
			while (StrUtil.isNotBlank(value)) {
				ThreadUtil.sleep(unit);
				haveWaitTime += unit;
				if (haveWaitTime > maxWaitTime) {
					log.error("系统获取redis锁超出最大时间:{}", redisKey);
					return false;
				}
				value = getLock(redisKey, maxWaitTime);
				if (StrUtil.isBlank(value)) {
					return true;
				}
			}
			return true;
		} catch (Exception e) {
			log.error("系统获取redis锁超出最大时间:{}\n{}", redisKey, e.getMessage());
			return false;
		}
	}

	private synchronized String getLock(String redisKey, Long maxWaitTime) {
		String key = GlobalRedisKeyEnum.REDIS_LOCK.name() + ":" + redisKey;
		String lockStr = redisTemplate.opsForValue().get(key);
		if (StrUtil.isBlank(lockStr)) {
			redisTemplate.opsForValue().set(key, "1", maxWaitTime, TimeUnit.MILLISECONDS);
		}
		return lockStr;
	}

	/**
	 * 删除redis锁
	 *
	 * @param redisKey
	 */
	public void delRedisLock(String redisKey) {
		try {
			redisTemplate.delete(GlobalRedisKeyEnum.REDIS_LOCK.name() + ":" + redisKey);
		} catch (Exception e) {
			log.error("删除redis锁失败:{}", e.getMessage());
		}
	}

}

你可能感兴趣的:(redis,缓存,java)