ダークホース点評 02 クーポンフラッシュセール (売られすぎ問題)

1. データベーステーブル

2. グローバルに一意なIDを生成する方法

2.1 IDが自動で増えないのはなぜですか?

上記の理由により、グローバル ID ジェネレーターが使用されます。

2.2グローバルIDジェネレーター(redisのオートインクリメント機能を利用)

タイムスタンプは、設定された開始時刻からの秒数です。

シリアル番号は Redis 文字列のキーであり、主にビジネス キーと時刻で構成されます。保存される redis 値は、x 月 x 日 x 年の注文数量であり、シリアル番号 (自己増加) です。

オーダー番号はスプライシングによって形成され、現在のオーダーのオーダー番号になります。

INCR コマンドは文字列に対する操作です。Redis には専用の整数型がないため、キーに格納されている値は、INCR コマンドの実行時に 10 進数の 64 ビット符号付き整数として解釈されます。 

package com.example.heima.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

@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;
    }

    //id构成 : 1位符号位 + 31位时间戳 + 32位序列号
    public long nextId(String keyPrefix){
        //1.生成时间戳,当时时间
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);//时间格式转换
        long timestamp = nowSecond - BEGIN_TIMESTAMP;//计算现在距离开始时间时间差

        //2.生成32位序列号
        //2.1生成订单时间当前日期,精确到天(因为32位最多2^32次方,序列号有上限,不管多久这个key都不变。
        // 所以为了让key随时间变化,加上天数拼接,但是一天很难到达2^32单)
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        //2.2自增长(传入的是string会被redis解释成数字类型)
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        //3,拼接并返回
        return timestamp << 32 | count ;

    }


}

2.2.1 テスト

CountDownLatch の使用法と原理の分析 - Zhihu (zhihu.com)

//线程池500个线程
    private ExecutorService es = Executors.newFixedThreadPool(500);
    @Test
    void testIdWorker(){
        //CountDownLatch可以使一个或多个线程等待其他线程各自执行完毕后再执行。
        CountDownLatch latch = new CountDownLatch(300);  //构造300的计数器
        //任务
        Runnable task = ()->{
            for(int i=0;i < 100;i++){
                long id =redisIdWorker.nextId("order");
                System.out.println("id = " + id);
            }
            latch.countDown(); //对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。
        };
        long begin = System.currentTimeMillis();
        //300个线程执行任务
        for(int i=0;i < 300;i ++){
            es.submit(task);
        }

        try {
            //等待latch计数器归零再开始执行该线程
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        long end = System.currentTimeMillis();

        System.out.println("time = " + (end-begin) );

    }

実行後、splicing という名前の順序カウンターが redis に書き込まれます。 

正常に生成されました

3. クーポンを追加する

基本的に、フロントエンドはクーポンの json データを送信し、データベース処理のためのコントロールとサービスを呼び出します。

3.1 テーブル構造

2 つのテーブルはクーポン ID によってリンクされています

フラッシュセールクーポン

 すべてのクーポン(通常クーポンおよびフラッシュセールクーポンを含む)

3.2 クーポンの追加

post経由でjsonを渡すことで情報を取得できます

4. クーポン購入プロセスを実装する

対応クーポン購入サービスの流れ

5. マルチスレッドのフラッシュ販売と過剰販売の問題を解決する

5.1 マルチユーザーリクエストソフトウェアをシミュレートする

このビデオでは、JMeter ソフトウェアを使用して負荷をテストし、スレッド数を設定し、特定の URL への多数のリクエストを同時に開始して、マルチユーザー シナリオをシミュレートできます。

JMeter パフォーマンス テスト、完全な入門チュートリアル - CSDN ブログ

5.2 売られすぎ問題

販売する前に判定を行い、判定が成功した場合のみ在庫を差し引く関数を呼び出します 在庫が1の場合、2つのスレッドが同時に在庫があると判断し、1つ差し引くそれぞれ在庫がある場合、在庫は -1 となり、売られすぎの問題が発生します。

5.3 解決策(ロック、楽観的ロック、悲観的ロック)

悲観的ロックは従来のロックであり、効率に大きな影響を与えるため、楽観的ロックを実装する方法を検討します。

楽観的ロックは変更に基づいてバージョン番号を決定する必要があるため、更新にのみ適用できます。マルチスレッド クエリの場合は、悲観的ロックのみを使用できます。

5.4 楽観的ロック

楽観的ロックは変更に基づいてバージョン番号を決定する必要があるため、更新にのみ適用できます。マルチスレッド クエリの場合は、悲観的ロックのみを使用できます。

5.4.1 バージョン番号の方式

5.4.2CAS方式(インベントリをバージョン番号として使用比較して切り替える)

5.5 楽観的ロックの実装 ---CAS 方式

在庫を差し引く前に、在庫を追加して、以前と同じかどうかを確認します。

しかし、これでは成功率が低すぎるという新たな問題が発生し、100 個のスレッドが同時に動作した場合、変更できるのは 1 つのスレッドのみで、残りのスレッドはバージョン変更により終了してしまい、エラー率が増加します。

そのため、コード上で在庫が以前と同じかどうかを判断する必要はなく、在庫が0以上であれば販売可能です。

5.5 セグメントロック

楽観的ロックでは失敗率が高くなるため、成功率を高めるために、分割ロックを使用して 100 個のクーポンを 10 個のテーブルに分散し、ユーザーは 10 個のテーブルに行ってそれぞれクーポンを取得できます。このようにして、1 回の購入とロックで、ロックする必要があるのは 1 つのテーブルだけであり、他のテーブルの購入には影響しないため、成功率が大幅に向上します。

おすすめ

転載: blog.csdn.net/m0_50973548/article/details/134982825