ゼロからjava分散プライマリ・キー編:分散id


目次
 
一:Mysql自己増加ソート
二:Mysqlのプライマリ・キーのソート
三.分散プライマリ・キー・ポリシー
1.クラスタまたは分散型データベースのプライマリ・キーの増加によるリスク
2.分散キー方向
3.データベースレベル方案(一)ステップの設定
(1)シナリオ
(2)メリット
(3)欠点
4.データベースレベル方案(二)クエリーMAX(ID)
(1)シナリオ
(2)メリット
(3)欠点
5.データベースレベルシナリオ(3)-idテーブルを1枚ずつ
6.データベースレベル方案(四)-redis
(1)シナリオ
(2)メリット
(3)欠点
7.応用レベル(一)-UUID
(1)シナリオ
(2)メリット
(3)欠点
8.応用レベル(二)-その他のアルゴリズム
(1)シナリオ
(2)メリット
四:雪花アルゴリズム
一:Mysql自己増加ソート
日常の開発では,ほとんどの場合Mysqlの自己増加プライマリ・キーを用いることに慣れている.しかし、このスキームは、クラスタまたは分散環境が発生すると、プライマリ・キーが一意ではないという問題が、単体の単一マシン・アプリケーションで使用される.
次に,Mysqlの自己増加プライマリ・キーから,分散プライマリ・キー生成ポリシーの解決について述べる.
二:Mysqlのプライマリ・キーのソート
プライマリ・キーのソートといえばエンジンに言及せざるを得ません.Mysqlの間のソートはエンジンによって決まるからです.Mysqlでは、私たちがよく使うデータベースエンジンは2つあります:MyISAMとInnoDB、多くの開発シーンでは後者を使用しています.多分覚えてるよ4以降、MysqlのデフォルトエンジンはMyISAMからInnoDBに変わりました.
この2つのエンジンの違いを簡単に比較してみましょう.
(1)InnoDb:サポート対象,行レベルロック,全文インデックス.
(2)MyISAM:ビッグデータの読み取りが速い(MysISAMはselect count(*))、トランザクション杭ロックなどはサポートされていないため.
2つのエンジンでデータを読み込む順番を簡単に比較してみましょう
(1)InnoDB:プライマリ・キーに従ってソート(プライマリ・キーはインデックス・ルールであるため)
(2)MyISAM:ヒープ構造によるソート
ps:MyISAMに添削があると順番が乱れます
次に、2つのエンジンの使用シーンを簡単にマークします.
(1)InnoDB:ほとんどの開発シーンでは、現在使用しているMYSQLはデフォルトエンジンです
(2)MyISAM:単純な注文、操作ログなど、物事の支出を必要としないもの
2つのエンジンの並べ替えの原理を簡単に比較してみましょう
(1)InnoDB:クラスタインデックステーブル
クラスタインデックステーブル、テーブルデータ、プライマリ・キーの1期間格納、1レベル・ノードの行データの格納、2レベル・ノードのプライマリ・キー値の格納.そのため、私たちはよく時計を建てて黙秘を指定します.
認める.I/O密集型アプリケーションのパフォーマンスが大幅に向上しました.
(2)MyISAM:ヒープ構造
デフォルトでは、変更などをせずに挿入順になります.
三.分散プライマリ・キー・ポリシー
ここのポリシーは分布式、InnoDBエンジンに対しては聞かないでください.
1.クラスタまたは分散型データベースのプライマリ・キーの増加によるリスク
データベース・クラスタまたは分散型データベースの場合、Mysqlに従ってプライマリ・キーを増加させると、プライマリ・キーが一意でないという問題が発生します.たとえばA,Bの2つのテーブルがクラスタ配置され,データがランダムに2つのテーブルに挿入され,クエリ時に2つのテーブルに同じプライマリ・キーの問題が必ず発生する.
2.分散キー方向
閲覧した資料と結びつけて、現在分布式のプライマリ・キーを解決するには2つの方向がある:応用レベルとデータベースレベル.
アプリケーションレベル:プライマリ・キーを生成する主なソリューションとしてアプリケーションが使用します.一般に、UUIDとタイムスタンプを一意の方向として参照する.
≪データベース・レベル|Database Level|emdw≫:Redisなどのストレージ・データベース自体またはグローバル・データベースに基づいています.
3.データベースレベル方案(一)ステップの設定
(1)シナリオ
データベースの自己拡張ステップを変更します.たとえば、Aデータベースは1から2(1,3,5,7,9,11...)ずつ増加します.Bデータベースは2から2からステップ長(すなわち2,4,6....)を開始する.具体的なステップは、データベースの個数によって決定されます.
修正my.cnfファイル
set global auto_increment_increment=1; —         
show global variables; —      global  
show global variables like ‘%test%’ —     test    global  

(2)メリット
シンプルで汎用性が高い
(3)欠点
ステップ長を決定するには、ライブラリの数が既知である必要があります.拡張が必要な項目には不適切です.
4.データベースレベル方案(二)クエリーMAX(ID)
(1)シナリオ
挿入するたびに、1つのデータベースに対して最大のIdをクエリーします.
(2)メリット
単一テーブル、小同時性の場合、一意性を保証
(3)欠点
パフォーマンスの消費量は増加しましたが、大きくはありませんが、高同時性の場合は少し窮屈です.また、アプリケーションレベルでライブラリ分割テーブルを作成すると、クエリーが特に面倒になります.
5.データベースレベルシナリオ(3)-idテーブルを1枚ずつ
シナリオの答えは同じで、idテーブルで取るたびに、挿入式で+1をトリガするだけです.
6.データベースレベル方案(四)-redis
(1)シナリオ
idの(k,v)をredisで確立し、読み取りごとに自動的に+1をトリガする
(2)メリット
reidsのグローバルキャッシュは、分散システムのプライマリ・キーの一意性を保証します.
(3)欠点
Redisダウンタイムの場合、(k,v)が失われ、再起動してもエラーが発生します.(つまり、RedisまたはRedisクラスタがダウンタイムしないことを保証する)リスクが高い
7.応用レベル(一)-UUID
(1)シナリオ
この案は言うまでもないでしょう.
(2)メリット
UUIDはidの独立性を完全に保証する
(3)欠点
UUIDが長すぎて、データベースに挿入されて規則がないため、データベースが物理的に並べ替えられ、パフォーマンスに影響します.
8.応用レベル(二)-その他のアルゴリズム
(1)シナリオ
アルゴリズムを使用して、タイムスタンプとデバイス番号を使用して一意の(主流のスキーム)を生成します.次に、雪のアルゴリズムを紹介します.
(2)メリット
ルールが定められ、一意性が保証され、以前のシナリオと競合しない限り、古いアイテムを変更できます.
四:雪花アルゴリズム
package commons.utils.fbsId;
 
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
 
import java.net.Inet4Address;
import java.net.UnknownHostException;
 
/**
 * Twitter_Snowflake
* SnowFlake ( - ):
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
* 1 , long Java , , 0, 1, id , 0
* 41 ( ), ,41 , ( - ) * ), , id , ( IdWorker startTime )。41 , 69 , T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
* 10 , 1024 , 5 datacenterId 5 workerId
* 12 , ,12 ( , ) 4096 ID
* 64 , Long 。
* SnowFlake , , ID ( ID ID ), , ,SnowFlake 26 ID 。 */ public class FbsId { // ==============================Fields=========================================== /** (2015-01-01) */ private final long twepoch = 1489111610226L; /** id */ private final long workerIdBits = 5L; /** id */ private final long dataCenterIdBits = 5L; /** id, 31 ( ) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** id, 31 */ private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); /** id */ private final long sequenceBits = 12L; /** ID 12 */ private final long workerIdShift = sequenceBits; /** id 17 (12+5) */ private final long dataCenterIdShift = sequenceBits + workerIdBits; /** 22 (5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits; /** , 4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** ID(0~31) */ private long workerId; /** ID(0~31) */ private long dataCenterId; /** (0~4095) */ private long sequence = 0L; /** ID */ private long lastTimestamp = -1L; private static FbsId idWorker; static { idWorker = new FbsId(getWorkId(),getDataCenterId()); } //==============================Constructors===================================== /** * * @param workerId ID (0~31) * @param dataCenterId ID (0~31) */ public FbsId(long workerId, long dataCenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId)); } if (dataCenterId > maxDataCenterId || dataCenterId < 0) { throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", maxDataCenterId)); } this.workerId = workerId; this.dataCenterId = dataCenterId; } // ==============================Methods========================================== /** * ID ( ) * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); // ID , if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } // , if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; // if (sequence == 0) { // , timestamp = tilNextMillis(lastTimestamp); } } // , else { sequence = 0L; } // ID lastTimestamp = timestamp; // 64 ID return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence; } /** * , * @param lastTimestamp ID * @return */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * * @return ( ) */ protected long timeGen() { return System.currentTimeMillis(); } private static Long getWorkId(){ try { String hostAddress = Inet4Address.getLocalHost().getHostAddress(); int[] ints = StringUtils.toCodePoints(hostAddress); int sums = 0; for(int b : ints){ sums += b; } return (long)(sums % 32); } catch (UnknownHostException e) { // , return RandomUtils.nextLong(0,31); } } private static Long getDataCenterId(){ int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName()); int sums = 0; for (int i: ints) { sums += i; } return (long)(sums % 32); } /** * * * @return */ public static String generateId(){ long id = idWorker.nextId(); return id+""; } //==============================Test============================================= /** */ public static void main(String[] args) { System.out.println(System.currentTimeMillis()); long startTime = System.nanoTime(); for (int i = 0; i < 500000; i++) { String id = FbsId.generateId(); System.out.println(id); } System.out.println((System.nanoTime()-startTime)/1000000+"ms"); } }