限流:通过配置sentinel解决
队列、异步
通过加锁sychronized或者lock来说定扣减优惠券这一步的化,出现的问题是:sychronized作用范围是单个jvm实例,对于集群分布就失效了,且单机jvm加锁之后变成串行效率下降
可以用分布式锁,但是分布式锁过于笨重性能下降。
有时为了解决ABA问题,还需要加上一个版本号字段,然后判断版本号是否相同。
扣减库存的多种sql适用场景
第一种 : update product set stock=stock-1 where id = 1 and stock>0
第二种: update product set stock=stock-1 where stock=#{原先查询的库存} and id = 1 and stock>0
第三种 :update product set stock=stock-1,versioin = version+1 where id = 1 and stock>0 and
version=#{原先查询的版本号}
核心是解决超卖问题,防止库存为负数:
对于方案1 :id是主键索引的前提下,如果每次只是减少一个库存,则可以才采用,只做数据安全的校验,可以有效减库存,性能更高,避免大量无效sql,只要有库存就可以操作成功
场景: 高并发场景下的取号器,优惠券发放扣减库存
方案2:使用业务自身的条件作为乐观锁,但是存在ABA问题,对比方案3 的好处是不用增加version版本号,如果只是扣减库存且不关心ABA问题,则可以采用。
方案3:增加版本号主要是为了解决ABA 问题,数据读取后,更新前数据被人篡改过,version只能做递增。
场景:商品秒杀,优惠券方法,需要记录库存操作前后的业务。
个人超领优惠券
领取优惠券的过程中,校验和保存到数据库两步没有加分布式锁,导致个人超领优惠券;
实现分布式锁的方法有mysql、redis、zookeeper
分布式锁的框架redisson
分布式锁https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
使用分布式锁插件
1、导入一俩到common模块
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.1</version>
2、配置分布式锁,在commoon中
@Configuration
@Data
public class APPConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redispwd;
/**
* 配置分布式锁的redisson
* @return
*/
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
// 单机方式
config.useSingleServer().setPassword(redispwd).setAddress("redis://"+redisHost+":"+redisPort);
// 集群模式
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
3、使用分布式锁
@GetMapping("lock")
public JsonData testLock(){
// 分布式锁与这个以及一样
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
RLock lock = redissonClient.getLock("lock:coupon:1");
// // 阻塞等待时间
// lock.lock(10, TimeUnit.SECONDS);
lock.lock();
//业务逻辑
try{
log.info("加锁成功,处理业务逻辑"+Thread.currentThread().getId());
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.info("解锁成功");
lock.unlock();
}
return JsonData.buildSuccess();
}
redisson如何解决过期时间小于业务的执行时间:
采用看门狗的方式;
看门狗的检查锁的超时时间是30s。
// 多个线程进入会阻塞,等待锁释放 有看门狗,默认30s
rlock.lock();
// 加锁10s钟过期,没有watch dog 功能
rlock.lock(10,TimeUnit.SECONDS);
项目中设置事务的时候,先在启动类中开启事务管理,然后再需要事务的方法上面添加事务的注解。