当前位置: 首页 > news >正文

惠州市网站建设企业学习网站建设建议调查问卷

惠州市网站建设企业,学习网站建设建议调查问卷,前端做图表的网站,成都十大平面设计工作室全局唯一ID 唯一ID的必要性 每个店铺都可以发布优惠券: 当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题: id的规律性太明显,容易被用户根据id的间隔来猜测…

全局唯一ID

唯一ID的必要性

每个店铺都可以发布优惠券:

当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题:

  • id的规律性太明显,容易被用户根据id的间隔来猜测到销量等商业信息,不够保密

  • 受单表数据量的限制,mysql的id自增长有数值约束,且数据量大的情况下会进行分库分表,表不同自增长id可能相同,在分布式系统中是不允许的

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:

Redis恰好满足以上特性,为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息:

ID的组成部分:符号位:1bit,永远为0

时间戳:31bit,以秒为单位,可以使用69年

序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID,这个序列号足够大,几乎不可能到达极限

redis实现全局唯一ID

获取当前时间戳的秒数

LocalDateTime time = LocalDateTime.of(2023, 9, 2, 0, 0, 0);long of = time.toEpochSecond(ZoneOffset.UTC);

生成序列号,自增长的key为了防止一直使用该key,最后导致达到redis的上限,故需要拼接上日期,既防止达到上限又能方便统计同一天的下单量

 //开始时间戳秒数private static final long BEGIN_TIMESTAMP = 1693612800L;//序列号位数private static final int COUNT_BITS = 32;private StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public long nextId(String keyPrefix) {//生成时间戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timestamp = nowSecond - BEGIN_TIMESTAMP;//利用redis的自增生成序列号String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));Long increment = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);//拼接return timestamp << COUNT_BITS | increment;}

添加优惠券

每个店铺都可以发布优惠券,分为平价券和特价券。平价券可以任意购买,而特价券需要秒杀抢购:

平价卷由于优惠力度并不是很大,所以是可以任意领取

而代金券由于优惠力度大,所以像第二种卷,就得限制数量,特价卷除了具有优惠卷的基本信息以外,还具有库存,抢购时间,结束时间等等字段

添加特价券

{"shopId":1,
"title":"100元代金券",
"subTitle":"周一至周五均可使用",
"rules":"全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食",
"payValue":8000,
"actualValue":10000,
"type":1,
"stock":100,
"beginTime":"2023-09-02T10:09:17",
"endTime":"2023-09-26T12:09:04"
}

由于没有后台管理系统,故使用postman进行post请求添加,需要关闭拦截器,同时设置有效的开始时间和结束时间,优惠券才会显示

实现秒杀下单

下单核心思路:当我们点击抢购时,会触发右侧的请求,我们只需要编写对应的controller即可,service层编写对应的代码操作数据库即可

下单时需要判断两点:

  • 秒杀是否开始或结束,如果尚未开始或已经结束则无法下单

  • 库存是否充足,不足则无法下单

下单核心逻辑分析:

当用户开始进行下单,我们应当去查询优惠卷信息,查询到优惠卷信息,判断是否满足秒杀条件

比如时间是否充足,如果时间充足,则进一步判断库存是否足够,如果两者都满足,则扣减库存,创建订单,然后返回订单id,如果有一个条件不满足则直接结束

代码实现

由于涉及到优惠券表和优惠券订单表两张表的dml操作,需要加上@Transactional声明事务

@Transactional@Overridepublic Result seckillVoucher(Long voucherId) {SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);//判断是否开始,开始时间如果在当前时间之后就是尚未开始if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始");}//判断是否结束,结束时间如果在当前时间之前就是已经结束if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束");}//判断库存是否充足if (seckillVoucher.getStock() < 1) {return Result.fail("库存不足");}boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).update();if (!success) {return Result.fail("库存不足");}VoucherOrder voucherOrder = new VoucherOrder();//订单idlong orderId = redisIdWorker.nextId("order");//用户idLong userId = UserHolder.getUser().getId();voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);voucherOrder.setId(orderId);save(voucherOrder);return Result.ok(orderId);}

超卖问题

模拟实现

使用jmeter模拟实现,注意带上请求头authorization,值为登录时的token的key

从数据库的库存中我们可以看到已经出现了超卖现象,库存出现了负数

超卖原因

我们原有的代码是这么写的

if (voucher.getStock() < 1) {// 库存不足return Result.fail("库存不足!");}//5,扣减库存boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).update();if (!success) {//扣减库存return Result.fail("库存不足!");}

假设线程1过来查询库存,判断出来库存大于1,正准备去扣减库存,但是还没有来得及去扣减,此时线程2过来,线程2也去查询库存,发现这个数量一定也大于1,那么这两个线程都会去扣减库存,最终多个线程相当于一起去扣减库存,此时就会出现库存的超卖问题。

解决方案

超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁:而对于加锁,我们通常有两种解决方案:见下图:

悲观锁:

悲观锁可以实现对于数据的串行化执行,比如syn,和lock都是悲观锁的代表,同时,悲观锁中又可以再细分为公平锁,非公平锁,可重入锁,等等

乐观锁:

乐观锁:会有一个版本号,每次操作数据会对版本号+1,再提交回数据时,会去校验是否比之前的版本大1 ,如果大1 ,则进行操作成功,这套机制的核心逻辑在于,如果在操作过程中,版本号只比原来大1 ,那么就意味着操作过程中没有人对他进行过修改,他的操作就是安全的,如果不大1,则数据被修改过,当然乐观锁还有一些变种的处理方式比如cas,即查值进行比对,发现值没有被修改,认为线程安全进行修改值,解决线程安全问题

解决方案实现

采用乐观锁方案,对于优惠券库存我们并需要设置版本号,因为查询到的库存和最后修改数据时再查第二遍库存后,我们只需要将这两次库存量进行比较,就能知道库存是否被修改过即线程是否安全,且为了性能,我们会将修改数据时设置的条件,并不需要两次库存完全相同,只需要在进行修改时,加上库存大于0的条件即可,上面代码只需要修改此处即可

boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).gt("stock",0).update();

开了两百个线程之后,异常率达到完美的50%,同时数据库数据正常

一人一单

优惠卷是为了引流,但是目前的情况是,一个人可以无限制的抢这个优惠卷,所以我们应当增加一层逻辑,让一个用户只能下一个单,而不是让一个用户下多个单

具体操作逻辑如下:比如时间是否充足,如果时间充足,则进一步判断库存是否足够,然后再根据优惠卷id和用户id查询是否已经下过这个订单,如果下过这个订单,则不再下单,否则进行下单

初步实现,在扣减库存前查询订单表,该用户是否已经下过单

 @Transactional@Overridepublic Result seckillVoucher(Long voucherId) {SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);//判断是否开始,开始时间如果在当前时间之后就是尚未开始if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始");}//判断是否结束,结束时间如果在当前时间之前就是已经结束if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束");}//判断库存是否充足if (seckillVoucher.getStock() < 1) {return Result.fail("库存不足");}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("用户已经购买过一次!");}//扣减库存boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).gt("stock",0).update();if (!success) {return Result.fail("库存不足");}VoucherOrder voucherOrder = new VoucherOrder();//订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);voucherOrder.setId(orderId);save(voucherOrder);return Result.ok(orderId);}

 还是出现了一人多张优惠券订单的情况

存在问题:现在的问题还是和之前一样,并发过来,查询数据库,都不存在订单,所以我们还是需要加锁,但是乐观锁比较适合更新数据,而现在是插入数据,所以我们需要使用悲观锁操作,可以直接在方法上直接加上synchronized 锁来解决

这样添加锁,锁的粒度太粗了,在使用锁过程中,控制锁粒度 是一个非常重要的事情,因为如果锁的粒度太大,会导致每个线程进来都会锁住,所以我们需要去控制锁的粒度,可以将用户下单的代码封装成一个方法,对该业务进行上锁,将锁的范围缩小,同时由于spring的事务必须等到锁释放之后才会提交,如果锁释放之后,有别的线程进入下单业务,而此时spring事务尚未提交,这就会造成订单尚未写入数据库,该线程仍会查到无订单,继续进行下单操作无法解决线程安全问题,所以我们要先提交事务才能释放锁,就能避免该问题。

最终实现

由于createVoucherOrder()要受事务控制,要注入IVoucherOrderService拿到代理对象,通过该代理对象调用该方法,事务才能生效,为了使事务提交在释放锁之前,可以将锁直接锁死事务方法。

 @Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Autowiredprivate IVoucherOrderService voucherOrderService;@Overridepublic Result seckillVoucher(Long voucherId) {SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);//判断是否开始,开始时间如果在当前时间之后就是尚未开始if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始");}//判断是否结束,结束时间如果在当前时间之前就是已经结束if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束");}//判断库存是否充足if (seckillVoucher.getStock() < 1) {return Result.fail("库存不足");}Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {return voucherOrderService.createVoucherOrder(voucherId);}}@Transactionalpublic 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("用户已经购买过一次!");}//扣减库存boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {return Result.fail("库存不足");}VoucherOrder voucherOrder = new VoucherOrder();//订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);voucherOrder.setId(orderId);save(voucherOrder);return Result.ok(orderId);}

http://www.yayakq.cn/news/180374/

相关文章:

  • 重庆工程建设招标网官方网站网络推广一般都干啥
  • 成都网站建设联系方式wordpress员工管理系统
  • dw如何用表格来做网站软件开发公司
  • 政务网站建设 紧急通知广州官网建站
  • 如皋市建设局网站福州短视频seo平台
  • 优质服务的网站设计制作百度小程序对网站seo
  • 可视化在线做网站街道网站建设更新汇报
  • 蒙阴建设局网站十大免费logo设计
  • seochinazcom如何快速优化网站排名
  • 书店网页设计图片网店seo
  • 企业网站pv是什么饲料网站源码
  • 请人开发一个网站需要多少钱顺德网站优化公司
  • 网站建设的词wordpress翻页显示404
  • 泉州市服务好的网站设计网站制作厂家
  • 太仓建设工程网站网站动态海报效果怎么做的
  • 苏州街网站建设炫酷网站欣赏
  • 鞍山做网站免费网站建设找云狄
  • 推广网站有哪些方式青浦网站建设
  • 关于加强网站信息建设的通知网站首页样式
  • 黄石规划建设局网站国内网站域名吗
  • 建设虚拟币交易网站潜江网络
  • 重庆制作网站公司简介网站可以个人备案吗
  • tap自助建站wordpress 扁担
  • 网站 报价方案有关网站升级建设的申请书
  • photoshop 做网站电商运营有几大平台
  • 网站建设实习困难线上培训课程
  • 做童装在哪个网站做广告沈阳市建设工程项目管理中心
  • 网站着陆率新校区建设专题网站
  • 建设银行江苏省行网站东家乐装修公司怎么样
  • 广州网站开发服务全国企业工商信息查询官网