Redis クーポンのフラッシュ セールの問題を解決する方法

王林
リリース: 2023-05-28 14:52:17
転載
1223 人が閲覧しました

1 クーポンフラッシュセール機能の実装

Redis クーポンのフラッシュ セールの問題を解決する方法

##注文する際には、1. フラッシュセールが開始されているか終了しているか、2. フラッシュセールが開始されているかどうかの 2 点を判断する必要があります。在庫は十分です

したがって、ビジネス ロジックは次のとおりです

1. クーポン ID からクーポン情報を取得します

2. フラッシュ セールが開始されているかどうかを判断し、開始されていない場合は、エラーメッセージが返される

#3. フラッシュセールが終了したかどうかを判断し、終了した場合はエラーメッセージを返す

#4. フラッシュセール期間内であれば、在庫があるかどうかを判断するで十分です

5. 十分な場合は、在庫を差し引きます

6. 注文情報を作成し、クーポン注文テーブルに保存します

6.1 注文IDを保存します

6.2 ユーザー ID を保存します

6.3 クーポン ID を保存します

7 .返品注文 ID

コードの実装: (サービス層の実装class)Redis クーポンのフラッシュ セールの問題を解決する方法

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);
    }
}
ログイン後にコピー

2 売られすぎの問題 (キーポイント)

まず、上記のコードを高い同時実行性で実行してみます。 (jmx ツールを使用)

下の図は、200 のスレッドが作成され、クーポン リクエストが瞬時に発行されたことを示しています

しかし、見てみると、集計レポートでは、異常値は 45.5% にすぎないことがわかりますが、論理的には 50% であるはずです (在庫に 100 個のアイテムがあり、200 件のリクエストがここに送信されたため) Redis クーポンのフラッシュ セールの問題を解決する方法

在庫番号を見てください。それは -9Redis クーポンのフラッシュ セールの問題を解決する方法

です。注文も 109 に追加されています。これは明らかに売られすぎの問題です。 Redis クーポンのフラッシュ セールの問題を解決する方法

それでは、なぜこの問題が発生するのでしょうか?

画像を見て話してください:

通常のプロセスに従います。つまり、スレッド 1 が在庫を確認し、在庫を差し引きます。このとき、スレッド 2 が在庫をクエリします。再度在庫を差し引くので問題ありません。

売られすぎの問題は、注文 1 が在庫をチェックした後、在庫が 1 であることが判明するが、差し引かれる前にスレッド 2 も在庫をチェックすることにあります。これも 1 であることが判明し、減算も行われました (同時実行性の高いシナリオで)Redis クーポンのフラッシュ セールの問題を解決する方法

これにより、売られすぎの問題が発生しました。 Redis クーポンのフラッシュ セールの問題を解決する方法

この種の同時実行性の高い問題の最も一般的な解決策は次のとおりです:lock~

ただし、ロックには悲観的ロックと楽観的ロックが含まれます。

悲観的ロックとは、簡単に言うと、スレッドが確実に発生すると考え、操作の前に全員がロックを取得します。実行が終了したら、次の実行が順番に実行されます (シリアル実行)

楽観的ロック: 各データ変更の前に限り、スレッドの安全性を確保するために他のスレッドがデータを変更したかどうかが判断される限り、楽観的です (スレッドの安全性は決して起こらないと考えています)。

悲観的ロックは比較的単純ですが、ここでは楽観的ロックが実装されています。 Redis クーポンのフラッシュ セールの問題を解決する方法

オプティミスティック ロックの鍵は、前のクエリで取得したデータが変更されているかどうかを判断することです。一般的な方法は 2 つあります。

注意: 左側のテーブル内のデータはすべて変更されています。スレッド 1 の実行後のデータ Oh~

1。バージョン番号メソッド

# は、インベントリのクエリのステップにバージョン番号を追加することです。データを変更するたびに、バージョン番号 + 1 と判定する where 条件を追加します。バージョン番号は変更前と同じですか?

#こうすることでスレッド セーフを実現できます~

Redis クーポンのフラッシュ セールの問題を解決する方法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);
    }
}
ログイン後にコピー
より大きいかどうかを判断する where 条件を追加します。

以上がRedis クーポンのフラッシュ セールの問題を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート