kelovp.
首页 Java Redis 分布式锁

Redis 分布式锁

说起来有些尴尬。最近写了一个抽奖发券的功能,因为是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 {

text
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

K
kelovp
后端工程师 · 广告投放 / 商业化
八年后端,做广告投放与商业化变现系统,现在带团队折腾商业化中台与 AIGC 内容平台。工作之外写点电子音乐、动漫解析和故事。相信把事情想清楚,才写得明白。