总结就是 所有敏感的操作 类似跟钱挂钩的 比如发放优惠券的前提就是判断到发放 我们一般都是让一系列操作是原子性 并且别人获取不到这个锁
如果数据不敏感 类似点赞的 那么没有必要 因为一致性必须牺牲高可用的
工厂模式帮我们自动根据传入参数自动执行不同的函数
策略模式帮我们设定了不同的方案 根据传入的参数选择不同的参数去选择不同的方法
首先大体确定问题范围属于并发引起的问题 所以很容易就想到锁了
超卖问题的话
我们只需要确定为什么会超卖
无非就是再发放优惠券的时候没有确定是否已达用户领取上限 或者是 优惠券总发放上限
所以很好解决 无非就是 用上乐观锁的思想 再发放的sql语句加上where条件判断而已
锁失效问题
查询 判断 新增满足这三步之后才会发放优惠券
逻辑上确实没问题 但是我们考虑的只是单机
如果我们并发前提下 A线程执行新增之前 B执行到判断了
这个时候就会出现 同时满足的情况
所以实际上也就是 查询判断新增这三步核心逻辑 我们分开写了 没有使他们同时执行 不具备原子性,
所以我们直接考虑用 Synchronized实现
锁方法太大了 太慢了 所以我们考虑锁方法块 锁的对象由于我们知道Long存在常量池复用-127到128的对象
所以不能直接锁long 要锁实际值 所以使用apiuserId.toString().intern()
来最终解决这个问题
事务边界问题
如果我们开启事务之后 才获取锁 然后释放锁 再提交事务
这个时候就会出现释放锁之后 还没提交事务 其他线程拿到锁 访问了资源 也就是脏读问题
那么只需要换个思考方式 那么我就提交好了再释放锁 不就可以了嘛
所以很简单 就是先获取锁 开启事务 提交事务 释放锁
事务失效问题
那么都用到事务了 事务肯定就会存在失效的情况
排查
1.事务方法非public修饰 spring会有方法判断方法如果不是public进不去
2.非事务方法调用事务方法 因为事务底层是aop生成动态代理 我们当前类调用实际上是this去调用 这个类似成员变量赋值都是this.set get
那么我们非事务 自然aop不会帮我们生成动态代理对象 所以也就不会检测到事务了 所以也就失效了 解决方案就是 手动调用aop对象去调用方法 也就是 aop(动态代理对象).方法名
3.事务方法的异常被捕获 被捕捉了 自然没异常就不会回滚
4事务异常类型不对 类型不对 也就不会检测到 也就相当于没回滚了
5事务传播行为不对 事务之间是有隔离性的 那么A回滚了 是不会影响B的 因为B是独立于A的 当然如果这里是嵌套事务的话 那么就大的会影响小的 小的不会影响大的
6.没有被Spring管理 如果类都没被spring管理 aop都没办法帮你创建代理对象 那就肯定失效了
集群锁失效问题
我们使用的Synchronized底层是基于jvm的锁监视器去做的
当我们部署了多个实例 那么就会有多个jvm
所以自然每个jvm的锁监视器就不一样了 那么这个时候实际上 就会有多个user2的锁
解决方案 类似seata记录表 不再基于jvm的锁监视器 而是基于数据库表 也就是设置分布式锁
分布式锁问题
我们一开始是加锁后再设置过期时间 那么如果加锁后宕机了呢 对吧 实际上还是回到两个操作是分开的 解决方案就是让两个操作有原子性 一起执行
加锁同时设置过期时间指令:SET key value NX EX seconds
那么设置了过期时间后 就会出现当线程1阻塞的时候 锁A过期了
所以这个时候别的线程2就可以获取到锁A 这个时候 开始执行业务
但是这个时候线程1醒过来了 继续删除释放锁 就会导致线程3可以拿到锁了 就会出现又并发了
解决方案 我们设置线程名字为锁key 比如 key为 Thread1 加上userid 这种唯一标识
删除之前判断是否当前线程的 如果不是就不删
记得把判断跟删除 作为一个操作 不然又会出现极端情况了
后面我们还使用了redisson来作为我们的锁
因为他支持重入,看门狗等 而且使用更加简单
还引入了工厂模式来帮我们自然化 根据用户传入的锁类型支持不同的获取锁的对象
后面引入策略模式来帮我们解决多种获取失败方案 解决代码冗余问题