Redis 分布式锁 - KelovpString

/ 3评 / 0

    说起来有些尴尬。最近写了一个抽奖发券的功能,因为是100%中奖,倒是抽奖没怎么折腾。但是问题在于返券。

    大致规则是这样子的:在活动期间每人每支付一个订单,就可以获取一次抽奖机会,而最多可以抽十次奖,抽奖之后得把奖品发放。

    分析需求之后就变得很简单,无非是拉取符合条件的订单数,然后计算抽奖机会,满足则可抽奖,抽完奖同步中奖纪录,然后发放奖品。的确在流程上也没什么问题,主要注意同步中奖信息时的事物就好。这样一来只需要在方法上加上@Transactional注解就好了。

    然后三下五除二搞定了,开始自己本地测试,也没啥问题。于是匆匆上了测试库。

    过了一会前端告诉我他抽奖抽了十二次。我告诉他不可能了。十次的时候就会有check,你连正常访问都做不到。然后打开DB一查还真是。。于是我开始重新审视我的逻辑。但毕竟一个流程清晰的抽奖,在业务上不会犯这种奇怪的逻辑错误。

    然后看了下DB插入时间,我似乎明白了啥。。于是我写了小的测试:

public static void main(String args[]){
        ExecutorService pool = Executors.newFixedThreadPool(5);  
        IntStream.range(0,4).forEach( i ->
        pool.execute(() -> {
        	HttpUtil.get("http://localhost:8080/xxxx?xx=xx");
        }));   
        pool.shutdown();  
    }

     然后一个人拿着一次的抽奖机会,抽中了七个奖品(我的次数check逻辑是通过奖品记录表来反减的

    然后我就想给这方法加锁。于是给方法加上了synchronized。这一幕被同事大哥给看见了。他问我我们的项目上锁别用这个,服务器有很多台,分布式的,你这个太鬼畜了。

    我一想??那分布式锁如何实现?同事给我一条明路:

@Component
public class RedisLockUtil {

	private static StringRedisTemplate redisTemplate;

	@Autowired
	public void setRedisTemplate(StringRedisTemplate redisTemplate) {
		RedisLockUtil.redisTemplate = redisTemplate;
	}

	/**
	 * 获取锁
	 */
	public static void lock(String key) {
		int i = 1;
		while (!StringUtil.isEmptyOrNull(redisTemplate.opsForValue().getAndSet(key, "1"))) {
			i++;
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
			}
			if (i >= 10) {
				break;
			}
		}
		redisTemplate.expire(key, 60, TimeUnit.SECONDS);
	}

	/**
	 * 释放锁
	 */
	public static void unlock(String key) {
		redisTemplate.delete(key);
	}

	/**
	 * 通用锁控制
	 * 
	 * @param key
	 *            加锁主键
	 * @param redisLockProcess
	 *            被锁定流程
	 */
	public <R> R process(String key, RedisLockProcess<? extends R> redisLockProcess) {
		try {
			lock(key);
			return redisLockProcess.process();
		} catch(BaseException e){
			throw e;
		} catch (Exception e) {
			throw new BaseException("请求处理异常", e);
		} finally {
			unlock(key);
		}
	}
}

    利用Redis快速起了一个分布式锁,完美解决了上面的问题,这样只需在方法执行前加上加锁的过程:

public Map<String, Object> lockProcess(String xx){
		String key = "NAME_SPACE_"+xx;
		return redisLockUtil.process(key, () -> this.fangfa(xx));
	}

        这样除了事务的保护之外还有针对并发的处理,问题就被解决了(对了,在此之前记得配置Namespce以及Spring的Bean注入。差点。。感谢前端的光速请求)

    期间顺便参观了下别人的用法:

    https://blog.csdn.net/he90227/article/details/69568702#t0



  1. Nostring说道:

    现在回看这种方案的使用,利用Redis做锁原则上可行,但是在效率和资源上我觉得还是自己真的土豪,居然拿这个去锁一个方法,其实这里不应该在使用这样的设计思路去设计。虽然思路简单实现简单,但是生产环境不推荐这样去做。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注