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 つのテーブルだけであり、他のテーブルの購入には影響しないため、成功率が大幅に向上します。