基本思路是:锁+幂等性
具体实现:
使用redis的decr (对key对应的数字做减1操作。如果key不存在,那么在操作之前,这个key对应的值会被置为0。如果key有一个错误类型的value或者是一个不能表示成数字的字符串,就返回错误。这个操作最大支持在64位有符号的整型数字。)可以实现原子性的递增递减操作控制优惠码不超送,然后给每个用户维护一个userid+优惠码活动的key保证幂等性,只要redis存在这种key,那就代表已经领取了,具体的优惠码分发可以异步执行。为了避免竞争(同一个用户,多个设备同时领取):
获取锁 领取优惠码(伪代码)1,分布式锁方法
public boolean acquireLock(String lock) {
// 1. 通过SETNX试图获取一个lock
boolean success;
Jedis jedis =jedisPool.getResource();
//强制过期时间:60 秒
int expired =60 *1000;
long value = System.currentTimeMillis() + expired;
long acquired = jedis.setnx(lock, String.valueOf(value));
if (1 == acquired) { //setnx 成功,则成功获取一个锁
success =true;
}else { //setnx 失败,说明锁仍然被其他对象保持,检查其是否已经超时
// 获取覆盖旧值之前获取到的旧值
long oldValue = Long.valueOf(jedis.get(lock));
//检查是否超时(对比锁是否超时)
if (oldValue < System.currentTimeMillis()) { //超时,进行锁覆盖
// 获取覆盖值之后的获取到的旧值
String getOldValue = jedis.getSet(lock, String.valueOf(value));
if (Long.valueOf(getOldValue) == oldValue) { // 获取锁成功(覆盖旧值之前获取到的旧值 和 覆盖值之后的获取到的旧值 相同 则本次获锁成功,否则 失败已经被别的对象获取到)
success =true;
}else { // 已被其他进程捷足先登了
success =false;
}
}else { //未超时,则直接返回失败
success =false;
}
}
jedisPool.close();
return success;
}
2.业务代码(优惠码领取发放)
@Test
public void redisAcquireLockLock(User user, String eventsPromoCodeMark) {
Jedis jedisResources = jedisPool.getResource();
//1,活动上线前设置优惠码总量一百万:jedisResources.set(promoCodeNumberKey, "1000000");
//设置本次活动优惠码锁(保证幂等性):userId + 优惠码活动的key
String lock = user.getId() + eventsPromoCodeMark;
//设置优惠码总量key
String promoCodeNumberKey = eventsPromoCodeMark + "promoCodeNumber";
//设置用户优惠码领取key
String userKey = lock + "receiveCoupon";
//优惠码领取完了
boolean promoCode = jedisResources.exists(promoCodeNumberKey) &&
StringUtils.isNotBlank(jedisResources.get(promoCodeNumberKey)) &&
0L < Long.valueOf(jedisResources.get(promoCodeNumberKey));
if (!promoCode) {
System.out.println("优惠码已经领取完了");
jedisResources.close();
return;
}
//为了避免竞争(同一个用户,多个设备同时领取).使用Redis.setNX() 实现分布式锁(重复数据插入可用其来实现排他锁)
boolean flag = acquireLock(lock);
if (flag) {
//1,获取锁成功,进行用户已领取标记,查询用户是否已经领取过
Boolean isExists = jedisResources.exists(userKey);
if (!isExists) {
//2,先使用redis的decr可以实现原子性的递增递减操作控制优惠码不超送,
Long success = jedisResources.decr(promoCodeNumberKey);
// 先减库存后发码(减库存后返回的现有库存数量大于等于0说明本次抢码成功,再进行发送优惠码,否则库存已经空了就不进行发送优惠码)
if (success >= 0L) {
//3,再进行优惠码分发(可进行MQ进行发放优惠码,如果减库存成功,发放失败,进行发放补偿性操作)
jedisResources.set(userKey, "received");
System.out.println("领取成功");
}
} else {
System.out.println("已经领取过了");
}
} else {
System.out.println("请重试");
}
jedisResources.close();
}
网友评论