#3. フラッシュセールが終了したかどうかを判断し、終了した場合はエラーメッセージを返す
#4. フラッシュセール期間内であれば、在庫があるかどうかを判断するで十分です5. 十分な場合は、在庫を差し引きます6. 注文情報を作成し、クーポン注文テーブルに保存します6.1 注文IDを保存します6.2 ユーザー ID を保存します6.3 クーポン ID を保存します7 .返品注文 ID
コードの実装: (サービス層の実装class)
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服务实现类 * </p> * * @author 虎哥 * @since 2021-12-22 */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.获取优惠券信息 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判断是否已经开始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒杀尚未开始!"); } //3.判断是否已经结束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒杀已经结束了!"); } //4.判断库存是否充足 if (voucher.getStock() < 1) { Result.fail("库存不充足!"); } //5.扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId) .update(); if (!success){ Result.fail("库存不充足!"); } //6. 创建订单 VoucherOrder voucherOrder = new VoucherOrder(); //6.1添加订单id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //6.2添加用户id Long userId = UserHolder.getUser().getId(); voucherOrder.setUserId(userId); //6.3添加优惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //7.返回订单id return Result.ok(orderId); } }
しかし、見てみると、集計レポートでは、異常値は 45.5% にすぎないことがわかりますが、論理的には 50% であるはずです (在庫に 100 個のアイテムがあり、200 件のリクエストがここに送信されたため)
在庫番号を見てください。それは -9
です。注文も 109 に追加されています。これは明らかに売られすぎの問題です。
それでは、なぜこの問題が発生するのでしょうか? 画像を見て話してください: 通常のプロセスに従います。つまり、スレッド 1 が在庫を確認し、在庫を差し引きます。このとき、スレッド 2 が在庫をクエリします。再度在庫を差し引くので問題ありません。売られすぎの問題は、注文 1 が在庫をチェックした後、在庫が 1 であることが判明するが、差し引かれる前にスレッド 2 も在庫をチェックすることにあります。これも 1 であることが判明し、減算も行われました (同時実行性の高いシナリオで)
これにより、売られすぎの問題が発生しました。
この種の同時実行性の高い問題の最も一般的な解決策は次のとおりです:lock~ただし、ロックには悲観的ロックと楽観的ロックが含まれます。 悲観的ロックとは、簡単に言うと、スレッドが確実に発生すると考え、操作の前に全員がロックを取得します。実行が終了したら、次の実行が順番に実行されます (シリアル実行)楽観的ロック: 各データ変更の前に限り、スレッドの安全性を確保するために他のスレッドがデータを変更したかどうかが判断される限り、楽観的です (スレッドの安全性は決して起こらないと考えています)。
悲観的ロックは比較的単純ですが、ここでは楽観的ロックが実装されています。
オプティミスティック ロックの鍵は、前のクエリで取得したデータが変更されているかどうかを判断することです。一般的な方法は 2 つあります。注意: 左側のテーブル内のデータはすべて変更されています。スレッド 1 の実行後のデータ Oh~1。バージョン番号メソッド # は、インベントリのクエリのステップにバージョン番号を追加することです。データを変更するたびに、バージョン番号 + 1 と判定する where 条件を追加します。バージョン番号は変更前と同じですか?#こうすることでスレッド セーフを実現できます~2.CAS メソッド
##これは、バージョン番号は必要ありません。データベースを変更した後に where 条件を追加するだけで、インベントリが変更前のインベントリであるかどうかを判断します。 # 売られすぎ問題を解決するためのコード実装: 最終分析では、在庫を差し引くときに、在庫が 0//5.1扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id" , voucherId).gt("stock" ,0) .update();
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服务实现类 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.获取优惠券信息 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判断是否已经开始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒杀尚未开始!"); } //3.判断是否已经结束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒杀已经结束了!"); } //4.判断库存是否充足 if (voucher.getStock() < 1) { Result.fail("库存不充足!"); } //5.扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("库存不充足!"); } //6. 创建订单 VoucherOrder voucherOrder = new VoucherOrder(); //6.1添加订单id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //6.2添加用户id Long userId = UserHolder.getUser().getId(); voucherOrder.setUserId(userId); //6.3添加优惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //7.返回订单id return Result.ok(orderId); } }
以上がRedis クーポンのフラッシュ セールの問題を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。