ゼロからjava分散プライマリ・キー編:分散id
9632 ワード
目次
一: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ファイル
(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)メリット
ルールが定められ、一意性が保証され、以前のシナリオと競合しない限り、古いアイテムを変更できます.
四:雪花アルゴリズム
一: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");
}
}