柚子快報(bào)激活碼778899分享:分布式ID生成方式
柚子快報(bào)激活碼778899分享:分布式ID生成方式
1.UUID
uuid方式存在問題:占用字節(jié)數(shù)比較大;ID比較隨機(jī),作為MySQL主鍵寫入庫(kù)時(shí),為了保證順序性將導(dǎo)致B+Tree節(jié)點(diǎn)分裂比較頻繁,影響IO性能。
2.數(shù)據(jù)庫(kù)方式
步長(zhǎng)step = 3,即為機(jī)器的數(shù)量。
??? 第一臺(tái)機(jī)器:起始值0,下一個(gè)ID位:0 + 0*3,0+1*3,0+2*3,..., ~ 0,3,6,9,...,3*n。 ??? 第一臺(tái)機(jī)器:起始值1,下一個(gè)ID位:1 + 0*3,1+1*3,1+2*3,..., ~ 1,4,7,10,...,3*n + 1。 ??? 第一臺(tái)機(jī)器:起始值2,下一個(gè)ID位:2 + 0*3,2+1*3,2+2*3,..., ~ 2,5,8,11,...,3*n + 2。
如果起初確認(rèn)Mysql機(jī)器數(shù)量為4,則每臺(tái)機(jī)器通過(guò)自增方式生成ID如下:
第一臺(tái):4 * n + 0。
第二臺(tái):4 * n + 1。
第三臺(tái):4 * n + 2。
第四臺(tái):4 * n + 3。
綜上,該方式每臺(tái)機(jī)器均衡負(fù)載后生成的ID均不會(huì)重復(fù)。每次請(qǐng)求均通過(guò)以下SQL申請(qǐng)新的ID:
begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;
其中,表Tickets64只有兩個(gè)字段,字段stub存在唯一索引。MySQL命令之replace保證每次更新stub值時(shí)如果存在該值則刪除后自增,否則直接自增。
備注:表Tickets64只有一行數(shù)據(jù),行鎖保證并發(fā)問題。
問題:運(yùn)行中途需要新增機(jī)器來(lái)增加并發(fā)性能,ID如何處理?
解答:觀察現(xiàn)有所有機(jī)器找到其中最大ID值,例如1800。其次,重新確定每臺(tái)機(jī)器的起始值,例如3000、3001、3002、...?!颈仨毚笥?800】。然后,再次修改Mysql自增的步長(zhǎng)。最后重啟所有機(jī)器。
以上這種方式存在的問題:
機(jī)器橫向擴(kuò)展比較麻煩。ID并非單調(diào)自增,而是趨勢(shì)自增。每次獲取ID都得操作數(shù)據(jù)庫(kù),高并發(fā)嚴(yán)重影響性能。
解決辦法之Leaf方案。
2.1.Leaf-segment數(shù)據(jù)庫(kù)方案
第一種Leaf-segment方案,在使用數(shù)據(jù)庫(kù)的方案上,做了如下改變: - 原方案每次獲取ID都得讀寫一次數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)壓力大。改為利用proxy server批量獲取,每次獲取一個(gè)segment(step決定大小)號(hào)段的值。用完之后再去數(shù)據(jù)庫(kù)獲取新的號(hào)段,可以大大的減輕數(shù)據(jù)庫(kù)的壓力。 - 各個(gè)業(yè)務(wù)不同的發(fā)號(hào)需求用biz_tag字段來(lái)區(qū)分,每個(gè)biz-tag的ID獲取相互隔離,互不影響。如果以后有性能需求需要對(duì)數(shù)據(jù)庫(kù)擴(kuò)容,不需要上述描述的復(fù)雜的擴(kuò)容操作,只需要對(duì)biz_tag分庫(kù)分表就行。
數(shù)據(jù)庫(kù)設(shè)計(jì)方案如下:
優(yōu)點(diǎn):
Leaf服務(wù)可以很方便的線性擴(kuò)展,性能完全能夠支撐大多數(shù)業(yè)務(wù)場(chǎng)景。ID號(hào)碼是趨勢(shì)遞增的8byte的64位數(shù)字,滿足上述數(shù)據(jù)庫(kù)存儲(chǔ)的主鍵要求。容災(zāi)性高:Leaf服務(wù)內(nèi)部有號(hào)段緩存,即使DB宕機(jī),短時(shí)間內(nèi)Leaf仍能正常對(duì)外提供服務(wù)。可以自定義max_id的大小,非常方便業(yè)務(wù)從原有的ID方式上遷移過(guò)來(lái)。
缺點(diǎn):
ID號(hào)碼不夠隨機(jī),能夠泄露發(fā)號(hào)數(shù)量的信息,不太安全。TP999數(shù)據(jù)波動(dòng)大,當(dāng)號(hào)段使用完之后還是會(huì)hang在更新數(shù)據(jù)庫(kù)的I/O上,tg999數(shù)據(jù)會(huì)出現(xiàn)偶爾的尖刺。DB宕機(jī)會(huì)造成整個(gè)系統(tǒng)不可用。
2.2.Leaf-segment數(shù)據(jù)庫(kù)進(jìn)化方案之雙buffer優(yōu)化?
對(duì)于第二個(gè)缺點(diǎn),Leaf-segment做了一些優(yōu)化,簡(jiǎn)單的說(shuō)就是:
Leaf 取號(hào)段的時(shí)機(jī)是在號(hào)段消耗完的時(shí)候進(jìn)行的,也就意味著號(hào)段臨界點(diǎn)的ID下發(fā)時(shí)間取決于下一次從DB取回號(hào)段的時(shí)間,并且在這期間進(jìn)來(lái)的請(qǐng)求也會(huì)因?yàn)镈B號(hào)段沒有取回來(lái),導(dǎo)致線程阻塞。如果請(qǐng)求DB的網(wǎng)絡(luò)和DB的性能穩(wěn)定,這種情況對(duì)系統(tǒng)的影響是不大的,但是假如取DB的時(shí)候網(wǎng)絡(luò)發(fā)生抖動(dòng),或者DB發(fā)生慢查詢就會(huì)導(dǎo)致整個(gè)系統(tǒng)的響應(yīng)時(shí)間變慢。
為此,我們希望DB取號(hào)段的過(guò)程能夠做到無(wú)阻塞,不需要在DB取號(hào)段的時(shí)候阻塞請(qǐng)求線程,即當(dāng)號(hào)段消費(fèi)到某個(gè)點(diǎn)時(shí)就異步的把下一個(gè)號(hào)段加載到內(nèi)存中。而不需要等到號(hào)段用盡的時(shí)候才去更新號(hào)段。這樣做就可以很大程度上的降低系統(tǒng)的TP999指標(biāo)。
public class SegmentIDGenImpl implements IDGen {
//tag 與 segment對(duì)應(yīng)關(guān)系
private Map
public Result get(final String key) {
if (cache.containsKey(key)) {
SegmentBuffer buffer = cache.get(key);
if (!buffer.isInitOk()) {// 雙檢加鎖
synchronized (buffer) {
if (!buffer.isInitOk()) {
updateSegmentFromDb(key, buffer.getCurrent());
buffer.setInitOk(true);
}
}
}
return getIdFromSegmentBuffer(cache.get(key));
}
return new Result(EXCEPTION_ID_KEY_NOT_EXISTS, Status.EXCEPTION);
}
public void updateSegmentFromDb(String key, Segment segment) {
StopWatch sw = new Slf4JStopWatch();
SegmentBuffer buffer = segment.getBuffer();
LeafAlloc leafAlloc;
if (!buffer.isInitOk()) {
//UPDATE T_LEAF_ALLOC SET MAX_ID = MAX_ID + STEP WHERE BIZ_TAG = #{tag}
// mysql 寫操作不存在并發(fā)問題。初始化完畢MAX_ID
leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key);
buffer.setStep(leafAlloc.getStep());
buffer.setMinStep(leafAlloc.getStep());//leafAlloc中的step為DB中的step
} else if (buffer.getUpdateTimestamp() == 0) {
leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key);
buffer.setUpdateTimestamp(System.currentTimeMillis());
buffer.setStep(leafAlloc.getStep());
buffer.setMinStep(leafAlloc.getStep());//leafAlloc中的step為DB中的step
} else {
long duration = System.currentTimeMillis() - buffer.getUpdateTimestamp();
int nextStep = buffer.getStep();
if (duration < SEGMENT_DURATION) {
if (nextStep * 2 > MAX_STEP) {
//do nothing
} else {
nextStep = nextStep * 2;
}
} else if (duration < SEGMENT_DURATION * 2) {
//do nothing with nextStep
} else {
nextStep = nextStep / 2 >= buffer.getMinStep() ? nextStep / 2 : nextStep;
}
logger.info("leafKey[{}], step[{}], duration[{}mins], nextStep[{}]", key, buffer.getStep(), String.format("%.2f",((double)duration / (1000 * 60))), nextStep);
LeafAlloc temp = new LeafAlloc();
temp.setKey(key);
temp.setStep(nextStep);
leafAlloc = dao.updateMaxIdByCustomStepAndGetLeafAlloc(temp);
buffer.setUpdateTimestamp(System.currentTimeMillis());
buffer.setStep(nextStep);
buffer.setMinStep(leafAlloc.getStep());//leafAlloc的step為DB中的step
}
// must set value before set max
long value = leafAlloc.getMaxId() - buffer.getStep();
segment.getValue().set(value);
segment.setMax(leafAlloc.getMaxId());
segment.setStep(buffer.getStep());
sw.stop("updateSegmentFromDb", key + " " + segment);
}
public Result getIdFromSegmentBuffer(final SegmentBuffer buffer) {
// 一個(gè)進(jìn)程中,存在并發(fā)訪問cache中某個(gè)tag
while (true) {
buffer.rLock().lock();
try {
// 從雙buffer中獲取當(dāng)前Segment
final Segment segment = buffer.getCurrent();
// 如果當(dāng)前步長(zhǎng)內(nèi),ID使用率已經(jīng)達(dá)到步長(zhǎng)的90%,則需要切換buffer
if (!buffer.isNextReady() && (segment.getIdle() < 0.9 * segment.getStep()) && buffer.getThreadRunning().compareAndSet(false, true)) {
service.execute(new Runnable() {
@Override
public void run() {
//獲取到第二個(gè)Segment
Segment next = buffer.getSegments()[buffer.nextPos()];
boolean updateOk = false;
try {
// 初始化第二個(gè)Segment對(duì)應(yīng)的其實(shí)ID,以及步長(zhǎng)
updateSegmentFromDb(buffer.getKey(), next);
updateOk = true;
} catch (Exception e) {
logger.warn(buffer.getKey() + " updateSegmentFromDb exception", e);
} finally {
if (updateOk) {
buffer.wLock().lock();
buffer.setNextReady(true);
buffer.getThreadRunning().set(false);
buffer.wLock().unlock();
} else {
buffer.getThreadRunning().set(false);
}
}
}
});
}
// 在當(dāng)前步長(zhǎng)中獲取下一個(gè)ID
long value = segment.getValue().getAndIncrement();
if (value < segment.getMax()) {// 沒有達(dá)到最大ID之前,均成功返回
return new Result(value, Status.SUCCESS);
}
} finally {
buffer.rLock().unlock();
}
//以下執(zhí)行說(shuō)明ID已經(jīng)達(dá)到最大值
waitAndSleep(buffer);
buffer.wLock().lock();
try {
final Segment segment = buffer.getCurrent();
long value = segment.getValue().getAndIncrement();
if (value < segment.getMax()) {//以下成立,說(shuō)明上述定時(shí)任務(wù)切換Segment成功
return new Result(value, Status.SUCCESS);
}
if (buffer.isNextReady()) {//
buffer.switchPos();
buffer.setNextReady(false);
} else {
return new Result(EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL, Status.EXCEPTION);
}
} finally {
buffer.wLock().unlock();
}
}
}
}
2.3.Leaf高可用容災(zāi)
對(duì)于第三點(diǎn)“DB可用性”問題,我們目前采用一主兩從的方式,同時(shí)分機(jī)房部署,Master和Slave之間采用半同步方式同步數(shù)據(jù)。同時(shí)使用公司Atlas數(shù)據(jù)庫(kù)中間件(已開源,改名為DBProxy)做主從切換。當(dāng)然這種方案在一些情況會(huì)退化成異步模式,甚至在非常極端情況下仍然會(huì)造成數(shù)據(jù)不一致的情況,但是出現(xiàn)的概率非常小。如果你的系統(tǒng)要保證100%的數(shù)據(jù)強(qiáng)一致,可以選擇使用“類Paxos算法”實(shí)現(xiàn)的強(qiáng)一致MySQL方案,如MySQL 5.7前段時(shí)間剛剛GA的MySQL Group Replication。但是運(yùn)維成本和精力都會(huì)相應(yīng)的增加,根據(jù)實(shí)際情況選型即可。
3.Leaf-snowflake方案
Leaf-segment方案可以生成趨勢(shì)遞增的ID,同時(shí)ID號(hào)是可計(jì)算的,不適用于訂單ID生成場(chǎng)景,比如競(jìng)對(duì)在兩天中午12點(diǎn)分別下單,通過(guò)訂單id號(hào)相減就能大致計(jì)算出公司一天的訂單量,這個(gè)是不能忍受的。面對(duì)這一問題,我們提供了 Leaf-snowflake方案。
public class SnowFlake {
/**
* 起始的時(shí)間戳
*/
private final static long START_STMP = 1480166465631L;
/**
* 每一部分占用的位數(shù)
*/
private final static long SEQUENCE_BIT = 12; //序列號(hào)占用的位數(shù)
private final static long MACHINE_BIT = 5; //機(jī)器標(biāo)識(shí)占用的位數(shù)
private final static long DATACENTER_BIT = 5;//數(shù)據(jù)中心占用的位數(shù)
/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId; //數(shù)據(jù)中心
private long machineId; //機(jī)器標(biāo)識(shí)
private long sequence = 0L; //序列號(hào)
private long lastStmp = -1L;//上一次時(shí)間戳
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
/**
* 產(chǎn)生下一個(gè)ID
* @return
* 同步鎖的保證:同一毫秒內(nèi)可能存在多個(gè)請(qǐng)求競(jìng)爭(zhēng)得到同步鎖。
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
/**
* 同一毫秒內(nèi)允許 MAX_SEQUENCE(4095)個(gè)請(qǐng)求先后生成ID。多余的請(qǐng)求因?yàn)橐韵聴l件的成立拒絕生成
*/
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStmp == lastStmp) {
//相同毫秒內(nèi),序列號(hào)自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列數(shù)已經(jīng)達(dá)到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒內(nèi),序列號(hào)置為0
sequence = 0L;
}
lastStmp = currStmp;
/**
* 在64位字節(jié)中通過(guò) 或 操作將 時(shí)間戳、數(shù)據(jù)中心、機(jī)器標(biāo)識(shí)、序列號(hào)4部分放到對(duì)應(yīng)字節(jié)范圍內(nèi)。
* 注意:對(duì)于同一個(gè)項(xiàng)目,datacenterId、machineId可能是不變的。如果隨便更改時(shí)間戳起始值之START_STMP值可能導(dǎo)致分布式ID存在相同的情況。
*
* 在分布式ID 64個(gè)字節(jié)中,41個(gè)字節(jié)作為時(shí)間戳,其對(duì)應(yīng)的最大值為2^41。41 位的時(shí)間位是2 ^ 41 / (365 * 24 * 3600 * 1000) = 69 年。
* currStmp - START_STMP 是指距離當(dāng)年之后的69年內(nèi)。
* 如果直接用 currStmp 替代,則表示距離1970年后的69年,以2024年為例最多可用15年。
*/
return (currStmp - START_STMP) << TIMESTMP_LEFT //時(shí)間戳部分
| datacenterId << DATACENTER_LEFT //數(shù)據(jù)中心部分
| machineId << MACHINE_LEFT //機(jī)器標(biāo)識(shí)部分
| sequence; //序列號(hào)部分
}
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private long getNewstmp() {
return System.currentTimeMillis();
}
public static void m1() {
SnowFlake snowFlake = new SnowFlake(2, 3);
for (int i = 0; i < (1 << 12); i++) {
System.out.println(snowFlake.nextId());
}
}
}
3.1.解決時(shí)鐘回?fù)軉栴}
時(shí)鐘回?fù)軐?dǎo)致的問題存在兩個(gè):獲取分布式ID & 服務(wù)啟動(dòng)流程。
3.1.1.服務(wù)啟動(dòng)
因?yàn)檫@種方案依賴時(shí)間,如果機(jī)器的時(shí)鐘發(fā)生了回?fù)?,那么就?huì)有可能生成重復(fù)的ID號(hào),需要解決時(shí)鐘回退的問題。
參見上圖整個(gè)啟動(dòng)流程圖,服務(wù)啟動(dòng)時(shí)首先檢查自己是否寫過(guò)ZooKeeper leaf_forever節(jié)點(diǎn):
若寫過(guò),則用自身系統(tǒng)時(shí)間與leaf_forever/${self}節(jié)點(diǎn)記錄時(shí)間做比較,若小于leaf_forever/${self}時(shí)間則認(rèn)為機(jī)器時(shí)間發(fā)生了大步長(zhǎng)回?fù)?,服?wù)啟動(dòng)失敗并報(bào)警。若未寫過(guò),證明是新服務(wù)節(jié)點(diǎn),直接創(chuàng)建持久節(jié)點(diǎn)leaf_forever/${self}并寫入自身系統(tǒng)時(shí)間,接下來(lái)綜合對(duì)比其余Leaf節(jié)點(diǎn)的系統(tǒng)時(shí)間來(lái)判斷自身系統(tǒng)時(shí)間是否準(zhǔn)確,具體做法是取leaf_temporary下的所有臨時(shí)節(jié)點(diǎn)(所有運(yùn)行中的Leaf-snowflake節(jié)點(diǎn))的服務(wù)IP:Port,然后通過(guò)RPC請(qǐng)求得到所有節(jié)點(diǎn)的系統(tǒng)時(shí)間,計(jì)算sum(time)/nodeSize。若abs( 系統(tǒng)時(shí)間-sum(time)/nodeSize ) < 閾值,認(rèn)為當(dāng)前系統(tǒng)時(shí)間準(zhǔn)確,正常啟動(dòng)服務(wù),同時(shí)寫臨時(shí)節(jié)點(diǎn)leaf_temporary/${self} 維持租約。否則認(rèn)為本機(jī)系統(tǒng)時(shí)間發(fā)生大步長(zhǎng)偏移,啟動(dòng)失敗并報(bào)警。每隔一段時(shí)間(3s)上報(bào)自身系統(tǒng)時(shí)間寫入leaf_forever/${self}。
?
public class SnowflakeIDGenImpl implements IDGen {
public SnowflakeIDGenImpl(String zkAddress, int port, long twepoch) {
this.twepoch = twepoch;
Preconditions.checkArgument(timeGen() > twepoch, "Snowflake not support twepoch gt currentTime");
final String ip = Utils.getIp();
SnowflakeZookeeperHolder holder = new SnowflakeZookeeperHolder(ip, String.valueOf(port), zkAddress);
boolean initFlag = holder.init();
Preconditions.checkArgument(workerId >= 0 && workerId <= maxWorkerId, "workerID must gte 0 and lte 1023");
}
}
public class SnowflakeZookeeperHolder {
public boolean init() {
try {
CuratorFramework curator = createWithOptions(connectionString, new RetryUntilElapsed(1000, 4), 10000, 6000);
curator.start();
Stat stat = curator.checkExists().forPath(PATH_FOREVER);
if (stat == null) {
//不存在根節(jié)點(diǎn),機(jī)器第一次啟動(dòng),創(chuàng)建/snowflake/ip:port-000000000,并上傳數(shù)據(jù)
zk_AddressNode = createNode(curator);
//worker id 默認(rèn)是0
updateLocalWorkerID(workerID);
//定時(shí)上報(bào)本機(jī)時(shí)間給forever節(jié)點(diǎn):其實(shí)就是一個(gè)定時(shí)任務(wù),每隔3秒上報(bào)一次當(dāng)前時(shí)間啊
ScheduledUploadData(curator, zk_AddressNode);
return true;
} else {
Map
Map
//存在根節(jié)點(diǎn),先檢查是否有屬于自己的根節(jié)點(diǎn)
List
for (String key : keys) {
String[] nodeKey = key.split("-");
realNode.put(nodeKey[0], key);
nodeMap.put(nodeKey[0], Integer.parseInt(nodeKey[1]));
}
Integer workerid = nodeMap.get(listenAddress);
if (workerid != null) {
//有自己的節(jié)點(diǎn),zk_AddressNode=ip:port
zk_AddressNode = PATH_FOREVER + "/" + realNode.get(listenAddress);
workerID = workerid;//啟動(dòng)worder時(shí)使用會(huì)使用
if (!checkInitTimeStamp(curator, zk_AddressNode)) {
throw new CheckLastTimeException("init timestamp check error,forever node timestamp gt this node time");
}
//準(zhǔn)備創(chuàng)建臨時(shí)節(jié)點(diǎn)
doService(curator);
// 將workerID 初始化在 leaf 服務(wù)本地磁盤內(nèi)部,一旦zk出現(xiàn)問題可以從本地磁盤獲取該workerID
updateLocalWorkerID(workerID);
} else {
//表示新啟動(dòng)的節(jié)點(diǎn),創(chuàng)建持久節(jié)點(diǎn) ,不用check時(shí)間
String newNode = createNode(curator);
zk_AddressNode = newNode;
String[] nodeKey = newNode.split("-");
workerID = Integer.parseInt(nodeKey[1]);
doService(curator);
updateLocalWorkerID(workerID);
LOGGER.info("[New NODE]can not find node on forever node that endpoint ip-{} port-{} workid-{},create own node on forever node and start SUCCESS ", ip, port, workerID);
}
}
} catch (Exception e) {
LOGGER.error("Start node ERROR {}", e);
try {
// 如果 zk 出現(xiàn)問題,從本地磁盤獲取workerID
Properties properties = new Properties();
properties.load(new FileInputStream(new File(PROP_PATH.replace("{port}", port + ""))));
workerID = Integer.valueOf(properties.getProperty("workerID"));
LOGGER.warn("START FAILED ,use local node file properties workerID-{}", workerID);
} catch (Exception e1) {
LOGGER.error("Read file error ", e1);
return false;
}
}
return true;
}
}
public class SnowflakeIDGenImpl implements IDGen {
public synchronized Result get(String key) {//獲取分布式事務(wù)ID
long timestamp = timeGen();
if (timestamp < lastTimestamp) {//出現(xiàn)了時(shí)鐘回?fù)墁F(xiàn)象
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
try {
wait(offset << 1);// 等待一段時(shí)間:時(shí)鐘回?fù)軙r(shí)間段
timestamp = timeGen();// 再次獲取新的時(shí)間
if (timestamp < lastTimestamp) {
return new Result(-1, Status.EXCEPTION);
}
} catch (InterruptedException e) {
LOGGER.error("wait interrupted");
return new Result(-2, Status.EXCEPTION);
}
} else {
return new Result(-3, Status.EXCEPTION);
}
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
//seq 為0的時(shí)候表示是下一毫秒時(shí)間開始對(duì)seq做隨機(jī)
sequence = RANDOM.nextInt(100);
timestamp = tilNextMillis(lastTimestamp);
}
} else {
//如果是新的ms開始
sequence = RANDOM.nextInt(100);
}
lastTimestamp = timestamp;
// 返回最終的分布式ID
long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
return new Result(id, Status.SUCCESS);
}
}
Leaf——美團(tuán)點(diǎn)評(píng)分布式ID生成系統(tǒng)
柚子快報(bào)激活碼778899分享:分布式ID生成方式
精彩內(nèi)容
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。