赞
踩
1、 版本号法
2、CAS法
- @Service
- public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
-
- @Resource
- private ISeckillVoucherService seckillVoucherService;
- @Override
- public Result seckillVoucher(Long voucherId) {
- //1.查询优惠券信息
- SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
- //2.判断秒杀是否开始
- if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
- return Result.fail("秒杀尚未开始");
- }
- //3.判断秒杀是否结束
- if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
- return Result.fail("秒杀已经结束");
- }
- //4.判断库存是否充足
- if (seckillVoucher.getStock() < 1) {
- return Result.fail("库存不足");
- }
- //5.扣减库存
- boolean success = seckillVoucherService.update()
- .setSql("stock = stock - 1")//set stock = stock - 1
- .eq("voucher_id", voucherId).eq("stock",seckillVoucher.getStock())//where id = ? and stock = ?
- .update();
- if (!success) {
- return Result.fail("库存不足");
- }
- //6.创建订单
- VoucherOrder voucherOrder = new VoucherOrder();
- //6.1订单id
- voucherOrder.setId(Long.valueOf(RandomUtil.randomNumbers(10)));
- //6.2用户id
- Long id = UserHolder.getUser().getId();
- voucherOrder.setUserId(id);
- //6.3代金券id
- voucherOrder.setVoucherId(voucherId);
- save(voucherOrder);
- //7.返回订单id
- return Result.ok();
- }
- }
没有超卖,为什么失败太多?
原因:库存充足时,只要改了stock就是取消。所以只要stock > 0就行
- @Service
- public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
-
- @Resource
- private ISeckillVoucherService seckillVoucherService;
- @Override
- public Result seckillVoucher(Long voucherId) {
- //1.查询优惠券信息
- SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
- //2.判断秒杀是否开始
- if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
- return Result.fail("秒杀尚未开始");
- }
- //3.判断秒杀是否结束
- if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
- return Result.fail("秒杀已经结束");
- }
- //4.判断库存是否充足
- if (seckillVoucher.getStock() < 1) {
- return Result.fail("库存不足");
- }
- //5.扣减库存
- boolean success = seckillVoucherService.update()
- .setSql("stock = stock - 1")//set stock = stock - 1
- .eq("voucher_id", voucherId).gt("stock",0)//where id = ? and stock > 0
- .update();
- if (!success) {
- return Result.fail("库存不足");
- }
- //6.创建订单
- VoucherOrder voucherOrder = new VoucherOrder();
- //6.1订单id
- voucherOrder.setId(Long.valueOf(RandomUtil.randomNumbers(10)));
- //6.2用户id
- Long id = UserHolder.getUser().getId();
- voucherOrder.setUserId(id);
- //6.3代金券id
- voucherOrder.setVoucherId(voucherId);
- save(voucherOrder);
- //7.返回订单id
- return Result.ok();
- }
- }
- @Service
- public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
-
- @Resource
- private ISeckillVoucherService seckillVoucherService;
- @Override
- public Result seckillVoucher(Long voucherId) {
- //1.查询优惠券信息
- SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
- //2.判断秒杀是否开始
- if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
- return Result.fail("秒杀尚未开始");
- }
- //3.判断秒杀是否结束
- if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
- return Result.fail("秒杀已经结束");
- }
- //4.判断库存是否充足
- if (seckillVoucher.getStock() < 1) {
- return Result.fail("库存不足");
- }
- //5.一人一单
- Long userId = UserHolder.getUser().getId();
- //5.1判断是否存在订单
- Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
- //5.2判断是否存在
- if (count > 0) {
- //用户已经购买过
- return Result.fail("用户已经购买一次");
- }
- //6.扣减库存
- boolean success = seckillVoucherService.update()
- .setSql("stock = stock - 1")//set stock = stock - 1
- .eq("voucher_id", voucherId).gt("stock",0)//where id = ? and stock > 0
- .update();
- if (!success) {
- return Result.fail("库存不足");
- }
- //7.创建订单
- VoucherOrder voucherOrder = new VoucherOrder();
- //7.1订单id
- voucherOrder.setId(Long.valueOf(RandomUtil.randomNumbers(10)));
- //7.2用户id
- Long id = UserHolder.getUser().getId();
- voucherOrder.setUserId(id);
- //7.3代金券id
- voucherOrder.setVoucherId(voucherId);
- save(voucherOrder);
- //8.返回订单id
- return Result.ok();
- }
- }
200个订单,为什么异常95%(为什么有10个订单)?
原因:多个线程穿插,并发情况(插入操作,不能用乐观锁),只能用悲观锁
- @Service
- public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
-
- @Resource
- private ISeckillVoucherService seckillVoucherService;
- @Override
- public Result seckillVoucher(Long voucherId) {
- //1.查询优惠券信息
- SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
- //2.判断秒杀是否开始
- if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
- return Result.fail("秒杀尚未开始");
- }
- //3.判断秒杀是否结束
- if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
- return Result.fail("秒杀已经结束");
- }
- //4.判断库存是否充足
- if (seckillVoucher.getStock() < 1) {
- return Result.fail("库存不足");
- }
- Long userId = UserHolder.getUser().getId();
- synchronized (userId.toString().intern()) {//锁代码块需要共用同一把锁,所以做字符串转成同一对象
- //获取代理对象
- IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
- return proxy.createVoucherOrder(voucherId);
- }
- }
- //在这个方法上加synchronized,它的范围是整个方法,锁的对象是this(当前类),意味着所有多线程对象都会加锁
- // ,且锁的对象都为同一个,会串行执行,前面的锁不释放,就会一直等待,影响性能
- @Transactional
- public Result createVoucherOrder(Long voucherId) {
- //5.一人一单
- Long userId = UserHolder.getUser().getId();
- //5.1判断是否存在订单
- Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
- //5.2判断是否存在
- if (count > 0) {
- //用户已经购买过
- return Result.fail("用户已经购买一次");
- }
- //6.扣减库存
- boolean success = seckillVoucherService.update()
- .setSql("stock = stock - 1")//set stock = stock - 1
- .eq("voucher_id", voucherId).gt("stock",0)//where id = ? and stock > 0
- .update();
- if (!success) {
- return Result.fail("库存不足");
- }
- //7.创建订单
- VoucherOrder voucherOrder = new VoucherOrder();
- //7.1订单id
- voucherOrder.setId(Long.valueOf(RandomUtil.randomNumbers(10)));
- //7.2用户id
- Long id = UserHolder.getUser().getId();
- voucherOrder.setUserId(id);
- //7.3代金券id
- voucherOrder.setVoucherId(voucherId);
- save(voucherOrder);
- //8.返回订单id
- return Result.ok();
- }
- }
总结:
1、synchronized锁不要加在createVoucherOrder方法上,虽然满足要求,但会影响性能。因为它的范围是整个方法,锁的对象是this(当前类),意味着所有多线程对象都会加锁,且锁的对象都为同一个,会串行执行,前面的锁不释放,就会一直等待,影响性能。
2、synchronized代码块不要写在createVoucherOrder方法内部,虽然满足要求,但也会造成并发问题。因为方法上加了@Transactional,在内部加锁会造成事务没有提交就有订单,也会造成并发问题。
3、写synchronized代码块时,括号里面参数要唯一。因为锁代码块需要共用同一把锁,所以做字符串转成同一对象。
4、要调用有@Transactional的方法时,必须要用代理对象调用。因为@Transactional底层是代理对象,而一般调用方法是this调用。
原因:多个jvm没有使用同一把锁
解决方法:分布式锁,Redis锁,Redssion
总结:
1、Redis分布式锁可以用命令SETNX lock thread1和命令expire lock 10,但要保证是一个事务,所以用命令SET lock thread1 NX EX 10。
2、Redis分布式锁要设置过期时间,防止服务器宕机后死锁,且锁是非阻塞式,保证死锁后线程不会一直等待释放锁。
1、tryLock和unLock工具类方法
- public class SimpleRedisLock {
-
- private StringRedisTemplate stringRedisTemplate;
- private String name;
- private static final String key_prefix = "lock:";
-
- public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
- this.stringRedisTemplate = stringRedisTemplate;
- this.name = name;
- }
-
- public boolean tryLock(long timeoutSec){
- long value = Thread.currentThread().getId();
- Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key_prefix + name, value + "", timeoutSec, TimeUnit.SECONDS);
- //自动拆箱,可能有安全问题,万一是null
- return Boolean.TRUE.equals(success);
- }
-
- public void unlock(){
- stringRedisTemplate.delete(key_prefix + name);
- }
- }
2、Redis分布式锁实现(部分逻辑)
- Long userId = UserHolder.getUser().getId();
- //创建锁对象
- SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order" + userId);
- //获取锁
- boolean isLock = lock.tryLock(1200);
- //判断是否获取锁成功
- if (!isLock) {
- return Result.fail("不允许重复下单");
- }
- //获取代理对象
- try {
- IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
- return proxy.createVoucherOrder(voucherId);
- } finally {
- //释放锁
- lock.unlock();
- }
1、Redis分布式锁误删问题
产生原因:业务执行时间比锁时间长
线程一尝试获取锁,因为第一个来,所以获取锁成功 。因为某种原因业务产生阻塞,超过超时时间,线程一锁被释放。
线程二尝试获取锁成功,执行业务。线程一业务完成,释放锁(del),线程二锁被释放。
线程三尝试获取锁成功,执行业务。
结果:出现并发问题。
解决方法 :判断是否是自己的锁
- public class SimpleRedisLock {
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- private String name;
- private static final String key_prefix = "lock:";
- private static final String id_prefix = UUID.randomUUID().toString(true) + "-";
-
-
- public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
- this.stringRedisTemplate = stringRedisTemplate;
- this.name = name;
- }
-
- public boolean tryLock(long timeoutSec){
- //获取线程标识
- String value = id_prefix + Thread.currentThread().getId();
- Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key_prefix + name, value, timeoutSec, TimeUnit.SECONDS);
- //自动拆箱,可能有安全问题,万一是null
- return Boolean.TRUE.equals(success);
- }
-
- public void unlock(){
- //获取线程标识
- String value = id_prefix + Thread.currentThread().getId();
- //获取锁中标识
- String id = stringRedisTemplate.opsForValue().get(key_prefix + name);
- //判断是否一致
- if (id.equals(value)) {
- //释放锁
- stringRedisTemplate.delete(key_prefix + name);
- }
- }
- }
总结:在获取锁可以用UUID + 线程号表示,防止集群中出现相同标识。
2、Redis锁的原子性问题
产生原因:当将要释放锁时产生阻塞。例如:jvm垃圾回收调用gc时会产生阻塞。
解决方法: Lua脚本
1、在Resource里编写Lua脚本
- --比较线程标识和锁中的标识是否一致
- if (redis.call('get', KEYS[1]) == ARGV[1]) then
- --释放锁
- return redis.call('del',KEYS[1])
- end
- return 0
2、调用Lua脚本
- public class SimpleRedisLock {
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- private String name;
- private static final String key_prefix = "lock:";
- private static final String id_prefix = UUID.randomUUID().toString(true) + "-";
- //读取Lua脚本
- private static final DefaultRedisScript<Long> unlock_script;
- static {
- unlock_script = new DefaultRedisScript<>();
- unlock_script.setLocation(new ClassPathResource("unlock.lua"));
- unlock_script.setResultType(Long.class);
- }
-
-
- public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
- this.stringRedisTemplate = stringRedisTemplate;
- this.name = name;
- }
-
- public boolean tryLock(long timeoutSec){
- //获取线程标识
- String value = id_prefix + Thread.currentThread().getId();
- Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key_prefix + name, value, timeoutSec, TimeUnit.SECONDS);
- //自动拆箱,可能有安全问题,万一是null
- return Boolean.TRUE.equals(success);
- }
-
- public void unlock(){
- //调用Lua脚本
- stringRedisTemplate.execute(unlock_script,
- Collections.singletonList(key_prefix + name),
- id_prefix + Thread.currentThread().getId());
- }
- }
Redission锁(不用自己写setnx,底层已经封装)
1、引入redission依赖
- <dependency>
- <groupId>org.redisson</groupId>
- <artifactId>redisson</artifactId>
- <version>2.9.0</version>
- </dependency>
2、配置redission
- @Configuration
- public class RedissionConfig {
-
- public RedissonClient redissonClient(){
- //配置
- Config config = new Config();
- //useSingleServer()单节点模式
- config.useSingleServer().setAddress("redis://127.0.0.1:6379");//.setPassword("")
- //创建RedissonClient对象
- return Redisson.create(config);
- }
- }
3、代码
- @Service
- public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
-
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- @Resource
- private ISeckillVoucherService seckillVoucherService;
- @Resource
- private RedissonClient redissonClient;
- @Override
- public Result seckillVoucher(Long voucherId) {
- //1.查询优惠券信息
- SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
- //2.判断秒杀是否开始
- if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
- return Result.fail("秒杀尚未开始");
- }
- //3.判断秒杀是否结束
- if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
- return Result.fail("秒杀已经结束");
- }
- //4.判断库存是否充足
- if (seckillVoucher.getStock() < 1) {
- return Result.fail("库存不足");
- }
- Long userId = UserHolder.getUser().getId();
- //创建锁对象
- // SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order" + userId);
- RLock lock = redissonClient.getLock("order" + userId);
- //获取锁
- // boolean isLock = lock.tryLock(1200);
- boolean isLock = lock.tryLock();
- //判断是否获取锁成功
- if (!isLock) {
- return Result.fail("不允许重复下单");
- }
- //获取代理对象
- try {
- IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
- return proxy.createVoucherOrder(voucherId);
- } finally {
- //释放锁
- lock.unlock();
- }
-
- }
- //在这个方法上加synchronized,它的范围是整个方法,锁的对象是this(当前类),意味着所有多线程对象都会加锁
- // ,且锁的对象都为同一个,会串行执行,前面的锁不释放,就会一直等待,影响性能
- @Transactional
- public Result createVoucherOrder(Long voucherId) {
- //5.一人一单
- Long userId = UserHolder.getUser().getId();
- //5.1判断是否存在订单
- Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
- //5.2判断是否存在
- if (count > 0) {
- //用户已经购买过
- return Result.fail("用户已经购买一次");
- }
- //6.扣减库存
- boolean success = seckillVoucherService.update()
- .setSql("stock = stock - 1")//set stock = stock - 1
- .eq("voucher_id", voucherId).gt("stock",0)//where id = ? and stock > 0
- .update();
- if (!success) {
- return Result.fail("库存不足");
- }
- //7.创建订单
- VoucherOrder voucherOrder = new VoucherOrder();
- //7.1订单id
- voucherOrder.setId(Long.valueOf(RandomUtil.randomNumbers(10)));
- //7.2用户id
- Long id = UserHolder.getUser().getId();
- voucherOrder.setUserId(id);
- //7.3代金券id
- voucherOrder.setVoucherId(voucherId);
- save(voucherOrder);
- //8.返回订单id
- return Result.ok();
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。