Redis Combat 9 - グローバルに一意の ID

クーポンを発行する場合、店舗ごとにクーポンを発行することができますが、ユーザーが購入を急ぐ際に、クーポンテーブルのIDがデータベースの自己増加IDを使用していると、以下のような問題が発生します。

1: イドの法則は明白すぎてブラシがかかりやすい

2: データ量が多い場合、単一のテーブルデータに制限されます

デメリットシナリオ分析:

ID ルールのシナリオ: ID に明白すぎるルールがある場合、ユーザーや取引相手は、モールで 1 日に販売された注文数など、機密情報の一部を簡単に推測できますが、これは明らかに不適切です。

単一テーブルの制限: モールの規模がますます大きくなるにつれて、単一の mysql テーブルの容量は 500W を超えてはなりません。データ量が大きすぎると、データベースとテーブルを分割する必要がありますが、テーブルを分割した後は、論理的には同じテーブルであるため、ID が同じになることはできません。そのため、ID の一意性を確保する必要があります。

グローバルIDジェネレーター

グローバル ID ジェネレーターは、分散システム内でグローバルに一意な ID を生成するために使用されるツールです。一般に、次の特性を秘匿する必要があります。

独自性、高可用性、増分性、セキュリティ、高パフォーマンス

グローバルに一意な ID 生成戦略:

UUID、Redis 自動インクリメント、スノーフレーク アルゴリズム、データベース自動インクリメント

Redis の自己インクリメント ID 戦略:

1: 1 日に 1 つのキー、注文を数えるのに便利。

2: ID はすべてタイムスタンプ + カウンタです

実戦:Redisをベースに他の情報をつなぎ合わせてグローバルにユニークなIDを実現

グローバルに一意な ID は長いタイプであり、タイムスタンプは特定の時点に基づいています。例えば2022.11.26 23:00:00から69年間使用可能となります。

考え方 1: 現在時刻の秒を取得する:

考察 2: タイムスタンプはどのように計算されますか?

現在時刻からの秒数 - 初期時刻からの秒数を使用

思考3: シリアル番号を設定するにはどうすればよいですか?

Redisのsetnxコマンドを使用して、現在の年、月、日を追加するのが最善です

思考 4: どのように接続するか?

返す必要があるのはlong型であるため、文字列のスプライシングを使用する場合は変換する必要があります。また、文字列のスプライシングを使用すると、同時実行の量が多い場合、パフォーマンスの問題も発生することにも注意してください。では、どのように対処すればよいのでしょうか?

注: グローバル一意 ID の形式をもう一度見てみましょう。上の図に示すように、合計 64 ビットがあり、そのうち符号ビットが 1、タイムスタンプが 31 ビットであることがわかります。シリアル番号は 32 桁ですが、何か見つかりましたか? タイムスタンプを 32 ビット左に移動すると (シリアル番号が 32 ビットであるため、シリアル番号のためのスペースを空けるために位置を左に移動します)、それは符号ビット + タイムスタンプになりますか?

1: コンピューターの左への最速の移動は x<< 桁であることもわかっています。

2: コンピュータ | または計算: ビットごとの OR 演算 "|" についても知っておく必要があります。

以上より、ビット演算の通し番号の次は、通し番号の値であることが分かります。シリアル番号が何であれ、それだけです。

したがって、スプライシング コードは次のとおりであることがわかります。

最終コード:

org.springframework.data.redis.core.StringRedisTemplate をインポートします。
org.springframework.stereotype.Component をインポートします。
 
インポート java.time.LocalDateTime; 
java.time.ZoneOffsetをインポートします。
インポート java.time.format.DateTimeFormatter; 
 
/** 
 * @author 凯哥Java 
 * @description に基づく Redis 实现 62 位の完全局唯一 ID 
 * @company 
 * */ 
@Component 
public class RedisIdWorker { 
 
    private static Final long BEGIN_TIMESTAMP = 1669503600L; 
 
 
    プライベート最終 StringRedisTemplate stringRedisTemplate; 
 
    /** 
     * シーケンス番号の位数
     */ 
    private static Final int COUNT_BITS = 32; 
 
    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate; 
    }
 
    /** 
     * ID の取得
     * @param kyePrefix 
     * @return 
     */ 
    public long nextId(String kyePrefix){ 
        //1: 生成タイムスタンプ = 現在のタイムスタンプ - 開始タイムスタンプ
        LocalDateTime now = LocalDateTime.now(); 
        long nowSecond = now.toEpochSecond (ZoneOffset.UTC); 
        long timeStamp = nowSecond - BEGIN_TIMESTAMP; 
 
        //2: シリアル番号を生成します。sexNx を使用します。それに現在の年、月、日を追加します。
        // 現在の時刻、月、日を取得します。
        String date = now.format (DateTimeFormatter.ofPattern("yyyy:MM:dd")); 
        //開始 32 ビット シリアル番号
        long no = stringRedisTemplate.opsForValue().increment("icr:"+kyePrefix+":"+date);  
        //3: スプライシングリターン
        return timeStamp << COUNT_BITS|no; 
 
    } 
    /**
     * 指定された時刻までのミリ秒を取得
     * @param args 
     */ 
    public static void main(String[] args) { 
        LocalDateTime time = LocalDateTime.of(2022,11,26,23,00,00); 
        long Second = time.toEpochSecond
    テスト:
        マルチスレッド
とカウントダウンラッチを使用する
private ExecutorService executorService = Executors.newFixedThreadPool(500); 
    @Resource 
    private RedisIdWorker redisIdWorker; 
    @Test 
    public void RedisIdWorkerTest() throws InterruptedExce
オプション{ 
        CountDownLatch ラッチ = new CountDownLatch(300);
 
 
 
 
        実行可能なタスク = ()->{
            for(int i = 0;i<100;i++){
                長い ID = redisIdWorker.nextId("myorder"); 
                System.out.println(id); 
            ラッチ
            .カウントダウン(); 
        }; 
 
        長い開始 = System.currentTimeMillis(); 
        for(int i = 0;i< 300;i++){ 
            executorService.submit(タスク); 
        ラッチ
        .await(); 
        long endTime = System.currentTimeMillis(); 
        System.out.println("所要時間:"+(endTime-begin)); 
    }

おすすめ

転載: blog.csdn.net/kaizi_1992/article/details/128784616