淘宝网站开发者,网站开发美工,网站qq交谈怎么做的,更换wordpress语言包1、全局id生成器
当用户抢购时#xff0c;就会生成订单并保存到tb_voucher_order这张表中#xff0c;而订单表如果使用数据库自增ID就存在一些问题#xff1a;
id的规律性太明显受单表数据量的限制
场景分析#xff1a;如果我们的id具有太明显的规则#xff0c;用户或者…1、全局id生成器
当用户抢购时就会生成订单并保存到tb_voucher_order这张表中而订单表如果使用数据库自增ID就存在一些问题
id的规律性太明显受单表数据量的限制
场景分析如果我们的id具有太明显的规则用户或者说商业对手很容易猜测出来我们的一些敏感信息比如商城在一天时间内卖出了多少单这明显不合适。
场景分析二随着我们商城规模越来越大mysql的单表的容量不宜超过500W数据量过大之后我们要进行拆库拆表但拆分表了之后他们从逻辑上讲他们是同一张表所以他们的id是不能一样的 于是乎我们需要保证id的唯一性 Component
public class RedisIdWorker {/*** 开始时间戳*/private static final long BEGIN_TIMESTAMP 1640995200L;/*** 序列号的位数*/private static final int COUNT_BITS 32;private StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplate;}public long nextId(String keyPrefix) {// 1.生成时间戳LocalDateTime now LocalDateTime.now();long nowSecond now.toEpochSecond(ZoneOffset.UTC);// 2.生成序列号long timestamp nowSecond - BEGIN_TIMESTAMP;// 2.1.获取当前日期精确到天// 2.2.自增长String date now.format(DateTimeFormatter.ofPattern(yyyy:MM:dd));Long count stringRedisTemplate.opsForValue().increment(icr: keyPrefix : date);// 3.拼接并返回return timestamp COUNT_BITS | count;}}
利用线程池创建300个并发线程每个线程生成100个id总耗时time 2629ms
Test
void testIdWorker() throws InterruptedException {
CountDownLatch latch new CountDownLatch(300);Runnable task () - {for (int i 0; i 100; i) {long id redisIdWorker.nextId(order);System.out.println(id id);}latch.countDown();
};
long begin System.currentTimeMillis();
for (int i 0; i 300; i) {es.submit(task);
}
latch.await();
long end System.currentTimeMillis();
System.out.println(time (end - begin));
} 2、添加优惠券,实现秒杀下单
下单时需要判断两点
秒杀是否开始或结束如果尚未开始或已经结束则无法下单库存是否充足不足则无法下单 Service
Transactional
public class VoucherOrderServiceImpl extends ServiceImplVoucherOrderMapper, VoucherOrder implements IVoucherOrderService {Resourceprivate SeckillVoucherServiceImpl seckillVoucherService;Resourceprivate RedisIdWorker redisIdWorker;Overridepublic Result seckillVoucher(Long voucherId) {// 1、查询优惠券信息SeckillVoucher voucher seckillVoucherService.getById(voucherId);// 2、判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now()) || voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail(秒杀未开始);}// 3、判断库存是否充足if(voucher.getStock()1) {return Result.fail(库存不足);}// 4、扣减库存voucher.setStock(voucher.getStock()-1);boolean success seckillVoucherService.updateById(voucher);if(!success) {return Result.fail(库存不足);}// 5、创建订单VoucherOrder voucherOrder new VoucherOrder();// 5.1.订单idlong orderId redisIdWorker.nextId(order);voucherOrder.setId(orderId);// 5.2.用户idLong userId UserHolder.getUser().getId();voucherOrder.setUserId(userId);// 5.3.代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);return Result.ok(orderId);}
}
3、解决超卖问题
jmeter分析时记得在HTTP信息头管理器中加上token 测试1秒200个并发量发现会出现超卖问题100个订单扣减库存剩下-9个。 乐观锁解决超卖问题
// 4、扣减库存
boolean success seckillVoucherService.update()
.setSql(stock stock -1)
.eq(voucher_id, voucherId)
.gt(stock, 0)
.update(); 4、一人一单
需求修改秒杀业务要求同一个优惠券一个用户只能下一单 // 根据用户id与优惠券id判断订单是否存在
Long useId UserHolder.getUser().getId();
Integer count query().eq(user_id, useId).eq(voucher_id, voucherId).count();
if(count 0) {return Result.fail(用户已经购买过一次);
}
// 4、扣减库存boolean success seckillVoucherService.update().setSql(stock stock -1).eq(voucher_id, voucherId).gt(stock, 0).update();
但是上述代码涉及到查询与修改因此还是会有多线程安全问题。用jmeter测试发现还是有相同用户id与优惠券id的订单超卖没有达到一人一单的需求。 因此尝试加锁。由于存在较多的写操作因此采用悲观锁。但如果对后面一大段设计增删改查的代码加锁锁粒度太大。如下代码所示。
Transactional
public synchronized Result createVoucherOrder(Long voucherId) {Long userId UserHolder.getUser().getId();// 5.1.查询订单int 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.订单idlong orderId redisIdWorker.nextId(order);voucherOrder.setId(orderId);// 7.2.用户idvoucherOrder.setUserId(userId);// 7.3.代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 7.返回订单idreturn Result.ok(orderId);
}
存在两个问题
1且由于在createVoucherOrder代码外加上事务事务包含锁因此会导致加锁读操作后事务还未提交就提前将所释放下一个线程获取锁时读取到的数据库的值为旧值造成数据不一致性因此需要在事务外加锁。
2将synchronized加在方法上相当于是对this加锁因此多线程过来加的是一把锁串行化性能差。由于需求是一人一单因此只需要对同一用户加锁。因此去除ThreadLocal中的userId进行加锁。但每次线程进入createVoucherOrder方法都会新建一个userId对象所以其实本质上还是对不同的对象进行了加锁userId.toString()的底层也是new一个string对象但我们需要的是同一用户只有一把锁因此需要intern() 这个方法从常量池中拿到数据。修改代码
Overridepublic Result seckillVoucher(Long voucherId) {// 1、查询优惠券信息SeckillVoucher voucher seckillVoucherService.getById(voucherId);// 2、判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now()) || voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail(秒杀未开始);}// 3、判断库存是否充足Integer stock voucher.getStock();if(voucher.getStock()1) {return Result.fail(库存不足);}Long userId UserHolder.getUser().getId();synchronized (userId.toString().intern()) {return createVoucherOrder(voucherId);}}Transactionalpublic Result createVoucherOrder(Long voucherId) {Long userId UserHolder.getUser().getId();// 根据用户id与优惠券id判断订单是否存在int count query().eq(user_id, userId).eq(voucher_id, voucherId).count();if(count 0) {return Result.fail(用户已经购买过一次);}// 4、扣减库存boolean success seckillVoucherService.update().setSql(stock stock -1).eq(voucher_id, voucherId).gt(stock, 0).update();if (!success) {//扣减库存return Result.fail(库存不足);}// 5、创建订单VoucherOrder voucherOrder new VoucherOrder();// 5.1.订单idlong orderId redisIdWorker.nextId(order);voucherOrder.setId(orderId);// 5.2.用户idvoucherOrder.setUserId(userId);// 5.3.代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);return Result.ok(orderId);}
但是以上做法依然有问题因为你调用的方法其实是this.的方式调用的事务想要生效还得利用代理来生效所以这个地方我们需要获得原始的事务对象 来操作事务
1在启动类上加上 2在pom.xml文件里加上依赖
dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactId
/dependency
3修改代码
synchronized (userId.toString().intern()) {IVoucherOrderService proxy (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);
}
记得将seckillVoucher方法上的事务注解取消否则还是会出现上述问题。
查看数据库成功实现一人一单 5、集群环境下的并发问题
通过加锁可以解决在单机情况下的一人一单安全问题但是在集群模式下就不行了。
开启两份服务同一用户下单两次负载均衡算法采用轮询库存扣减两次。 有关锁失效原因分析
由于现在我们部署了多个tomcat每个tomcat都有一个属于自己的jvm那么假设在服务器A的tomcat内部有两个线程这两个线程由于使用的是同一份代码那么他们的锁对象是同一个是可以实现互斥的但是如果现在是服务器B的tomcat内部又有两个线程但是他们的锁对象写的虽然和服务器A一样但是锁对象却不是同一个所以线程3和线程4可以实现互斥但是却无法和线程1和线程2实现互斥这就是 集群环境下syn锁失效的原因在这种情况下我们就需要使用分布式锁来解决这个问题。