自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

分布式架構(gòu)系統(tǒng)生成全局唯一序列號(hào)的一個(gè)思路

存儲(chǔ) 存儲(chǔ)軟件 分布式
分布式架構(gòu)下,唯一序列號(hào)生成是我們?cè)谠O(shè)計(jì)一個(gè)系統(tǒng),尤其是數(shù)據(jù)庫(kù)使用分庫(kù)分表的時(shí)候常常會(huì)遇見(jiàn)的問(wèn)題。當(dāng)分成若干個(gè)sharding表后,如何能夠快速拿到一個(gè)唯一序列號(hào),是經(jīng)常遇到的問(wèn)題。

 一、相關(guān)背景

分布式架構(gòu)下,唯一序列號(hào)生成是我們?cè)谠O(shè)計(jì)一個(gè)系統(tǒng),尤其是數(shù)據(jù)庫(kù)使用分庫(kù)分表的時(shí)候常常會(huì)遇見(jiàn)的問(wèn)題。當(dāng)分成若干個(gè)sharding表后,如何能夠快速拿到一個(gè)唯一序列號(hào),是經(jīng)常遇到的問(wèn)題。

在攜程賬號(hào)數(shù)據(jù)庫(kù)遷移MySql過(guò)程中,我們對(duì)用戶ID的生成方案進(jìn)行了新的設(shè)計(jì),要求能夠支撐攜程現(xiàn)有的新用戶注冊(cè)體量。

本文通過(guò)攜程用戶ID生成器的實(shí)現(xiàn),希望能夠?qū)Υ蠹以O(shè)計(jì)分庫(kù)分表的唯一id有一些新的思路。

[[210819]]

二、特性需求

  1. 全局唯一
  2. 支持高并發(fā)
  3. 能夠體現(xiàn)一定屬性
  4. 高可靠,容錯(cuò)單點(diǎn)故障
  5. 高性能

三、業(yè)內(nèi)方案

生成ID的方法有很多,來(lái)適應(yīng)不同的場(chǎng)景、需求以及性能要求。

常見(jiàn)方式有:

1、利用數(shù)據(jù)庫(kù)遞增,全數(shù)據(jù)庫(kù)唯一。

優(yōu)點(diǎn):明顯,可控。

缺點(diǎn):?jiǎn)螏?kù)單表,數(shù)據(jù)庫(kù)壓力大。

2、UUID, 生成的是length=32的16進(jìn)制格式的字符串,如果回退為byte數(shù)組共16個(gè)byte元素,即UUID是一個(gè)128bit長(zhǎng)的數(shù)字,一般用16進(jìn)制表示。

優(yōu)點(diǎn):對(duì)數(shù)據(jù)庫(kù)壓力減輕了。

缺點(diǎn):但是排序怎么辦?

此外還有UUID的變種,增加一個(gè)時(shí)間拼接,但是會(huì)造成id非常長(zhǎng)。

3、twitter在把存儲(chǔ)系統(tǒng)從MySQL遷移到Cassandra的過(guò)程中由于Cassandra沒(méi)有順序ID生成機(jī)制,于是自己開(kāi)發(fā)了一套全局唯一ID生成服務(wù):Snowflake。

  1. 41位的時(shí)間序列(精確到毫秒,41位的長(zhǎng)度可以使用69年)
  2. 10位的機(jī)器標(biāo)識(shí)(10位的長(zhǎng)度最多支持部署1024個(gè)節(jié)點(diǎn))
  3. 12位的計(jì)數(shù)順序號(hào)(12位的計(jì)數(shù)順序號(hào)支持每個(gè)節(jié)點(diǎn)每毫秒產(chǎn)生4096個(gè)ID序號(hào)) ***位是符號(hào)位,始終為0。

優(yōu)點(diǎn):高性能,低延遲;獨(dú)立的應(yīng)用;按時(shí)間有序。

缺點(diǎn):需要獨(dú)立的開(kāi)發(fā)和部署。

4、Redis生成ID

當(dāng)使用數(shù)據(jù)庫(kù)來(lái)生成ID性能不夠要求的時(shí)候,我們可以嘗試使用Redis來(lái)生成ID。這主要依賴于Redis是單線程的,所以也可以用生成全局唯一的ID??梢杂肦edis的原子操作INCR和INCRBY來(lái)實(shí)現(xiàn)。

可以使用Redis集群來(lái)獲取更高的吞吐量。假如一個(gè)集群中有5臺(tái)Redis??梢猿跏蓟颗_(tái)Redis的值分別是1,2,3,4,5,然后步長(zhǎng)都是5。各個(gè)Redis生成的ID為:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

比較適合使用Redis來(lái)生成每天從0開(kāi)始的流水號(hào)。比如訂單號(hào)=日期+當(dāng)日自增長(zhǎng)號(hào)??梢悦刻煸赗edis中生成一個(gè)Key,使用INCR進(jìn)行累加。

優(yōu)點(diǎn):

不依賴于數(shù)據(jù)庫(kù),靈活方便,且性能優(yōu)于數(shù)據(jù)庫(kù)。

數(shù)字ID天然排序,對(duì)分頁(yè)或者需要排序的結(jié)果很有幫助。

使用Redis集群也可以防止單點(diǎn)故障的問(wèn)題。

缺點(diǎn):

如果系統(tǒng)中沒(méi)有Redis,還需要引入新的組件,增加系統(tǒng)復(fù)雜度。

需要編碼和配置的工作量比較大,多環(huán)境運(yùn)維很麻煩,

在開(kāi)始時(shí),程序?qū)嵗?fù)載到哪個(gè)redis實(shí)例一旦確定好,未來(lái)很難做修改。

5.   Flicker的解決方案

因?yàn)镸ySQL本身支持auto_increment操作,很自然地,我們會(huì)想到借助這個(gè)特性來(lái)實(shí)現(xiàn)這個(gè)功能。

Flicker在解決全局ID生成方案里就采用了MySQL自增長(zhǎng)ID的機(jī)制(auto_increment + replace into + MyISAM)。

6.還有其他一些方案,比如京東淘寶等電商的訂單號(hào)生成。因?yàn)橛唵翁?hào)和用戶id在業(yè)務(wù)上的區(qū)別,訂單號(hào)盡可能要多些冗余的業(yè)務(wù)信息,比如:

滴滴:時(shí)間+起點(diǎn)編號(hào)+車(chē)牌號(hào)

淘寶訂單:時(shí)間戳+用戶ID

其他電商:時(shí)間戳+下單渠道+用戶ID,有的會(huì)加上訂單***個(gè)商品的ID。

而用戶ID,則要求含義簡(jiǎn)單明了,包含注冊(cè)渠道即可,盡量短。

四、最終方案

最終我們選擇了以flicker方案為基礎(chǔ)進(jìn)行優(yōu)化改進(jìn)。具體實(shí)現(xiàn)是,單表遞增,內(nèi)存緩存號(hào)段的方式。

首先建立一張表,像這樣:

SEQUENCE_GENERATOR_TABLE

id   stub

1    192.168.1.1

其中id是自增的,stub是服務(wù)器ip

因?yàn)樾聰?shù)據(jù)庫(kù)采用mysql,所以使用mysql的獨(dú)有語(yǔ)法 replace to來(lái)更新記錄來(lái)獲得唯一id,例如這樣:

  1. REPLACE INTO SEQUENCE_GENERATOR_TABLE (stub) VALUES (“192.168.1.1”); 

再用SELECT id FROM SEQUENCE_GENERATOR_TABLEWHERE stub = “192.168.1.1”;   把它拿回來(lái)。

到上面為止,我們只是在單臺(tái)數(shù)據(jù)庫(kù)上生成ID,從高可用角度考慮,接下來(lái)就要解決單點(diǎn)故障問(wèn)題。

這也就是為什么要有這個(gè)機(jī)器ip字段呢?就是為了防止多服務(wù)器同時(shí)更新數(shù)據(jù),取回的id混淆的問(wèn)題。

所以,當(dāng)多個(gè)服務(wù)器的時(shí)候,這個(gè)表是這樣的:

id   stub

5    192.168.1.1

2    192.168.1.2

3    192.168.1.3

4    192.168.1.4

每臺(tái)服務(wù)器只更新自己的那條記錄,保證了單線程操作單行記錄。

這時(shí)候每個(gè)機(jī)器拿到的分別是5,2,3,4這4個(gè)id。

至此,我們似乎解決這個(gè)服務(wù)器隔離,原子性獲得id的問(wèn)題,也和flicker方案基本一致。

但是追根溯源,在原理上,方案還是依靠數(shù)據(jù)庫(kù)的特性,每次生成id都要請(qǐng)求db,開(kāi)銷(xiāo)很大。我們對(duì)此又進(jìn)行優(yōu)化,把這個(gè)id作為一個(gè)號(hào)段,而并不是要發(fā)出去的序列號(hào),并且這個(gè)號(hào)段是可以配置長(zhǎng)度的,可以1000也可以10000,也就是對(duì)拿回來(lái)的這個(gè)id放大多少倍的問(wèn)題。

OK,我們從DB一次查詢操作的開(kāi)銷(xiāo),拿回來(lái)了1000個(gè)用戶id到內(nèi)存中了。

現(xiàn)在的問(wèn)題就是要解決同一臺(tái)服務(wù)器在高并發(fā)場(chǎng)景,讓大家順序拿號(hào),別拿重復(fù),也別漏拿。

這個(gè)問(wèn)題簡(jiǎn)單來(lái)說(shuō),就是個(gè)保持這個(gè)號(hào)段對(duì)象隔離性的問(wèn)題。

AtomicLong是個(gè)靠譜的辦法。

當(dāng)***次拿回號(hào)段id后,擴(kuò)大1000倍,然后賦值給這個(gè)變量atomic,這就是這個(gè)號(hào)段的***個(gè)號(hào)碼。

  1. atomic.set(n * 1000); 

并且內(nèi)存里保存一下***id,也就是這個(gè)號(hào)段的***一個(gè)號(hào)碼

  1. currentMaxId = (n + 1) * 1000; 

一個(gè)號(hào)段就形成了。

此時(shí)每次有請(qǐng)求來(lái)取號(hào)時(shí)候,判斷一下有沒(méi)有到***一個(gè)號(hào)碼,沒(méi)有到,就拿個(gè)號(hào),走人。

  1. Long uid = atomic.incrementAndGet(); 

如果到達(dá)了***一個(gè)號(hào)碼,那么阻塞住其他請(qǐng)求線程,最早的那個(gè)線程去db取個(gè)號(hào)段,再更新一下號(hào)段的兩個(gè)值,就可以了。

這個(gè)方案,核心代碼邏輯不到20行,解決了分布式系統(tǒng)序列號(hào)生成的問(wèn)題。

這里有個(gè)小問(wèn)題,就是在服務(wù)器重啟后,因?yàn)樘?hào)碼緩存在內(nèi)存,會(huì)浪費(fèi)掉一部分用戶ID沒(méi)有發(fā)出去,所以在可能頻繁發(fā)布的應(yīng)用中,盡量減小號(hào)段放大的步長(zhǎng)n,能夠減少浪費(fèi)。

經(jīng)過(guò)實(shí)踐,性能的提升遠(yuǎn)遠(yuǎn)重要于浪費(fèi)一部分id。

如果再追求***,可以監(jiān)聽(tīng)spring或者servlet上下文的銷(xiāo)毀事件,把當(dāng)前即將發(fā)出去的用戶ID保存起來(lái),下次啟動(dòng)時(shí)候再撈回內(nèi)存即可。

五、上線效果

運(yùn)行5個(gè)多月,十分穩(wěn)定。

SOA服務(wù)平均響應(yīng)時(shí)間 0.59毫秒;

客戶端調(diào)用平均響應(yīng)時(shí)間2.52毫秒;

附流程圖:

責(zé)任編輯:武曉燕 來(lái)源: 36大數(shù)據(jù)
相關(guān)推薦

2024-03-13 08:23:08

分布式系統(tǒng)隨機(jī)

2021-06-28 14:45:07

分布式框架操作

2021-11-08 19:25:37

Go生成系統(tǒng)

2021-06-05 07:33:09

ID分布式架構(gòu)

2022-02-23 07:09:30

分布式ID雪花算法

2023-09-03 22:14:23

分布式ID

2022-08-01 08:01:04

ID發(fā)號(hào)器系統(tǒng)

2013-09-11 16:02:00

Spark分布式計(jì)算系統(tǒng)

2021-10-13 06:49:14

事故復(fù)盤(pán)ID

2019-01-28 11:46:53

架構(gòu)運(yùn)維技術(shù)

2016-09-30 10:13:07

分布式爬蟲(chóng)系統(tǒng)

2018-09-06 22:49:31

分布式架構(gòu)服務(wù)器

2022-06-27 08:36:27

分布式事務(wù)XA規(guī)范

2020-06-11 13:31:45

TCP序列號(hào)網(wǎng)絡(luò)

2020-07-30 09:35:09

Redis分布式鎖數(shù)據(jù)庫(kù)

2017-04-12 09:29:02

HiveMapReduceSpark

2011-09-14 10:08:07

Beanstalkd

2024-10-31 13:51:58

2023-05-29 14:07:00

Zuul網(wǎng)關(guān)系統(tǒng)

2021-07-03 19:21:50

架構(gòu)分布式系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)