又一個多月沒冒泡了,其實最近學了些東西,可是沒有安排時間整理成博文,後續再奉上。最近還寫了一個發郵件的組件以及性能測試請看 《NET開發郵件發送功能的全面教程(含郵件組件源碼)》 ,還弄了個MSSQL參數化語法生成器,會在9月整理出來,有興趣的園友能夠關注下個人博客。html
分享起因,最近公司用到,而且在找最合適的方案,但願你們多參與討論和提出新方案。我和個人小夥伴們也討論了這個主題,我受益不淺啊……java
博文示例:mysql
1. GUID生成Int64值後是否還具備惟一性測試程序員
2. Random生成高惟一性隨機碼web
今天分享的主題是:如何在高併發分佈式系統中生成全局惟一Id。redis
但這篇博文其實是「半分享半討論」的博文:算法
1) 半分享是我將說下我所瞭解到的關於今天主題所涉及的幾種方案。sql
2) 半討論是我但願你們對各個方案都說說本身的看法,更加但願你們能提出更好的方案。(我還另外提問在此:http://q.cnblogs.com/q/53552/上面已有幾位園友回覆(感謝dudu站長的參與),若大家有看法和新方案就在本博文留言吧,方便我整理更新到博文中,謝謝!)mongodb
我瞭解的方案以下……………………………………………………………………數據庫
一、 使用數據庫自增Id
優點:編碼簡單,無需考慮記錄惟一標識的問題。
缺陷:
1) 在大表作水平分表時,就不能使用自增Id,由於Insert的記錄插入到哪一個分表依分表規則斷定決定,如果自增Id,各個分表中Id就會重複,在作查詢、刪除時就會有異常。
2) 在對錶進行高併發單記錄插入時須要加入事物機制,不然會出現Id重複的問題。
3) 在業務上操做父、子表(即關聯表)插入時,須要在插入數據庫以前獲取max(id)用於標識父表和子表關係,若存在併發獲取max(id)的狀況,max(id)會同時被別的線程獲取到。
4) 等等。
結論:適合小應用,無需分表,沒有高併發性能要求。
二、 單獨開一個數據庫,獲取全局惟一的自增序列號或各表的MaxId
1) 使用自增序列號表
專門一個數據庫,生成序列號。開啓事物,每次操做插入時,先將數據插入到序列表並返回自增序列號用於作爲惟一Id進行業務數據插入。
注意:須要按期清理序列表的數據以保證獲取序列號的效率;插入序列表記錄時要開啓事物。
使用此方案的問題是:每次的查詢序列號是一個性能損耗;若是這個序列號列暴了,那就杯具了,你不知道哪一個表使用了哪一個序列,因此就必須換另外一種惟一Id方式如GUID。
2) 使用MaxId表存儲各表的MaxId值
專門一個數據庫,記錄各個表的MaxId值,建一個存儲過程來取Id,邏輯大體爲:開啓事物,對於在表中不存在記錄,直接返回一個默認值爲1的鍵值,同時插入該條記錄到table_key表中。而對於已存在的記錄,key值直接在原來的key基礎上加1更新到MaxId表中並返回key。
使用此方案的問題是:每次的查詢MaxId是一個性能損耗;不過不會像自增序列表那麼容易列暴掉,由於是擺表進行劃分的。
詳細可參考:《使用MaxId表存儲各表的MaxId值,以獲取全局惟一Id》
我截取此文中的sql語法以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
第一步:建立表
create
table
table_key
(
table_name
varchar
(50)
not
null
primary
key
,
key_value
int
not
null
)
第二步:建立存儲過程來取自增ID
create
procedure
up_get_table_key
(
@table_name
varchar
(50),
@key_value
int
output
)
as
begin
begin
tran
declare
@
key
int
--initialize the key with 1
set
@
key
=1
--whether the specified table is exist
if
not
exists(
select
table_name
from
table_key
where
table_name=@table_name)
begin
insert
into
table_key
values
(@table_name,@
key
)
--default key vlaue:1
end
-- step increase
else
begin
select
@
key
=key_value
from
table_key
with
(nolock)
where
table_name=@table_name
set
@
key
=@
key
+1
--update the key value by table name
update
table_key
set
key_value=@
key
where
table_name=@table_name
end
--set ouput value
set
@key_value=@
key
--commit tran
commit
tran
if @@error>0
rollback
tran
end
|
感謝園友的好建議:
1. (@輝_輝)建議給table_key中爲每一個表初始化一條key爲1的記錄,這樣就不用每次if來判斷了。
2. (@樂活的CodeMonkey)建議給存儲過程當中數據庫事物隔離級別提升一下,由於出如今CS代碼層上使用以下事物代碼會致使併發重複問題.
1
2
3
4
5
6
7
8
|
TransactionOptions option =
new
TransactionOptions();
option.IsolationLevel = IsolationLevel.ReadUncommitted;
option.Timeout =
new
TimeSpan(0, 10, 0);
using
(TransactionScope transaction =
new
TransactionScope(TransactionScopeOption.RequiresNew, option))
{
//調用存儲過程
}
|
在諮詢過DBA後,這個存儲過程提升數據庫隔離級別會加大數據庫訪問壓力,致使響應超時問題。因此這個建議咱們只能在代碼編寫宣導上作。
3. (@土豆烤肉)存儲過程當中不使用事物,一旦使用到事物性能就急劇下滑。直接使用UPDATE獲取到的更新鎖,即SQL SERVER會保證UPDATE的順序執行。(已在用戶過千萬的併發系統中使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
create
procedure
[dbo].[up_get_table_key]
(
@table_name
varchar
(50),
@key_value
int
output
)
as
begin
SET
NOCOUNT
ON
;
DECLARE
@maxId
INT
UPDATE
table_key
SET
@maxId = key_value,key_value = key_value + 1
WHERE
table_name=@table_name
SELECT
@maxId
end
|
結論:適用中型應用,此方案解決了分表,關聯表插入記錄的問題。可是沒法知足高併發性能要求。同時也存在單點問題,若是這個數據庫cash掉的話……
咱們目前正頭痛這個問題,由於咱們的高併發經常出現數據庫訪問超時,瓶頸就在這個MaxId表。咱們也有考慮使用分佈式緩存(eg:memcached)緩存第一次訪問MaxId表數據,以提升再次訪問速度,並定時用緩存數據更新一次MaxId表,但咱們擔憂的問題是:
a) 假若緩存失效或暴掉了,那緩存的MaxId沒有更新到數據庫致使數據丟失,必須停掉站點來執行Select max(id)各個表來同步MaxId表。
b) 分佈式緩存不是一保存下去,其餘服務器上就立馬能夠獲取到的,即數據存在不肯定性。(其實也是緩存的一個誤用,緩存應該用來存的是頻繁訪問而且不多改動的內容)
改進方案:
總體思想:創建兩臺以上的數據庫ID生成服務器,每一個服務器都有一張記錄各表當前ID的MaxId表,可是MaxId表中Id的增加步長是服務器的數量,起始值依次錯開,這樣至關於把ID的生成散列到每一個服務器節點上。例如:若是咱們設置兩臺數據庫ID生成服務器,那麼就讓一臺的MaxId表的Id起始值爲1(或當前最大Id+1),每次增加步長爲2,另外一臺的MaxId表的ID起始值爲2(或當前最大Id+2),每次步長也爲2。這樣就將產生ID的壓力均勻分散到兩臺服務器上,同時配合應用程序控制,當一個服務器失效後,系統能自動切換到另外一個服務器上獲取ID,從而解決的單點問題保證了系統的容錯。(Flickr思想)
可是要注意:一、多服務器就必須面臨負載均衡的問題;二、假若添加新節點,須要對原有數據從新根據步長計算遷移數據。
結論:適合大型應用,生成Id較短,友好性比較好。(強烈推薦)
三、 Sequence特性
這個特性在SQL Server 2012、Oracle中可用。這個特性是數據庫級別的,容許在多個表之間共享序列號。它能夠解決分表在同一個數據庫的狀況,但假若分表放在不一樣數據庫,那將共享不到此序列號。(eg:Sequence使用場景:你須要在多個表之間公用一個流水號。以往的作法是額外創建一個表,而後存儲流水號)
相關Sequence特性資料:
SQL Server2012中的SequenceNumber嘗試
SQL Server 2012 開發新功能——序列對象(Sequence)
Difference between Identity and Sequence in SQL Server 2012
結論:適用中型應用,此方案不能徹底解決分表問題,並且沒法知足高併發性能要求。同時也存在單點問題,若是這個數據庫cash掉的話……
四、 經過數據庫集羣編號+集羣內的自增類型兩個字段共同組成惟一主鍵
優勢:實現簡單,維護也比較簡單。
缺點:關聯表操做相對比較複雜,須要兩個字段。而且業務邏輯必須是一開始就設計爲處理複合主鍵的邏輯,假若是到了後期,由單主鍵轉爲複合主鍵那改動成本就太大了。
結論:適合大型應用,但須要業務邏輯配合處理複合主鍵。
五、 經過設置每一個集羣中自增 ID 起始點(auto_increment_offset),將各個集羣的ID進行絕對的分段來實現全局惟一。當遇到某個集羣數據增加過快後,經過命令調整下一個 ID 起始位置跳過可能存在的衝突。
優勢:實現簡單,且比較容易根據 ID 大小直接判斷出數據處在哪一個集羣,對應用透明。缺點:維護相對較複雜,須要高度關注各個集羣 ID 增加情況。
結論:適合大型應用,但須要高度關注各個集羣 ID 增加情況。
六、 GUID(Globally Unique Identifier,全局惟一標識符)
GUID一般表示成32個16進制數字(0-9,A-F)組成的字符串,如:{21EC2020-3AEA-1069-A2DD-08002B30309D},它實質上是一個128位長的二進制整數。
GUID制定的算法中使用到用戶的網卡MAC地址,以保證在計算機集羣中生成惟一GUID;在相同計算機上隨機生成兩個相同GUID的可能性是很是小的,但並不爲0。因此,用於生成GUID的算法一般都加入了非隨機的參數(如時間),以保證這種重複的狀況不會發生。
優勢:GUID是最簡單的方案,跨平臺,跨語言,跨業務邏輯,全局惟一的Id,數據間同步、遷移都能簡單實現。
缺點:
1) 存儲佔了32位,且無可讀性,返回GUID給客戶顯得很不專業;
2) 佔用了珍貴的彙集索引,通常咱們不會根據GUID去查單據,而且插入時由於GUID是無需的,在彙集索引的排序規則下可能移動大量的記錄。
有兩位園友主推GUID,無須順序GUID方案緣由以下:
@徐少俠 GUID無序在併發下效率高,而且一個數據頁內添加新行,是在B樹內增長,本質沒有什麼數據被移動,惟一可能的,是頁填充因子滿了,須要拆頁。而GUID方案致使的拆頁比順序ID要低太多了(數據庫不是很懂,暫時沒法判定,你們本身認識)
@無色 咱們要明白id是什麼,是身份標識,標識身份是id最大的業務邏輯,不要引入什麼時間,什麼用戶業務邏輯,那是另一個字段乾的事,使用base64(guid,uuid),是通盤考慮,徹底能夠更好的兼容nosql,key-value存儲。
(推薦),可是假若你係統一開始沒有規劃一個業務Id,那麼將致使大量的改動,因此這個方案的最佳狀態是一開始就設計業務Id,固然業務Id的惟一性也是咱們要考慮的。
結論:適合大型應用;生成的Id不夠友好;佔據了32位;索引效率較低。
改進:
1) (@dudu提點)在SQL Server 2005中新增了NEWSEQUENTIALID函數。
詳細請看:《理解newid()和newsequentialid()》
在指定計算機上建立大於先前經過該函數生成的任何 GUID 的 GUID。 newsequentialid 產生的新的值是有規律的,則索引B+樹的變化是有規律的,就不會致使索引列插入時移動大量記錄的問題。
但一旦服務器從新啓動,其再次生成的GUID可能反而變小(但仍然保持惟一)。這在很大程度上提升了索引的性能,但並不能保證所生成的GUID一直增大。SQL的這個函數產生的GUID很簡單就能夠預測,所以不適合用於安全目的。
a) 只能作爲數據庫列的DEFAULT VALUE,不能執行相似SELECT NEWSEQUENTIALID()的語句.
b) 如何得到生成的GUID.
若是生成的GUID所在字段作爲外鍵要被其餘表使用,咱們就須要獲得這個生成的值。一般,PK是一個IDENTITY字段,咱們能夠在INSERT以後執行 SELECT SCOPE_IDENTITY()來得到新生成的ID,可是因爲NEWSEQUENTIALID()不是一個INDETITY類型,這個辦法是作不到了,而他自己又只能在默認值中使用,不能夠事先SELECT好再插入,那麼咱們如何獲得呢?有如下兩種方法:
1
2
3
4
5
6
7
8
9
10
11
12
|
--1. 定義臨時表變量
DECLARE
@outputTable
TABLE
(ID uniqueidentifier)
INSERT
INTO
TABLE1(col1, col2)
OUTPUT
INSERTED.ID
INTO
@outputTable
VALUES
(
'value1'
,
'value2'
)
SELECT
ID
FROM
@outputTable
--2. 標記ID字段爲ROWGUID(一個表只能有一個ROWGUID)
INSERT
INTO
TABLE1(col1, col2)
VALUES
(
'value1'
,
'value2'
)
--在這裏,ROWGUIDCOL其實至關於一個別名
SELECT
ROWGUIDCOL
FROM
TABLE1
|
結論:適合大型應用,解決了GUID無序特性致使索引列插入移動大量記錄的問題。可是在關聯表插入時須要返回數據庫中生成的GUID;生成的Id不夠友好;佔據了32位。
2) 「COMB」(combined guid/timestamp,意思是:組合GUID/時間截)
(感謝:@ ethan-luo ,@lcs-帥 )
COMB數據類型的基本設計思路是這樣的:既然GUID數據因毫無規律可言形成索引效率低下,影響了系統的性能,那麼能不能經過組合的方式,保留GUID的10個字節,用另6個字節表示GUID生成的時間(DateTime),這樣咱們將時間信息與GUID組合起來,在保留GUID的惟一性的同時增長了有序性,以此來提升索引效率。
在NHibernate中,COMB型主鍵的生成代碼以下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/// <summary> /// Generate a new <see cref="Guid"/> using the comb algorithm.
/// </summary>
private
Guid GenerateComb()
{
byte
[] guidArray = Guid.NewGuid().ToByteArray();
DateTime baseDate =
new
DateTime(1900, 1, 1);
DateTime now = DateTime.Now;
// Get the days and milliseconds which will be used to build
//the byte string
TimeSpan days =
new
TimeSpan(now.Ticks - baseDate.Ticks);
TimeSpan msecs = now.TimeOfDay;
// Convert to a byte array
// Note that SQL Server is accurate to 1/300th of a
// millisecond so we divide by 3.333333
byte
[] daysArray = BitConverter.GetBytes(days.Days);
byte
[] msecsArray = BitConverter.GetBytes((
long
)
(msecs.TotalMilliseconds / 3.333333));
// Reverse the bytes to match SQL Servers ordering
Array.Reverse(daysArray);
Array.Reverse(msecsArray);
// Copy the bytes into the guid
Array.Copy(daysArray, daysArray.Length - 2, guidArray,
guidArray.Length - 6, 2);
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray,
guidArray.Length - 4, 4);
return
new
Guid(guidArray);
}
|
結論:適合大型應用。即保留GUID的惟一性的同時增長了GUID有序性,提升了索引效率;解決了關聯表業務問題;生成的Id不夠友好;佔據了32位。(強烈推薦)
3) 長度問題,使用Base64或Ascii85編碼解決。(要注意的是上述有序性方案在進行編碼後也會變得無序)
如:
GUID:{3F2504E0-4F89-11D3-9A0C-0305E82C3301}
當須要使用更少的字符表示GUID時,可能會使用Base64或Ascii85編碼。Base64編碼的GUID有22-24個字符,如:
7QDBkvCA1+B9K/U0vrQx1A
7QDBkvCA1+B9K/U0vrQx1A==
Ascii85編碼後是20個字符,如:
5:$Hj:Pf\4RLB9%kU\Lj
代碼如:
Guid guid = Guid.NewGuid();
byte[] buffer = guid.ToByteArray();
var shortGuid = Convert.ToBase64String(buffer);
結論:適合大型應用,縮短GUID的長度。生成的Id不夠友好;索引效率較低。
七、 GUID TO Int64
對於GUID的可讀性,有園友給出以下方案:(感謝:@黑色的羽翼)
1
2
3
4
5
6
7
8
|
/// <summary>
/// 根據GUID獲取19位的惟一數字序列
/// </summary>
public
static
long
GuidToLongID()
{
byte
[] buffer = Guid.NewGuid().ToByteArray();
return
BitConverter.ToInt64(buffer, 0);
}
|
即將GUID轉爲了19位數字,數字反饋給客戶能夠必定程度上緩解友好性問題。EG:
GUID: cfdab168-211d-41e6-8634-ef5ba6502a22 (不友好)
Int64: 5717212979449746068 (友好性還行)
不過個人小夥伴說ToInt64後就不惟一了。所以我專門寫了個併發測試程序,後文將給出測試結果截圖及代碼簡單說明。
(惟一性、業務適合性是能夠權衡的,這個惟一性確定比不過GUID的,通常程序上都會安排錯誤處理機制,好比異常後執行一次重插的方案……)
結論:適合大型應用,生成相對友好的Id(純數字)------因簡單和業務友好性而推薦。
八、 本身寫編碼規則
優勢:全局惟一Id,符合業務後續長遠的發展(可能具體業務須要本身的編碼規則等等)。
缺陷:根據具體編碼規則實現而不一樣;還要考慮假若主鍵在業務上容許改變的,會帶來外鍵同步的麻煩。
我這邊寫兩個編碼規則方案:(可能不惟一,只是我的方案,也請你們提出本身的編碼規則)
1) 12位年月日時分秒+5位隨機碼+3位服務器編碼 (這樣就徹底單機完成生成全局惟一編碼)---共20位
缺陷:由於附帶隨機碼,因此編碼缺乏必定的順序感。(生成高惟一性隨機碼的方案稍後給給出程序)
2) 12位年月日時分秒+5位流水碼+3位服務器編碼 (這樣流水碼就須要結合數據庫和緩存)---共20位 (將影響順序權重大的「流水碼」放前面,影響順序權重小的服務器編碼放後)
缺陷:由於使用到流水碼,流水碼的生成必然會遇到和MaxId、序列表、Sequence方案中相似的問題
(爲何沒有毫秒?毫秒也不具有業務可讀性,我改用5位隨機碼、流水碼代替,推測1秒內應該不會下99999[五位]條語法)
結論:適合大型應用,從業務上來講,有一個規則的編碼能體現產品的專業成度。(強烈推薦)
GUID生成Int64值後是否還具備惟一性測試
測試環境
主要測試思路:
1. 根據內核數使用多線程併發生成Guid後再轉爲Int64位值,放入集合A、B、…N,多少個線程就有多少個集合。
2. 再使用Dictionary字典高效查key的特性,將步驟1中生成的多個集合所有加到Dictionary中,看是否有重複值。
示例註解:測了 Dictionary<long,bool> 最大容量就在5999470左右,因此每次併發生成的惟一值總數控制在此範圍內,讓測試達到最有效話。
主要代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
for
(
int
i = 0; i <= Environment.ProcessorCount - 1; i++)
{
ThreadPool.QueueUserWorkItem(
(list) =>
{
List<
long
> tempList = list
as
List<
long
>;
for
(
int
j = 1; j < listLength; j++)
{
byte
[] buffer = Guid.NewGuid().ToByteArray();
tempList.Add(BitConverter.ToInt64(buffer, 0));
}
barrier.SignalAndWait();
}, totalList[i]);
}
|
測試數據截圖:
數據一(循環1000次,測試數:1000*5999470)
數據二(循環5000次,測試數:5000*5999470)--跑了一個晚上……
感謝@Justany_WhiteSnow的專業回答:(你們分析下,我數學比較差,稍後再說本身的理解)
GUID桶數量:(2 ^ 4) ^ 32 = 2 ^ 128
Int64桶數量: 2 ^ 64
假若每一個桶的機會是均等的,則每一個桶的GUID數量爲:
(2 ^ 128) / (2 ^ 64) = 2 ^ 64 = 18446744073709551616
也就是說,其實重複的機會是有的,只是機率問題。
樓主測試數是29997350000,發生重複的機率是:
1 - ((1 - (1 / (2 ^ 64))) ^ 29997350000) ≈ 1 - ((1 - 1 / (2 ^ 64)) ^ (2 ^ 32)) < 1 - 1 + 1 / (2 ^ 32) = 1 / (2 ^ 32) ≈ 2.3283064e-10
(惟一性、業務適合性是能夠權衡的,這個惟一性確定比不過GUID的,通常程序上都會安排錯誤處理機制,好比異常後執行一次重插的方案……)
(惟一性、業務適合性是能夠權衡的,這個惟一性確定比不過GUID的,通常程序上都會安排錯誤處理機制,好比異常後執行一次重插的方案……)
結論:GUID轉爲Int64值後,也具備高惟一性,可使用與項目中。
Random生成高惟一性隨機碼
我使用了五種Random生成方案,要Random生成惟一主要因素就是種子參數要惟一。(這是比較久之前寫的測試案例了,一直找不到合適的博文放,今天終於找到合適的地方了)
不過該測試是在單線程下的,多線程應使用不一樣的Random實例,因此對結果影響不會太大。
1. 使用Environment.TickCount作爲Random參數(即Random的默認參數),重複性最大。
2. 使用DateTime.Now.Ticks作爲Random參數,存在重複。
3. 使用unchecked((int)DateTime.Now.Ticks)作爲Random參數,存在重複。
4. 使用Guid.NewGuid().GetHashCode()作爲random參數,測試不存在重複(或存在性極小)。
5. 使用RNGCryptoServiceProvider作爲random參數,測試不存在重複(或存在性極小)。
即:
static int GetRandomSeed()
{
byte[] bytes = new byte[4];
System.Security.Cryptography.RNGCryptoServiceProvider rng
= new System.Security.Cryptography.RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return BitConverter.ToInt32(bytes, 0);
}
測試結果:
結論:隨機碼使用RNGCryptoServiceProvider或Guid.NewGuid().GetHashCode()生成的惟一性較高。
一些精彩評論(部分更新到原博文對應的地方)
1、
數據庫文件體積只是一個參考值,可水平擴展系統性能(如nosql,緩存系統)並不和文件體積有高指數的線性相關。
如taobao/qq的系統比拼byte系統慢,關鍵在於索引的命中率,緩存,系統的水平擴展。
若是數據庫不多,你搞這麼多byte能提升性能?
若是數據庫很大,你搞這麼多byte不兼容索引不兼容緩存,不是害自已嗎?
若是數據庫要求伸縮性,你搞這麼多byte,須要不斷改程序,不是自找苦嗎?
若是數據庫要求移植性,你搞這麼多byte,移植起來不如從新設計,這是否是不少公司不斷加班的緣由?
不依賴於數據存儲系統是分層設計思想的精華,實現戰略性能最大化,而不是追求戰術單機性能最大化。
不要迷信數據庫性能,不要迷信三範式,不要使用外鍵,不要使用byte,不要使用自增id,不要使用存儲過程,不要使用內部函數,不要使用非標準sql,存儲系統只作存儲系統的事。當出現系統性能時,如此設計的數據庫能夠更好的實現遷移數據庫(如mysql->oracle),實現nosql改造((mongodb/hadoop),實現key-value緩存(redis,memcache)。
2、
不少程序員有對性能認識有誤區,如使用存儲過程代替正常程序,其實使用存儲過程只是追求單服務器的高性能,當須要服務器水平擴展時,存儲過程當中的業務邏輯就是你的噩運。
3、
除數字日期,能用字符串存儲的字段儘可能使用字符串存儲,不要爲節省那不值錢的1個g的硬盤而使用相似字節之類的字段,進而大幅犧牲系統可伸縮性和可擴展性。
不要爲了追求所謂的性能,引入byte,使用byte註定是短命和難於移植,想一想爲何html,email一直流行,由於它們使用的是字符串表示法,只要有人類永遠都能解析,如email把二進制轉成base64存儲。除了實時系統,視頻外,建議使用字符串來存儲數據,系統性能的關鍵在於分佈式,在於水平擴展。
本次博文到此結束,但願你們對本次主題「如何在高併發分佈式系統中生成全局惟一Id」多提出本身寶貴的意見。另外看着感受舒服,還請多幫推薦…推薦……