Springcloudの実用的な自社開発分散IDジェネレーター

1. 背景

        日常の開発では、システム内のさまざまなデータを 一意に表現するためにIDを使用する必要があります。たとえば、ユーザー IDは1人にのみ対応し、プロダクトIDは 1つの製品にのみ対応し、注文 IDは1人にのみ 対応します。 1 つの注文にのみ対応します。私たちの現実社会にも様々なIDが存在しますが 、例えばIDカードの IDは 一人の人間に対応しており、簡単に言うと データを一意に識別するものです。通常はデータベースの自動インクリメントされる主キーをデータIDとして使用します が、大量の場合には分散やサブデータベース、テーブルサブデータベースなどの手法を導入して対応することが多いです。明らかに、データがサブデータベースおよびサブテーブルになった後でも、データまたはメッセージの一部を識別するには一意の ID が必要であり 、データベースの自己増加 ID では 需要を満たすことができなくなります。このとき、世界的にユニークなIDを生成できるシステムが 非常に必要となります。要約すると、 ビジネス システムにおける ID 番号の要件は何でしょうか?
        グローバル一意性: ID 番号が重複しないこと 一意の識別子であるため、これは最も基本的な要件です。
        傾向増加 単調増加 : 次の ID が前の ID より大きくなければなりません
        情報セキュリティ ID が連続していると、悪意のあるユーザーに情報を盗まれやすくなります 指定されたURLを順番にダウンロードするだけですが、 注文番号であればさらに危険です 競合他社が当社の日々の情報を直接知ることができます注文量。したがって、一部のアプリケーション シナリオでは、ID を 不規則または不規則にする必要がある場合があります。
        同時に、企業は ID 番号自体の要件に加えて、 ID生成システムの可用性についても非常に高い要件を持っています。ID 生成システムが不安定で、 ID生成システムに大きく依存している 場合を想像してください。 、注文生成などのキーアクションは実行できません。したがって、ID生成システムには、平均遅延とTP999遅延ができるだけ低く、可用性が 5 9秒でありQPSが高い必要があります

2: 一般的な方法の紹介

        2.1 UUID

        UUID (Universally Unique Identifier) の標準形式には、ハイフンで 5 つのセグメントに分割された 32 桁の 16 進数が含まれ、8-4-4-4-12 の形式の 36 文字が含まれます。例: 550e8400-e29b-41d4-a716 - 446655440000。

        2.1.1 利点

                非常に高いパフォーマンス: ローカルで生成され、ネットワークを消費しません。

        2.1.2 欠点

                保存が容易ではない: UUID は 16 バイト、128 ビットと長すぎ、通常は 36 ビット文字列で表されるため、多くのシナリオには適用できません。

                情報セキュリティの脆弱性: MAC アドレスに基づいて UUID を生成するアルゴリズムにより MAC アドレスが漏洩する可能性があり、この脆弱性はかつて Melissa ウイルスの作成者の場所を見つけるために使用されました。

                ID を主キーとして使用すると、特定の環境でいくつかの問題が発生します。たとえば、DB の主キーのシナリオでは、UUID は非常に不適切です。

                ① MySQL は主キーをできるだけ短くすることを公式に推奨しています [4] が、36 文字の UUID は要件を満たしていません。

                ② MySQL インデックスに不利: InnoDB エンジン下でデータベースの主キーとして使用すると、UUID の乱れによりデータの場所が頻繁に変更され、パフォーマンスに重大な影響を与える可能性があります。クラスター化インデックスは MySQL InnoDB エンジンで使用されます。ほとんどの RDBMS はインデックス データの保存に B ツリー データ構造を使用するため、主キーを選択するときに書き込みパフォーマンスを確保するために、順序付けされた主キーを使用するように努める必要があります。

                jdk に付属の UUID を直接使用できます。元の生成された UUID にはアンダースコアが付いています。必要ない場合は、自分で削除できます。たとえば、次のコード:

public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            String rawUUID = UUID.randomUUID().toString();
            //去除“-”
            String uuid = rawUUID.replaceAll("-", "");
            System.out.println(uuid);
        }
    }
        2.2 Snowflake アルゴリズムとその派生アルゴリズム

                このソリューションは大まかに言うと、名前空間を分割してIDを生成するアルゴリズムです(UUIDもカウントされます。UUIDは比較的一般的であるため、個別に分析されます) SnowflakeはTwitterのオープンソースの分散ID生成アルゴリズムです。Snowflake は、64 ビットを複数のセグメントに分割して、マシンや時間などをマークします。たとえば、snowflake の 64 ビットは、次の図のように表されます。

        ビット 0: 符号ビット (正と負のマーク)、常に 0、役に立たず、気にしません。ビット 1 ~ 41: 合計 41 ビット、タイムスタンプを表すために使用され、単位はミリ秒、2^41 ミリ秒 (約 69 年) をサポート可能 ビット 42 ~
        52: 合計 10 ビット、一般に、最初の 5 ビットはコンピュータを表しますルーム ID、最後の 5 桁はマシン ID を表し (実際のプロジェクトの実際の状況に応じて調整可能)、異なるクラスター/コンピューター ルーム内のノードを区別できるため、32 の IDC を表すことができ、各 IDC 32 台のマシンを搭載できます。
        ビット 53 ~ 64: 合計 12 ビットで、シリアル番号を表すために使用されます。シリアル番号は自動インクリメント値で、1 台のマシンが 1 ミリ秒あたりに生成できる ID の最大数 (2^12 = 4096) を表します。これは、1 台のマシンが 1 ミリ秒あたり最大 4096 個の一意の ID を生成できることを意味します。

        理論的には、スノーフレーク ソリューションの QPS は約 409.6w/s です。この割り当て方法により、どのミリ秒内にどの IDC のどのマシンによって生成された ID も異なることが保証されます。

        3 つの 分散 ID マイクロサービス

        上記の分析から、各ソリューションには独自の長所と短所があることがわかります。ここではMeituan Leaf ソリューションを参照して独自の分散 ID を実装します。

        3.1 Meituan Leaf ソリューションの導入

        元の MySQL ソリューションでは、ID を取得するために毎回データベースの読み取りと書き込みを行う必要があり、データベースに大きな負荷がかかりました。一括取得に変更し、毎回1セグメント(ステップによりサイズが決まります)の数値セグメントの値を取得します。使用後、データベースにアクセスして新しい番号セグメントを取得すると、データベースへの負担が大幅に軽減されます。

       3.1.1 利点

        リーフ サービスは簡単に直線的に拡張でき、そのパフォーマンスはほとんどのビジネス シナリオを完全にサポートできます。ID 番号は 64 ビットの数値で、8 バイトずつ増加傾向にあり、上記のデータベース ストレージの主キー要件を満たしています。
        高い耐災害性: Leaf サービスは内部番号セグメント キャッシュを備えており、DB がダウンした場合でも、短時間であれば正常に外部にサービスを提供できます。
        max_id のサイズをカスタマイズできるため、従来の ID 方式からの業務移行に非常に便利です。


       3.1.2 欠点

        ID 番号は十分にランダムではないため、発行された番号の数に関する情報が漏洩する可能性があり、安全ではありません。
        TP999 のデータは変動が大きく、番号セグメントを使い果たした場合でも、新しい番号セグメントを取得する際にデータベース更新の I/O 待ちが発生し、tg999 データにスパイクが発生することがあります。
        DB が停止すると、システム全体が使用できなくなる可能性があります。

        3.1.3 最適化

        Leaf が番号セグメントを取得するタイミングは、番号セグメントが消費されたときです。つまり、番号セグメントの臨界点での ID 発行時間は、次に DB から番号セグメントが取得されるタイミングと、その要求に応じて決まります。この期間中にアクセスがあった場合、DB 番号セグメントが取得されないため、スレッドがブロックされる可能性もあります。DB を要求するネットワークと DB のパフォーマンスが安定している場合は、システムへの影響はほとんどありませんが、DB を取得するときにネットワークが不安定になったり、DB で遅いクエリが発生したりすると、DB の応答時間が低下します。システム全体の速度が低下します。
        このため、DB が数値セグメントを取得するプロセスがノンブロッキングであり、DB が数値セグメントを取得しているときにリクエスト スレッドをブロックする必要がないことを望んでいます。特定の時点で、次の番号セグメントがメモリに非同期的にロードされます。番号範囲を更新する前に、番号範囲がなくなるまで待つ必要はありません。これを行うと、システムの TP999 インジケーターを大幅に減らすことができます。
        ダブル バッファー方式を使用すると、Leaf サービス内に 2 つの数値セグメント バッファー セグメントが存在します。現在の番号セグメントの 10% が発行されたときに、次の番号セグメントが更新されていない場合は、次の番号セグメントを更新するために別の更新スレッドが開始されます。現在の番号セグメントがすべて配信された後、次の番号セグメントの準備ができている場合は、次の番号セグメントが現在のセグメントに切り替えられてから配信され、このサイクルが繰り返されます。一般に、サービスのピーク時にセグメント長をコール QPS (10 分) の 600 倍に設定することをお勧めします。これにより、DB がダウンした場合でも、Leaf は影響を受けることなく 10 ~ 20 分間コールを送信し続けることができます。リクエストが届くたびに、次の番号セグメントのステータスが決定され、この番号セグメントが更新されるため、時折発生するネットワーク ジッターが次の番号セグメントの更新に影響を与えることはありません。

4:分散ID実戦

        データベース構成

CREATE DATABASE qiyu_live_common CHARACTER set utf8mb3 
COLLATE=utf8_bin;


CREATE TABLE `t_id_generate_config` (
 `id` int NOT NULL AUTO_INCREMENT COMMENT '主键 id',
 `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE 
utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
 `next_threshold` bigint DEFAULT NULL COMMENT '当前 id 所在阶段的阈
值',
 `init_num` bigint DEFAULT NULL COMMENT '初始化值',
 `current_start` bigint DEFAULT NULL COMMENT '当前 id 所在阶段的开始
值',
 `step` int DEFAULT NULL COMMENT 'id 递增区间',
 `is_seq` tinyint DEFAULT NULL COMMENT '是否有序(0 无序,1 有序)',
 `id_prefix` varchar(60) CHARACTER SET utf8mb4 COLLATE 
utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务前缀码,如果没有则返回
时不携带',
 `version` int NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时
间',
 `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 
COLLATE=utf8mb4_unicode_ci;

        レコードの挿入

INSERT INTO `t_id_generate_config` (`id`, `remark`, 
`next_threshold`, `init_num`, `current_start`, `step`, `is_seq`, 
`id_prefix`, `version`, `create_time`, `update_time`)
VALUES
 (1, '用户 id 生成策略', 10050, 10000, 10000, 50, 0, 
'user_id', 0, '2023-05-23 12:38:21', '2023-05-23 23:31:45');

        Springboot プロジェクトと構成ファイルをビルドする

        1. 2 つの Maven を作成し、Maven の依存関係をインポートします。

        Maven の依存関係をインポートする

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${qiyu-mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.idea</groupId>
            <artifactId>qiyu-live-id-generate-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

        設定ファイル

spring:
  application:
    name: qiyu-live-id-generate-provider
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #    访问主库
    url: jdbc:mysql://192.168.1.128:8808/qiyu_live_common?useUnicode=true&characterEncoding=utf8
    username: root
    password: root

        次のモジュールでは、基本的な構成ポリシーの列挙と外部インターフェイスが生成されます。

       ID 生成戦略列挙型クラスを作成する

package org.qiyu.live.id.generate.enums;

/**
 * @Author idea
 * @Date: Created in 17:55 2023/6/13
 * @Description
 */
public enum IdTypeEnum {

    USER_ID(1,"用户id生成策略");

    int code;
    String desc;

    IdTypeEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

        外部インターフェースメソッドの生成

package org.qiyu.live.id.generate.interfaces;

/**
 * @Author idea
 * @Date: Created in 19:45 2023/5/25
 * @Description
 */
public interface IdGenerateRpc {
    /**
     * 获取有序id
     *
     * @param id
     * @return
     */
    Long getSeqId(Integer id);

    /**
     * 获取无序id
     *
     * @param id
     * @return
     */
    Long getUnSeqId(Integer id);

}

        次に、ID生成モジュールに実装します。

        データベース po クラスを作成します (ここにデータベース ID 構成ポリシー テーブルがあります)。

package com.laoyang.id.dao.po;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.Date;

/**
 * @Author idea
 * @Date: Created in 19:59 2023/5/23
 * @Description
 */
@TableName("t_id_gengrate_config")
public class IdGeneratePO {

    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * id备注描述
     */
    private String remark;

    /**
     * 初始化值
     */
    private long initNum;

    /**
     * 步长
     */
    private int step;

    /**
     * 是否是有序的id
     */
    private int isSeq;

    /**
     * 当前id所在阶段的开始值
     */
    private long currentStart;

    /**
     * 当前id所在阶段的阈值
     */
    private long nextThreshold;

    /**
     * 业务代码前缀
     */
    private String idPrefix;

    /**
     * 乐观锁版本号
     */
    private int version;

    private Date createTime;

    private Date updateTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public long getInitNum() {
        return initNum;
    }

    public void setInitNum(long initNum) {
        this.initNum = initNum;
    }

    public int getStep() {
        return step;
    }

    public void setStep(int step) {
        this.step = step;
    }

    public long getCurrentStart() {
        return currentStart;
    }

    public void setCurrentStart(long currentStart) {
        this.currentStart = currentStart;
    }

    public long getNextThreshold() {
        return nextThreshold;
    }

    public void setNextThreshold(long nextThreshold) {
        this.nextThreshold = nextThreshold;
    }

    public String getIdPrefix() {
        return idPrefix;
    }

    public void setIdPrefix(String idPrefix) {
        this.idPrefix = idPrefix;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public int getIsSeq() {
        return isSeq;
    }

    public void setIsSeq(int isSeq) {
        this.isSeq = isSeq;
    }

    @Override
    public String toString() {
        return "IdGeneratePO{" +
                "id=" + id +
                ", remark='" + remark + '\'' +
                ", initNum=" + initNum +
                ", step=" + step +
                ", isSeq=" + isSeq +
                ", currentStart=" + currentStart +
                ", nextThreshold=" + nextThreshold +
                ", idPrefix='" + idPrefix + '\'' +
                ", version=" + version +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                '}';
    }
}

        マッパー マッピング クラスを生成します。挿入中にオプティミスティック ロックが追加されることに注意してください。この SQL に注意してください。

package com.laoyang.id.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.laoyang.id.dao.po.IdGeneratePO;

import java.util.List;

/**
 * @Author idea
 * @Date: Created in 19:47 2023/5/25
 * @Description
 */
@Mapper
public interface IdGenerateMapper extends BaseMapper<IdGeneratePO> {

    @Update("update t_id_gengrate_config set next_threshold=next_threshold+step," +
            "current_start=current_start+step,version=version+1 where id =#{id} and version=#{version}")
    int updateNewIdCountAndVersion(@Param("id")int id,@Param("version")int version);

    @Select("select * from t_id_gengrate_config")
    List<IdGeneratePO> selectAll();
}

        サービスの下に bo クラスを作成し、順序付き ID オブジェクトと順序なし ID オブジェクトを生成します。

        

package com.laoyang.id.service.bo;

import java.util.concurrent.atomic.AtomicLong;

/**
 * @Author idea
 * @Date: Created in 20:00 2023/5/25
 * @Description 有序id的BO对象
 */
public class LocalSeqIdBO {

    private int id;
    /**
     * 在内存中记录的当前有序id的值
     */
    private AtomicLong currentNum;

    /**
     * 当前id段的开始值
     */
    private Long currentStart;
    /**
     * 当前id段的结束值
     */
    private Long nextThreshold;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public AtomicLong getCurrentNum() {
        return currentNum;
    }

    public void setCurrentNum(AtomicLong currentNum) {
        this.currentNum = currentNum;
    }

    public Long getCurrentStart() {
        return currentStart;
    }

    public void setCurrentStart(Long currentStart) {
        this.currentStart = currentStart;
    }

    public Long getNextThreshold() {
        return nextThreshold;
    }

    public void setNextThreshold(Long nextThreshold) {
        this.nextThreshold = nextThreshold;
    }
}
package com.laoyang.id.service.bo;

import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @Author idea
 * @Date: Created in 20:32 2023/5/26
 * @Description 无序id的BO对象
 */
public class LocalUnSeqIdBO {

    private int id;
    /**
     * 提前将无序的id存放在这条队列中
     */
    private ConcurrentLinkedQueue<Long> idQueue;
    /**
     * 当前id段的开始值
     */
    private Long currentStart;
    /**
     * 当前id段的结束值
     */
    private Long nextThreshold;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public ConcurrentLinkedQueue<Long> getIdQueue() {
        return idQueue;
    }

    public void setIdQueue(ConcurrentLinkedQueue<Long> idQueue) {
        this.idQueue = idQueue;
    }

    public Long getCurrentStart() {
        return currentStart;
    }

    public void setCurrentStart(Long currentStart) {
        this.currentStart = currentStart;
    }

    public Long getNextThreshold() {
        return nextThreshold;
    }

    public void setNextThreshold(Long nextThreshold) {
        this.nextThreshold = nextThreshold;
    }
}

        順序付き ID と順序なし ID を生成するサービス クラスを生成します。

package com.laoyang.id.service;

/**
 * @Author idea
 * @Date: Created in 19:58 2023/5/25
 * @Description
 */
public interface IdGenerateService {

    /**
     * 获取有序id
     *
     * @param id
     * @return
     */
    Long getSeqId(Integer id);

    /**
     * 获取无序id
     *
     * @param id
     * @return
     */
    Long getUnSeqId(Integer id);
}

        順序付き ID メソッドと順序なし ID メソッドを実装します (ここが重要で、主にアトミック クラス、一部の同期操作など、スレッド プールを使用します)。

package com.laoyang.id.service.impl;

import jakarta.annotation.Resource;
import com.laoyang.id.dao.mapper.IdGenerateMapper;
import com.laoyang.id.dao.po.IdGeneratePO;
import com.laoyang.id.service.IdGenerateService;
import com.laoyang.id.service.bo.LocalSeqIdBO;
import com.laoyang.id.service.bo.LocalUnSeqIdBO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @Author idea
 * @Date: Created in 19:58 2023/5/25
 * @Description
 */
@Service
public class IdGenerateServiceImpl implements IdGenerateService, InitializingBean {

    @Resource
    private IdGenerateMapper idGenerateMapper;

    private static final Logger LOGGER = LoggerFactory.getLogger(IdGenerateServiceImpl.class);
    private static Map<Integer, LocalSeqIdBO> localSeqIdBOMap = new ConcurrentHashMap<>();
    private static Map<Integer, LocalUnSeqIdBO> localUnSeqIdBOMap = new ConcurrentHashMap<>();
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 16, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("id-generate-thread-" + ThreadLocalRandom.current().nextInt(1000));
                    return thread;
                }
            });
    private static final float UPDATE_RATE = 0.50f;
    private static final int SEQ_ID = 1;
    private static Map<Integer, Semaphore> semaphoreMap = new ConcurrentHashMap<>();

    @Override
    public Long getUnSeqId(Integer id) {
        if (id == null) {
            LOGGER.error("[getSeqId] id is error,id is {}", id);
            return null;
        }
        LocalUnSeqIdBO localUnSeqIdBO = localUnSeqIdBOMap.get(id);
        if (localUnSeqIdBO == null) {
            LOGGER.error("[getUnSeqId] localUnSeqIdBO is null,id is {}", id);
            return null;
        }
        Long returnId = localUnSeqIdBO.getIdQueue().poll();
        if (returnId == null) {
            LOGGER.error("[getUnSeqId] returnId is null,id is {}", id);
            return null;
        }
        this.refreshLocalUnSeqId(localUnSeqIdBO);
        return returnId;
    }

    /**
     *
     * @param id 传的是对应的业务id
     * @return
     */
    @Override
    public Long getSeqId(Integer id) {
        if (id == null) {
            LOGGER.error("[getSeqId] id is error,id is {}", id);
            return null;
        }
        LocalSeqIdBO localSeqIdBO = localSeqIdBOMap.get(id);
        if (localSeqIdBO == null) {
            LOGGER.error("[getSeqId] localSeqIdBO is null,id is {}", id);
            return null;
        }
        this.refreshLocalSeqId(localSeqIdBO);
        long returnId = localSeqIdBO.getCurrentNum().incrementAndGet();
        if (returnId > localSeqIdBO.getNextThreshold()) {
            //同步去刷新 可能是高并发下还未更新本地数据
            LOGGER.error("[getSeqId] id is over limit,id is {}", id);
            return null;
        }
        return returnId;
    }

    /**
     * 刷新本地有序id段
     *
     * @param localSeqIdBO
     */
    private void refreshLocalSeqId(LocalSeqIdBO localSeqIdBO) {
        long step = localSeqIdBO.getNextThreshold() - localSeqIdBO.getCurrentStart();
        if (localSeqIdBO.getCurrentNum().get() - localSeqIdBO.getCurrentStart() > step * UPDATE_RATE) {
            Semaphore semaphore = semaphoreMap.get(localSeqIdBO.getId());
            if (semaphore == null) {
                LOGGER.error("semaphore is null,id is {}", localSeqIdBO.getId());
                return;
            }
            boolean acquireStatus = semaphore.tryAcquire();
            if (acquireStatus) {
                LOGGER.info("开始尝试进行本地id段的同步操作");
                //异步进行同步id段操作
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            IdGeneratePO idGeneratePO = idGenerateMapper.selectById(localSeqIdBO.getId());
                            tryUpdateMySQLRecord(idGeneratePO);
                        } catch (Exception e) {
                            LOGGER.error("[refreshLocalSeqId] error is ", e);
                        } finally {
                            semaphoreMap.get(localSeqIdBO.getId()).release();
                            LOGGER.info("本地有序id段同步完成,id is {}", localSeqIdBO.getId());
                        }
                    }
                });
            }
        }
    }

    /**
     * 刷新本地无序id段
     *
     * @param localUnSeqIdBO
     */
    private void refreshLocalUnSeqId(LocalUnSeqIdBO localUnSeqIdBO) {
        long begin = localUnSeqIdBO.getCurrentStart();
        long end = localUnSeqIdBO.getNextThreshold();
        long remainSize = localUnSeqIdBO.getIdQueue().size();
        //如果使用剩余空间不足25%,则进行刷新
        if ((end - begin) * 0.35 > remainSize) {
            LOGGER.info("本地无序id段同步开始,id is {}", localUnSeqIdBO.getId());
            Semaphore semaphore = semaphoreMap.get(localUnSeqIdBO.getId());
            if (semaphore == null) {
                LOGGER.error("semaphore is null,id is {}", localUnSeqIdBO.getId());
                return;
            }
            boolean acquireStatus = semaphore.tryAcquire();
            if (acquireStatus) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            IdGeneratePO idGeneratePO = idGenerateMapper.selectById(localUnSeqIdBO.getId());
                            tryUpdateMySQLRecord(idGeneratePO);
                        } catch (Exception e) {
                            LOGGER.error("[refreshLocalUnSeqId] error is ", e);
                        } finally {
                            semaphoreMap.get(localUnSeqIdBO.getId()).release();
                            LOGGER.info("本地无序id段同步完成,id is {}", localUnSeqIdBO.getId());
                        }
                    }
                });
            }
        }
    }

    //bean初始化的时候会回调到这里
    @Override
    public void afterPropertiesSet() throws Exception {
        List<IdGeneratePO> idGeneratePOList = idGenerateMapper.selectAll();
        for (IdGeneratePO idGeneratePO : idGeneratePOList) {
            LOGGER.info("服务刚启动,抢占新的id段");
            tryUpdateMySQLRecord(idGeneratePO);
            semaphoreMap.put(idGeneratePO.getId(), new Semaphore(1));
        }
    }

    /**
     * 更新mysql里面的分布式id的配置信息,占用相应的id段
     * 同步执行,很多的网络IO,性能较慢
     *
     * @param idGeneratePO
     */
    private void tryUpdateMySQLRecord(IdGeneratePO idGeneratePO) {
        int updateResult = idGenerateMapper.updateNewIdCountAndVersion(idGeneratePO.getId(), idGeneratePO.getVersion());
        if (updateResult > 0) {
            localIdBOHandler(idGeneratePO);
            return;
        }
        //重试进行更新
        for (int i = 0; i < 3; i++) {
            idGeneratePO = idGenerateMapper.selectById(idGeneratePO.getId());
            updateResult = idGenerateMapper.updateNewIdCountAndVersion(idGeneratePO.getId(), idGeneratePO.getVersion());
            if (updateResult > 0) {
                localIdBOHandler(idGeneratePO);
                return;
            }
        }
        throw new RuntimeException("表id段占用失败,竞争过于激烈,id is " + idGeneratePO.getId());
    }

    /**
     * 专门处理如何将本地ID对象放入到Map中,并且进行初始化的
     *
     * @param idGeneratePO
     */
    private void localIdBOHandler(IdGeneratePO idGeneratePO) {
        long currentStart = idGeneratePO.getCurrentStart();
        long nextThreshold = idGeneratePO.getNextThreshold();
        long currentNum = currentStart;
        if (idGeneratePO.getIsSeq() == SEQ_ID) {
            LocalSeqIdBO localSeqIdBO = new LocalSeqIdBO();
            AtomicLong atomicLong = new AtomicLong(currentNum);
            localSeqIdBO.setId(idGeneratePO.getId());
            localSeqIdBO.setCurrentNum(atomicLong);
            localSeqIdBO.setCurrentStart(currentStart);
            localSeqIdBO.setNextThreshold(nextThreshold);
            localSeqIdBOMap.put(localSeqIdBO.getId(), localSeqIdBO);
        } else {
            LocalUnSeqIdBO localUnSeqIdBO = new LocalUnSeqIdBO();
            localUnSeqIdBO.setCurrentStart(currentStart);
            localUnSeqIdBO.setNextThreshold(nextThreshold);
            localUnSeqIdBO.setId(idGeneratePO.getId());
            long begin = localUnSeqIdBO.getCurrentStart();
            long end = localUnSeqIdBO.getNextThreshold();
            List<Long> idList = new ArrayList<>();
            for (long i = begin; i < end; i++) {
                idList.add(i);
            }
            //将本地id段提前打乱,然后放入到队列中
            Collections.shuffle(idList);
            ConcurrentLinkedQueue<Long> idQueue = new ConcurrentLinkedQueue<>();
            idQueue.addAll(idList);
            localUnSeqIdBO.setIdQueue(idQueue);
            localUnSeqIdBOMap.put(localUnSeqIdBO.getId(), localUnSeqIdBO);
        }
    }
}

        最後にスタートアップクラスを作成します

package com.laoyang.id;

import jakarta.annotation.Resource;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import com.laoyang.id.service.IdGenerateService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import java.util.HashSet;

/**
 * @Author idea
 * @Date: Created in 19:45 2023/5/25
 * @Description
 */
@SpringBootApplication
public class IdGenerateApplication implements CommandLineRunner {

    private static final Logger LOGGER = LoggerFactory.getLogger(IdGenerateApplication.class);

    @Resource
    private IdGenerateService idGenerateService;

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(IdGenerateApplication.class);
        springApplication.setWebApplicationType(WebApplicationType.NONE);
        springApplication.run(args);
    }

    @Override
    public void run(String... args) throws Exception {
        HashSet<Long> idSet = new HashSet<>();
        for (int i = 0; i < 1500; i++) {
            Long id = idGenerateService.getSeqId(1);
            System.out.println(id);
            idSet.add(id);
        }
        System.out.println(idSet.size());
    }
}

        最終的には、出力がコンソールに表示されます。

おすすめ

転載: blog.csdn.net/qq_67801847/article/details/133254215