赤赤元
有梦想的咸鱼

解决思路
  从读到写这段时间的数据不一致问题,根源在于用户并行(个人认为并发是时间概念,并行是空间概念),
要解决这个问题,需要让用户串行,单个用户原子性。锁 说它可以做到。
  锁只有一个目的,就是把并行变为串行,但是上锁的方式 五花八门。
  1. Java应用内存锁
    Java中自带很多内存锁,synchronize,各种Lock,但是优惠券服务多机部署,内存锁无法满足需求;
  2. Mysql数据库锁
    优惠券服务使用MySql(一个写节点),innodb存储引擎,innodb 支持 行锁。
    利用innodb的行锁机制,可以使用两种方式实现用户领券的原子性:
    第一种,读取之前上锁, 更新之后解锁
      select  ... from table where ... for update;
      update table set ....
      优点: 简单明了; 缺点: select 和 update 之间处理 出异常或应用异常终止 会产生死锁。
    第二中,利用update 锁行机制,加上where 条件 判断数据,也是读取前上锁,更新后解锁。
      update table set .... where ....
      优点:简单明了; 缺点: 效率不高
    另外更新操作直接命中数据库会对数据库产生很大的压力,所以数据库锁无法满足抢券业务;
  3. Redis分布式内存锁
    优惠券服务使用单节点Redis,Redis 支持setnx命令。
    利用setnx命令,可以在应用中自建锁及维护锁的生命周期。
    基本思路是领券前将优惠券的key通过 setnx 命令写进 redis,成功则之后便执行后续的三次读取 比较 和更新,
  最后 del 命令删除优惠券的key。
    优点:逻辑简单,实现简单,total_got,user_got,user_today_got 三个值 存哪里不受任何限制。
    缺点:不太可靠,setnx 成功后,应用出现异常,没有执行最后的del , 会产生死锁;也可以在 setnx 后再
  设置一个过期时间,是的,这是一个办法,只需要保证过期时间大于 接口的最大执行时间。
    另外,也可以使用 官方推荐的 分布式Redis锁 开源实现 Redisson。

以redis为例:

@RequestMapping("testRedisson")
@ResponseBody
public String testRedisson(){
Jedis jedis = redisUtil.getJedis();
RLock lock = redissonClient.getLock("lock");// 声明锁
lock.lock();//上锁
try {
String v = jedis.get("k");
if (StringUtils.isBlank(v)) {
v = "1";
}
System.out.println("->" + v);
jedis.set("k", (Integer.parseInt(v) + 1) + "");
}finally {
jedis.close();
lock.unlock();// 解锁
}
return "success";
}

 

 

 

 

 

posted on 2020-04-08 10:02  赤赤元  阅读(2240)  评论(0编辑  收藏  举报