1.snowflake簡介
互聯網快速發展的今天,分布式應用系統已經見怪不怪,在分布式系統中,我們需要各種各樣的ID,既然是ID那么必然是要保證全局唯一,除此之外,不同當業務還需要不同的特性,比如像並發巨大的業務要求ID生成效率高,吞吐大;比如某些銀行類業務,需要按每日日期制定交易流水號;又比如我們希望用戶的ID是隨機的,無序的,純數字的,且位數長度是小於10位的。等等,不同的業務場景需要的ID特性各不一樣,於是,衍生了各種ID生成器,但大多數利用數據庫控制ID的生成,性能受數據庫並發能力限制,那么有沒有一款不需要依賴任何中間件(如數據庫,分布式緩存服務等)的ID生成器呢?本着取之於開源,用之於開源的原則,今天,特此介紹Twitter開源的一款分布式自增ID算法snowflake,並附上算法原理推導和演算過程!
snowflake算法是一款本地生成的(ID生成過程不依賴任何中間件,無網絡通信),保證ID全局唯一,並且ID總體有序遞增,性能每秒生成300w+。
-
1bit:一般是符號位,不做處理
-
41bit:用來記錄時間戳,這里可以記錄69年,如果設置好起始時間比如今年是2018年,那么可以用到2089年,到時候怎么辦?要是這個系統能用69年,我相信這個系統早都重構了好多次了。
-
10bit:10bit用來記錄機器ID,總共可以記錄1024台機器,一般用前5位代表數據中心ID,后面5位是某個數據中心的機器ID
-
12bit:循環位,用來對同一個毫秒之內產生不同的ID,12位可以最多記錄4095個,也就是在同一個機器同一毫秒最多記錄4095個,多余的需要進行等待下毫秒。
<p>總體來說,在工作節點達到1024頂配的場景下,SnowFlake算法在同一毫秒內最多可以生成多少個全局唯一ID呢?這是一個簡單的乘法:</p> <p>同一毫秒的ID數量 = 1024 X 4096 = 4194304</p> <p>400多萬個ID,這個數字在絕大多數並發場景下都是夠用的。</p> <p>snowflake 算法中,第三個部分是工作機器ID,可以結合上一節的命名方法,並通過Zookeeper管理workId,免去手動頻繁修改集群節點,去配置機器ID的麻煩。</p> </li>
上面只是一個將64bit划分的標准,當然也不一定這么做,可以根據不同業務的具體場景來划分,比如下面給出一個業務場景:
-
服務目前QPS10萬,預計幾年之內會發展到百萬。
-
當前機器三地部署,上海,北京,深圳都有。
-
當前機器10台左右,預計未來會增加至百台。
這個時候我們根據上面的場景可以再次合理的划分62bit,QPS幾年之內會發展到百萬,那么每毫秒就是千級的請求,目前10台機器那么每台機器承擔百級的請求,為了保證擴展,后面的循環位可以限制到1024,也就是2^10,那么循環位10位就足夠了。
機器三地部署我們可以用3bit總共8來表示機房位置,當前的機器10台,為了保證擴展到百台那么可以用7bit 128來表示,時間位依然是41bit,那么還剩下64-10-3-7-41-1 = 2bit,還剩下2bit可以用來進行擴展。
適用場景:當我們需要無序不能被猜測的ID,並且需要一定高性能,且需要long型,那么就可以使用我們雪花算法。比如常見的訂單ID,用雪花算法別人就發猜測你每天的訂單量是多少。
上面定義了雪花算法的實現,在nextId中是我們生成雪花算法的關鍵。
2.防止時鍾回撥
因為機器的原因會發生時間回撥,我們的雪花算法是強依賴我們的時間的,如果時間發生回撥,有可能會生成重復的ID,在我們上面的nextId中我們用當前時間和上一次的時間進行判斷,如果當前時間小於上一次的時間那么肯定是發生了回撥,普通的算法會直接拋出異常,這里我們可以對其進行優化,一般分為兩個情況:
-
如果時間回撥時間較短,比如配置5ms以內,那么可以直接等待一定的時間,讓機器的時間追上來。
-
如果時間的回撥時間較長,我們不能接受這么長的阻塞等待,那么又有兩個策略:
-
直接拒絕,拋出異常,打日志,通知RD時鍾回滾。
-
利用擴展位,上面我們討論過不同業務場景位數可能用不到那么多,那么我們可以把擴展位數利用起來了,比如當這個時間回撥比較長的時候,我們可以不需要等待,直接在擴展位加1。2位的擴展位允許我們有3次大的時鍾回撥,一般來說就夠了,如果其超過三次我們還是選擇拋出異常,打日志。
通過上面的幾種策略可以比較的防護我們的時鍾回撥,防止出現回撥之后大量的異常出現。下面是修改之后的代碼,這里修改了時鍾回撥的邏輯:
最終代碼如下:
-
-
import org.apache.commons.lang3.RandomUtils;
-
import org.apache.commons.lang3.StringUtils;
-
import org.slf4j.Logger;
-
import org.slf4j.LoggerFactory;
-
-
import java.lang.management.ManagementFactory;
-
import java.net.InetAddress;
-
import java.net.NetworkInterface;
-
import java.net.UnknownHostException;
-
import java.text.SimpleDateFormat;
-
import java.util.*;
-
import java.util.concurrent.ConcurrentHashMap;
-
import java.util.concurrent.CountDownLatch;
-
import java.util.concurrent.TimeUnit;
-
import java.util.concurrent.locks.LockSupport;
-
-
-
/**
-
* 分布式全局ID雪花算法解決方案
-
*
-
* 防止時鍾回撥
-
* 因為機器的原因會發生時間回撥,我們的雪花算法是強依賴我們的時間的,如果時間發生回撥,
-
* 有可能會生成重復的ID,在我們上面的nextId中我們用當前時間和上一次的時間進行判斷,
-
* 如果當前時間小於上一次的時間那么肯定是發生了回撥,
-
* 普通的算法會直接拋出異常,這里我們可以對其進行優化,一般分為兩個情況:
-
* 如果時間回撥時間較短,比如配置5ms以內,那么可以直接等待一定的時間,讓機器的時間追上來。
-
* 如果時間的回撥時間較長,我們不能接受這么長的阻塞等待,那么又有兩個策略:
-
* 直接拒絕,拋出異常,打日志,通知RD時鍾回滾。
-
* 利用擴展位,上面我們討論過不同業務場景位數可能用不到那么多,那么我們可以把擴展位數利用起來了,
-
* 比如當這個時間回撥比較長的時候,我們可以不需要等待,直接在擴展位加1。
-
* 2位的擴展位允許我們有3次大的時鍾回撥,一般來說就夠了,如果其超過三次我們還是選擇拋出異常,打日志。
-
* 通過上面的幾種策略可以比較的防護我們的時鍾回撥,防止出現回撥之后大量的異常出現。下面是修改之后的代碼,這里修改了時鍾回撥的邏輯:
-
*/
-
public
class SnowflakeIdFactory {
-
-
private
static final Logger
log = LoggerFactory.getLogger(SnowflakeIdFactory.class);
-
-
/**
-
* EPOCH是服務器第一次上線時間點, 設置后不允許修改
-
* 2018/9/29日,從此時開始計算,可以用到2089年
-
*/
-
private
static
long EPOCH =
1538211907857L;
-
-
/**
-
* 每台workerId服務器有3個備份workerId, 備份workerId數量越多, 可靠性越高, 但是可部署的sequence ID服務越少
-
*/
-
private
static final
long BACKUP_COUNT =
3;
-
-
/**
-
* worker id 的bit數,最多支持8192個節點
-
*/
-
private
static final
long workerIdBits =
5L;
-
/**
-
* 數據中心標識位數
-
*/
-
private
static final
long dataCenterIdBits =
5L;
-
/**
-
* 序列號,支持單節點最高每毫秒的最大ID數4096
-
* 毫秒內自增位
-
*/
-
private
static final
long sequenceBits =
12L;
-
-
/**
-
* 機器ID偏左移12位
-
*/
-
private
static final
long workerIdShift = sequenceBits;
-
-
/**
-
* 數據中心ID左移17位(12+5)
-
*/
-
private
static final
long dataCenterIdShift = sequenceBits + workerIdBits;
-
-
/**
-
* 時間毫秒左移22位(5+5+12)
-
*/
-
private
static final
long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
-
/**
-
* sequence掩碼,確保sequnce不會超出上限
-
* 最大的序列號,4096
-
* -1 的補碼(二進制全1)右移12位, 然后取反
-
* 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095)
-
*/
-
private
static final
long sequenceMask =
-1L ^ (
-1L << sequenceBits);
-
-
//private final static long sequenceMask = ~(-1L << sequenceBits);
-
/**
-
* 實際的最大workerId的值 結果是31,8091
-
* workerId原則上上限為1024, 但是需要為每台sequence服務預留BACKUP_AMOUNT個workerId,
-
* (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數)
-
*/
-
//private static final long maxWorkerId = (1L << workerIdBits) / (BACKUP_COUNT + 1);
-
-
//原來代碼 -1 的補碼(二進制全1)右移13位, 然后取反
-
private
static final
long maxWorkerId =
-1L ^ (
-1L << workerIdBits);
-
//private final static long maxWorkerId = ~(-1L << workerIdBits);
-
-
/**
-
* 支持的最大數據標識id,結果是31
-
*/
-
private
static final
long maxDataCenterId =
-1L ^ (
-1L << dataCenterIdBits);
-
/**
-
* long workerIdBits = 5L;
-
* -1L 的二進制: 1111111111111111111111111111111111111111111111111111111111111111
-
* -1L<<workerIdBits = -32 ,二進制: 1111111111111111111111111111111111111111111111111111111111100000
-
* workerMask= -1L ^ -32 = 31, 二進制: 11111
-
*/
-
private
static
long workerMask=
-1L ^ (
-1L << workerIdBits);
-
//進程編碼
-
private
long processId =
1L;
-
private
static
long processMask=
-1L ^ (
-1L << dataCenterIdBits);
-
-
-
/**
-
* 工作機器ID(0~31)
-
* snowflake算法給workerId預留了10位,即workId的取值范圍為[0, 1023],
-
* 事實上實際生產環境不大可能需要部署1024個分布式ID服務,
-
* 所以:將workerId取值范圍縮小為[0, 511],[512, 1023]
-
* 這個范圍的workerId當做備用workerId。workId為0的備用workerId是512,
-
* workId為1的備用workerId是513,以此類推
-
*/
-
private
static
long workerId;
-
-
/**
-
* 數據中心ID(0~31)
-
*/
-
private
long dataCenterId;
-
-
/**
-
* 當前毫秒生成的序列
-
*/
-
private
long sequence =
0L;
-
-
/**
-
* 上次生成ID的時間戳
-
*/
-
private
long lastTimestamp =
-1L;
-
-
private
long extension =
0L;
-
private
long maxExtension =
0L;
-
-
/**
-
* 保留workerId和lastTimestamp, 以及備用workerId和其對應的lastTimestamp
-
*/
-
private
static Map<Long, Long> workerIdLastTimeMap =
new ConcurrentHashMap<>();
-
-
/**
-
* 最大容忍時間, 單位毫秒, 即如果時鍾只是回撥了該變量指定的時間, 那么等待相應的時間即可;
-
* 考慮到sequence服務的高性能, 這個值不易過大
-
*/
-
private
static final
long MAX_BACKWARD_MS =
3;
-
private
static SnowflakeIdFactory idWorker;
-
-
static {
-
idWorker =
new SnowflakeIdFactory();
-
}
-
-
-
-
static {
-
Calendar calendar = Calendar.getInstance();
-
calendar.
set(
2018, Calendar.NOVEMBER,
1);
-
calendar.
set(Calendar.HOUR_OF_DAY,
0);
-
calendar.
set(Calendar.MINUTE,
0);
-
calendar.
set(Calendar.SECOND,
0);
-
calendar.
set(Calendar.MILLISECOND,
0);
-
// EPOCH是服務器第一次上線時間點, 設置后不允許修改
-
EPOCH = calendar.getTimeInMillis();
-
-
// 初始化workerId和其所有備份workerId與lastTimestamp
-
// 假設workerId為0且BACKUP_AMOUNT為4, 那么map的值為: {0:0L, 256:0L, 512:0L, 768:0L}
-
// 假設workerId為2且BACKUP_AMOUNT為4, 那么map的值為: {2:0L, 258:0L, 514:0L, 770:0L}
-
/* for (int i = 0; i<= BACKUP_COUNT; i++){
-
workerIdLastTimeMap.put(workerId + (i * maxWorkerId), 0L);
-
}*/
-
}
-
-
//成員類,IdGenUtils的實例對象的保存域
-
private
static
class SnowflakeIdGenHolder {
-
private
static final SnowflakeIdFactory instance =
new SnowflakeIdFactory();
-
}
-
//外部調用獲取IdGenUtils的實例對象,確保不可變
-
public static SnowflakeIdFactory getInstance(){
-
return SnowflakeIdGenHolder.instance;
-
}
-
-
/**
-
* 靜態工具類
-
*
-
* @return
-
*/
-
public static Long generateId(){
-
long id = idWorker.nextId();
-
return id;
-
}
-
-
//初始化構造,無參構造有參函數,默認節點都是0
-
public SnowflakeIdFactory(){
-
//this(0L, 0L);
-
this.dataCenterId = getDataCenterId(maxDataCenterId);
-
//獲取機器編碼
-
this.workerId = getWorkerId(dataCenterId, maxWorkerId);
-
}
-
-
/**
-
* 構造函數
-
* @param workerId 工作ID (0~31)
-
* @param dataCenterId 數據中心ID (0~31)
-
*/
-
public SnowflakeIdFactory(long workerId, long dataCenterId) {
-
if (workerId > maxWorkerId || workerId <
0) {
-
throw
new IllegalArgumentException(String.format(
"worker Id can't be greater than %d or less than 0", maxWorkerId));
-
}
-
if (dataCenterId > maxDataCenterId || dataCenterId <
0) {
-
throw
new IllegalArgumentException(String.format(
"datacenter Id can't be greater than %d or less than 0", maxDataCenterId));
-
}
-
this.workerId = workerId;
-
this.dataCenterId = dataCenterId;
-
}
-
-
/**
-
* 獲取帶自定義前綴的全局唯一編碼
-
*/
-
public String getStrCodingByPrefix(String prefix){
-
Long ele =
this.nextId();
-
return prefix + ele.toString();
-
}
-
-
/**
-
* 獲得下一個ID (該方法是線程安全的)
-
* 在單節點上獲得下一個ID,使用Synchronized控制並發,而非CAS的方式,
-
* 是因為CAS不適合並發量非常高的場景。
-
*
-
* 考慮時鍾回撥
-
* 缺陷: 如果連續兩次時鍾回撥, 可能還是會有問題, 但是這種概率極低極低
-
* @return
-
*/
-
public synchronized long nextId() {
-
long currentTimestamp = timeGen();
-
// 當發生時鍾回撥時
-
if (currentTimestamp < lastTimestamp){
-
// 如果時鍾回撥在可接受范圍內, 等待即可
-
long offset = lastTimestamp - currentTimestamp;
-
if ( offset <= MAX_BACKWARD_MS){
-
try {
-
//睡(lastTimestamp - currentTimestamp)ms讓其追上
-
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(offset));
-
//時間偏差大小小於5ms,則等待兩倍時間
-
//wait(offset << 1);
-
//Thread.sleep(waitTimestamp);
-
-
currentTimestamp = timeGen();
-
//如果時間還小於當前時間,那么利用擴展字段加1
-
//或者是采用拋異常並上報
-
if (currentTimestamp < lastTimestamp) {
-
//擴展字段
-
//extension += 1;
-
//if (extension > maxExtension) {
-
//服務器時鍾被調整了,ID生成器停止服務.
-
throw
new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - currentTimestamp));
-
//}
-
}
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
else {
-
//擴展字段
-
/*extension += 1;
-
if (extension > maxExtension) {
-
//服務器時鍾被調整了,ID生成器停止服務.
-
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - currentTimestamp));
-
}*/
-
tryGenerateKeyOnBackup(currentTimestamp);
-
}
-
}
-
//對時鍾回撥簡單處理
-
/* if (currentTimestamp < lastTimestamp) {
-
//服務器時鍾被調整了,ID生成器停止服務.
-
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - currentTimestamp));
-
}*/
-
-
// 如果和最后一次請求處於同一毫秒, 那么sequence+1
-
if (lastTimestamp == currentTimestamp) {
-
// 如果當前生成id的時間還是上次的時間,那么對sequence序列號進行+1
-
sequence = (sequence +
1) & sequenceMask;
-
if (sequence ==
0) {
-
//自旋等待到下一毫秒
-
currentTimestamp = waitUntilNextTime(lastTimestamp);
-
}
-
//判斷是否溢出,也就是每毫秒內超過4095,當為4096時,與sequenceMask相與,sequence就等於0
-
/*if (sequence == sequenceMask) {
-
-
// 當前毫秒生成的序列數已經大於最大值,那么阻塞到下一個毫秒再獲取新的時間戳
-
currentTimestamp = this.waitUntilNextTime(lastTimestamp);
-
}*/
-
-
}
else {
-
// 如果是一個更近的時間戳, 那么sequence歸零
-
sequence =
0L;
-
}
-
// 更新上次生成id的時間戳
-
lastTimestamp = currentTimestamp;
-
-
// 更新map中保存的workerId對應的lastTimestamp
-
//workerIdLastTimeMap.put(this.workerId, lastTimestamp);
-
-
if (
log.isDebugEnabled()) {
-
log.debug(
"{}-{}-{}",
new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS").format(
new Date(lastTimestamp)), workerId, sequence);
-
}
-
-
// 進行移位操作生成int64的唯一ID
-
//時間戳右移動23位
-
long timestamp = (currentTimestamp - EPOCH) << timestampLeftShift;
-
-
//workerId 右移動10位
-
long workerId =
this.workerId << workerIdShift;
-
-
//dataCenterId 右移動(sequenceBits + workerIdBits = 17位)
-
long dataCenterId =
this.dataCenterId << dataCenterIdShift;
-
return timestamp | dataCenterId | workerId | sequence;
-
}
-
-
/**
-
* 嘗試在workerId的備份workerId上生成
-
* 核心優化代碼在方法tryGenerateKeyOnBackup()中,BACKUP_COUNT即備份workerId數越多,
-
* sequence服務避免時鍾回撥影響的能力越強,但是可部署的sequence服務越少,
-
* 設置BACKUP_COUNT為3,最多可以部署1024/(3+1)即256個sequence服務,完全夠用,
-
* 抗時鍾回撥影響的能力也得到非常大的保障。
-
* @param currentMillis 當前時間
-
*/
-
private long tryGenerateKeyOnBackup(long currentMillis){
-
// 遍歷所有workerId(包括備用workerId, 查看哪些workerId可用)
-
for (Map.Entry<Long, Long> entry:workerIdLastTimeMap.entrySet()){
-
this.workerId = entry.getKey();
-
// 取得備用workerId的lastTime
-
Long tempLastTime = entry.getValue();
-
lastTimestamp = tempLastTime==null?
0L:tempLastTime;
-
-
// 如果找到了合適的workerId
-
if (lastTimestamp<=currentMillis){
-
return lastTimestamp;
-
}
-
}
-
-
// 如果所有workerId以及備用workerId都處於時鍾回撥, 那么拋出異常
-
throw
new IllegalStateException(
"Clock is moving backwards, current time is "
-
+currentMillis+
" milliseconds, workerId map = " + workerIdLastTimeMap);
-
}
-
-
/**
-
* 阻塞到下一個毫秒,直到獲得新的時間戳
-
* @param lastTimestamp 上次生成ID的時間截
-
* @return 當前時間戳
-
*/
-
protected long waitUntilNextTime(long lastTimestamp) {
-
long timestamp = timeGen();
-
while (timestamp <= lastTimestamp) {
-
timestamp = timeGen();
-
}
-
return timestamp;
-
}
-
-
protected long timeGen() {
-
return System.currentTimeMillis();
-
}
-
-
/**
-
* 獲取WorkerId
-
* @param dataCenterId
-
* @param maxWorkerId
-
* @return
-
*/
-
protected static long getWorkerId(long dataCenterId, long maxWorkerId) {
-
StringBuffer mpid =
new StringBuffer();
-
mpid.append(dataCenterId);
-
String name = ManagementFactory.getRuntimeMXBean().getName();
-
if (!name.isEmpty()) {
-
// GET jvmPid
-
mpid.append(name.split(
"@")[
0]);
-
}
-
// MAC + PID 的 hashcode 獲取16個低位
-
return (mpid.toString().hashCode() &
0xffff) % (maxWorkerId +
1);
-
}
-
-
/**
-
* 獲取機器編碼 用來做數據ID
-
* 數據標識id部分 通常不建議采用下面的MAC地址方式,
-
* 因為用戶通過破解很容易拿到MAC進行破壞
-
*/
-
protected static long getDataCenterId(long tempMaxDataCenterId) {
-
if (tempMaxDataCenterId <
0L || tempMaxDataCenterId > maxDataCenterId) {
-
tempMaxDataCenterId = maxDataCenterId;
-
}
-
long id =
0L;
-
try {
-
InetAddress ip = InetAddress.getLocalHost();
-
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
-
if (network == null) {
-
id =
1L;
-
}
else {
-
byte[] mac = network.getHardwareAddress();
-
id = ((
0x000000FF & (
long) mac[mac.length -
1])
-
| (
0x0000FF00 & (((
long) mac[mac.length -
2]) <<
8))) >>
6;
-
id = id % (tempMaxDataCenterId +
1);
-
}
-
}
catch (Exception e) {
-
System.out.println(
" getDatacenterId: " + e.getMessage());
-
}
-
return id;
-
}
-
-
-
public static void testProductIdByMoreThread(int dataCenterId, int workerId, int n) throws InterruptedException {
-
List<Thread> tlist =
new ArrayList<>();
-
Set<Long> setAll =
new HashSet<>();
-
CountDownLatch cdLatch =
new CountDownLatch(
10);
-
long start = System.currentTimeMillis();
-
int threadNo = dataCenterId;
-
Map<String,SnowflakeIdFactory> idFactories =
new HashMap<>();
-
for(
int i=
0;i<
10;i++){
-
//用線程名稱做map key.
-
idFactories.put(
"snowflake"+i,
new SnowflakeIdFactory(workerId, threadNo++));
-
}
-
for(
int i=
0;i<
10;i++){
-
Thread temp =
new Thread(
new Runnable() {
-
@Override
-
public
void run() {
-
Set<Long> setId =
new HashSet<>();
-
SnowflakeIdFactory idWorker = idFactories.get(Thread.currentThread().getName());
-
for(
int j=
0;j<n;j++){
-
setId.add(idWorker.nextId());
-
}
-
synchronized (setAll){
-
setAll.addAll(setId);
-
log.info(
"{}生產了{}個id,並成功加入到setAll中.",Thread.currentThread().getName(),n);
-
}
-
cdLatch.countDown();
-
}
-
},
"snowflake"+i);
-
tlist.add(temp);
-
}
-
for(
int j=
0;j<
10;j++){
-
tlist.get(j).start();
-
}
-
cdLatch.await();
-
-
long end1 = System.currentTimeMillis() - start;
-
-
log.info(
"共耗時:{}毫秒,預期應該生產{}個id, 實際合並總計生成ID個數:{}",end1,
10*n,setAll.size());
-
-
}
-
-
public static void testProductId(int dataCenterId, int workerId, int n){
-
SnowflakeIdFactory idWorker =
new SnowflakeIdFactory(workerId, dataCenterId);
-
SnowflakeIdFactory idWorker2 =
new SnowflakeIdFactory(workerId+
1, dataCenterId);
-
Set<Long> setOne =
new HashSet<>();
-
Set<Long> setTow =
new HashSet<>();
-
long start = System.currentTimeMillis();
-
for (
int i =
0; i < n; i++) {
-
setOne.add(idWorker.nextId());
//加入set
-
}
-
long end1 = System.currentTimeMillis() - start;
-
log.info(
"第一批ID預計生成{}個,實際生成{}個<<<<*>>>>共耗時:{}",n,setOne.size(),end1);
-
-
for (
int i =
0; i < n; i++) {
-
setTow.add(idWorker2.nextId());
//加入set
-
}
-
long end2 = System.currentTimeMillis() - start;
-
log.info(
"第二批ID預計生成{}個,實際生成{}個<<<<*>>>>共耗時:{}",n,setTow.size(),end2);
-
-
setOne.addAll(setTow);
-
log.info(
"合並總計生成ID個數:{}",setOne.size());
-
-
}
-
-
public static void testPerSecondProductIdNums(){
-
SnowflakeIdFactory idWorker =
new SnowflakeIdFactory(
1,
2);
-
long start = System.currentTimeMillis();
-
int count =
0;
-
for (
int i =
0; System.currentTimeMillis()-start<
1000; i++,count=i) {
-
/** 測試方法一: 此用法純粹的生產ID,每秒生產ID個數為300w+ */
-
idWorker.nextId();
-
/** 測試方法二: 在log中打印,同時獲取ID,此用法生產ID的能力受限於log.error()的吞吐能力.
-
* 每秒徘徊在10萬左右. */
-
//log.error("{}",idWorker.nextId());
-
}
-
long end = System.currentTimeMillis()-start;
-
System.out.println(end);
-
System.out.println(count);
-
}
-
-
public static void main(String[] args) {
-
/** case1: 測試每秒生產id個數?
-
* 結論: 每秒生產id個數300w+ */
-
testPerSecondProductIdNums();
-
-
/** case2: 單線程-測試多個生產者同時生產N個id,驗證id是否有重復?
-
* 結論: 驗證通過,沒有重復. */
-
//testProductId(1,2,10000);//驗證通過!
-
//testProductId(1,2,20000);//驗證通過!
-
-
/** case3: 多線程-測試多個生產者同時生產N個id, 全部id在全局范圍內是否會重復?
-
* 結論: 驗證通過,沒有重復. */
-
/* try {
-
testProductIdByMoreThread(1,2,100000);//單機測試此場景,性能損失至少折半!
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}*/
-
-
}
-
}
本文鏈接:https://blog.csdn.net/ycb1689/article/details/89331634