又一個多月沒冒泡了,其實最近學(xué)了些東西,但是沒有安排時間整理成博文,后續(xù)再奉上。最近還寫了一個發(fā)郵件的組件以及性能測試請看 《NET開發(fā)郵件發(fā)送功能的全面教程(含郵件組件源碼)》 ?,還弄了個MSSQL參數(shù)化語法生成器,會在9月整理出來,有興趣的園友可以關(guān)注下我的博客。
?
分享原由,最近公司用到,并且在找最合適的方案,希望大家多參與討論和提出新方案。我和我的小伙伴們也討論了這個主題,我受益匪淺啊……
?
博文示例:
?
今天分享的主題是:如何在高并發(fā)分布式系統(tǒng)中生成全局唯一Id。
但這篇博文實際上是“半分享半討論”的博文:
1)???????? 半分享是我將說下我所了解到的關(guān)于今天主題所涉及的幾種方案。
2)???????? 半討論是我希望大家對各個方案都說說自己的見解,更加希望大家能提出更好的方案。(我還另外提問在此: http://q.cnblogs.com/q/53552/ 上面已有幾位園友回復(fù)(感謝dudu站長的參與),若你們有見解和新方案就在本博文留言吧,方便我整理更新到博文中,謝謝!)
?
我了解的方案如下……………………………………………………………………
1、? 使用數(shù)據(jù)庫自增Id
優(yōu)勢:編碼簡單,無需考慮記錄唯一標(biāo)識的問題。
缺陷:
1)???????? 在大表做水平分表時,就不能使用自增Id,因為Insert的記錄插入到哪個分表依分表規(guī)則判定決定,若是自增Id,各個分表中Id就會重復(fù),在做查詢、刪除時就會有異常。
2)???????? 在對表進行高并發(fā)單記錄插入時需要加入事物機制,否則會出現(xiàn)Id重復(fù)的問題。
3)???????? 在業(yè)務(wù)上操作父、子表(即關(guān)聯(lián)表)插入時,需要在插入數(shù)據(jù)庫 之前 獲取max(id)用于標(biāo)識父表和子表關(guān)系,若存在并發(fā)獲取max(id)的情況,max(id)會同時被別的線程獲取到。
4)???????? 等等。
結(jié)論:適合小應(yīng)用,無需分表,沒有高并發(fā)性能要求。
2、? 單獨開一個數(shù)據(jù)庫,獲取全局唯一的自增序列號或各表的MaxId
1)???????? 使用自增序列號表
專門一個數(shù)據(jù)庫,生成序列號。開啟事物,每次操作插入時,先將數(shù)據(jù)插入到序列表并返回自增序列號用于做為唯一Id進行業(yè)務(wù)數(shù)據(jù)插入。
注意:需要定期清理序列表的數(shù)據(jù)以保證獲取序列號的效率;插入序列表記錄時要開啟事物。
使用此方案的問題是:每次的查詢序列號是一個性能損耗;如果這個序列號列暴了,那就杯具了,你不知道哪個表使用了哪個序列,所以就必須換另一種唯一Id方式如GUID。
2)???????? 使用MaxId表存儲各表的MaxId值
專門一個數(shù)據(jù)庫,記錄各個表的MaxId值,建一個存儲過程來取Id,邏輯大致為:開啟事物,對于在表中不存在記錄,直接返回一個默認(rèn)值為1的鍵值,同時插入該條記錄到table_key表中。而對于已存在的記錄,key值直接在原來的key基礎(chǔ)上加1更新到MaxId表中并返回key。
使用此方案的問題是:每次的查詢MaxId是一個性能損耗;不過不會像自增序列表那么容易列暴掉,因為是擺表進行劃分的。
詳細(xì)可參考: 《使用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
|
第一步:創(chuàng)建表
create
table
table_key
(
???????
table_name??
varchar
(50)
not
null
primary
key
,
???????
key_value???
int
????????
not
null
)
?
?
第二步:創(chuàng)建存儲過程來取自增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
|
感謝園友的好建議:
- ( @輝_輝 )建議給table_key中為每個表初始化一條key為1的記錄,這樣就不用每次if來判斷了。
- ( @樂活的CodeMonkey )建議給存儲過程中 數(shù)據(jù)庫事物隔離級別 提高一下,因為出現(xiàn)在CS代碼層上使用如下事物代碼會導(dǎo)致并發(fā)重復(fù)問題.
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))
{
????????
//調(diào)用存儲過程
}
|
在咨詢過DBA后,這個存儲過程提高數(shù)據(jù)庫隔離級別會加大數(shù)據(jù)庫訪問壓力,導(dǎo)致響應(yīng)超時問題。所以這個建議我們只能在代碼編寫宣導(dǎo)上做。
- ( @土豆烤肉 )存儲過程中不使用事物,一旦使用到事物性能就急劇下滑。直接使用UPDATE獲取到的更新鎖,即SQL SERVER會保證UPDATE的順序執(zhí)行。(已在用戶過千萬的并發(fā)系統(tǒng)中使用)
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
|
結(jié)論:適用中型應(yīng)用,此方案解決了分表,關(guān)聯(lián)表插入記錄的問題。但是無法滿足高并發(fā)性能要求。同時也存在單點問題,如果這個數(shù)據(jù)庫cash 掉的話……
我們目前正頭痛這個問題,因為我們的高并發(fā)常常出現(xiàn)數(shù)據(jù)庫訪問超時,瓶頸就在這個MaxId表。我們也有考慮使用分布式緩存(eg:memcached)緩存第一次訪問MaxId表數(shù)據(jù),以提高再次訪問速度,并定時用緩存數(shù)據(jù)更新一次MaxId表,但我們擔(dān)心的問題是:
a)???????? 倘若緩存失效或暴掉了,那緩存的MaxId沒有更新到數(shù)據(jù)庫導(dǎo)致數(shù)據(jù)丟失,必須停掉站點來執(zhí)行Select max(id)各個表來同步MaxId表。
b)???????? 分布式緩存不是一保存下去,其他服務(wù)器上就立馬可以獲取到的,即數(shù)據(jù)存在不確定性。(其實也是緩存的一個誤用,緩存應(yīng)該用來存的是頻繁訪問并且很少改動的內(nèi)容)
???????? 改進方案:
整體思想:建立兩臺以上的數(shù)據(jù)庫ID生成服務(wù)器,每個服務(wù)器都有一張記錄各表當(dāng)前ID的MaxId表,但是MaxId表中Id的增長步長是服務(wù)器的數(shù)量,起始值依次錯開,這樣相當(dāng)于把ID的生成散列到每個服務(wù)器節(jié)點上。例如:如果我們設(shè)置兩臺數(shù)據(jù)庫ID生成服務(wù)器,那么就讓一臺的MaxId表的Id起始值為1(或當(dāng)前最大Id+1),每次增長步長為2,另一臺的MaxId表的ID起始值為2(或當(dāng)前最大Id+2),每次步長也為2。這樣就將產(chǎn)生ID的壓力均勻分散到兩臺服務(wù)器上,同時配合應(yīng)用程序控制,當(dāng)一個服務(wù)器失效后,系統(tǒng)能自動切換到另一個服務(wù)器上獲取ID,從而解決的單點問題保證了系統(tǒng)的容錯。(Flickr思想)
但是要注意:1、多服務(wù)器就必須面臨負(fù)載均衡的問題;2、倘若添加新節(jié)點,需要對原有數(shù)據(jù)重新根據(jù)步長計算遷移數(shù)據(jù)。
結(jié)論:適合大型應(yīng)用,生成 Id 較短,友好性比較好。(強烈推薦)
3、? Sequence特性
這個特性在SQL Server 2012、Oracle中可用。這個特性是數(shù)據(jù)庫級別的,允許在多個表之間共享序列號。它可以解決分表在同一個數(shù)據(jù)庫的情況,但倘若分表放在不同數(shù)據(jù)庫,那將共享不到此序列號。(eg:Sequence使用場景:你需要在多個表之間公用一個流水號。以往的做法是額外建立一個表,然后存儲流水號)
相關(guān)Sequence特性資料:
SQL Server2012中的SequenceNumber嘗試
SQL Server 2012 開發(fā)新功能——序列對象(Sequence)
Difference between Identity and Sequence in SQL Server 2012
結(jié)論:適用中型應(yīng)用,此方案不能完全解決分表問題, 而且無法滿足高并發(fā)性能要求。同時也存在單點問題,如果這個數(shù)據(jù)庫cash掉的話……
4、? 通過數(shù)據(jù)庫集群編號+集群內(nèi)的自增類型兩個字段共同組成唯一主鍵
優(yōu)點:實現(xiàn)簡單,維護也比較簡單。
缺點:關(guān)聯(lián)表操作相對比較復(fù)雜,需要兩個字段。并且業(yè)務(wù)邏輯必須是一開始就設(shè)計為處理復(fù)合主鍵的邏輯,倘若是到了后期,由單主鍵轉(zhuǎn)為復(fù)合主鍵那改動成本就太大了。
結(jié)論:適合大型應(yīng)用,但需要業(yè)務(wù)邏輯配合處理復(fù)合主鍵。
5、? 通過設(shè)置每個集群中自增 ID 起始點(auto_increment_offset),將各個集群的ID進行絕對的分段來實現(xiàn)全局唯一。當(dāng)遇到某個集群數(shù)據(jù)增長過快后,通過命令調(diào)整下一個 ID 起始位置跳過可能存在的沖突。
優(yōu)點:實現(xiàn)簡單,且比較容易根據(jù) ID 大小直接判斷出數(shù)據(jù)處在哪個集群,對應(yīng)用透明。缺點:維護相對較復(fù)雜,需要高度關(guān)注各個集群 ID 增長狀況。
結(jié)論:適合大型應(yīng)用,但需要高度關(guān)注各個集群 ID 增長狀況。
6、? GUID(Globally Unique Identifier,全局唯一標(biāo)識符)
GUID通常表示成32個16進制數(shù)字(0-9,A-F)組成的字符串,如:{21EC2020-3AEA-1069-A2DD-08002B30309D},它實質(zhì)上是一個128位長的二進制整數(shù)。
GUID制定的算法中使用到用戶的網(wǎng)卡MAC地址,以保證在計算機集群中生成唯一GUID;在相同計算機上隨機生成兩個相同GUID的可能性是非常小的,但并不為0。所以,用于生成GUID的算法通常都加入了非隨機的參數(shù)(如時間),以保證這種重復(fù)的情況不會發(fā)生。
優(yōu)點:GUID是最簡單的方案,跨平臺,跨語言,跨業(yè)務(wù)邏輯,全局唯一的Id,數(shù)據(jù)間同步、遷移都能簡單實現(xiàn)。
缺點:
1)???????? 存儲占了32位,且無可讀性,返回GUID給客戶顯得很不專業(yè);
2)???????? 占用了珍貴的聚集索引,一般我們不會根據(jù)GUID去查單據(jù),并且插入時因為GUID是無需的,在聚集索引的排序規(guī)則下可能移動大量的記錄。
有兩位園友主推GUID,無須順序GUID方案原因如下:
@徐少俠 ?????????? GUID無序在并發(fā)下效率高,并且一個數(shù)據(jù)頁內(nèi)添加新行,是在B樹內(nèi)增加,本質(zhì)沒有什么數(shù)據(jù)被移動, 唯一可能的,是頁填充因子滿了 ,需要拆頁。而GUID方案導(dǎo)致的拆頁比 順序 ID要低太多了(數(shù)據(jù)庫不是很懂,暫時無法斷定,大家自己認(rèn)識)
@無色 ??????????????? 我們要明白id是什么,是身份標(biāo)識,標(biāo)識身份是id最大的業(yè)務(wù)邏輯,不要引入什么時間,什么用戶業(yè)務(wù)邏輯, 那是另外一個字段干的事 ,使用base64(guid,uuid),是通盤考慮,完全可以更好的兼容nosql,key-value存儲。
(推薦),但是倘若你系統(tǒng)一開始沒有規(guī)劃一個業(yè)務(wù) Id ,那么將導(dǎo)致大量的改動,所以這個方案的最佳狀態(tài)是一開始就設(shè)計業(yè)務(wù) Id ,當(dāng)然業(yè)務(wù) Id 的唯一性也是我們要考慮的。
結(jié)論:適合大型應(yīng)用;生成的Id 不夠友好;占據(jù)了32 位;索引效率較低。
改進:
1)???????? ( @dudu 提點)在SQL Server 2005中新增了NEWSEQUENTIALID函數(shù)。
詳細(xì)請看: 《理解newid()和newsequentialid()》
在指定計算機上創(chuàng)建大于先前通過該函數(shù)生成的任何 GUID 的 GUID。 newsequentialid 產(chǎn)生的新的值是有規(guī)律的,則索引B+樹的變化是有規(guī)律的,就不會導(dǎo)致索引列插入時移動大量記錄的問題。
但一旦服務(wù)器重新啟動,其再次生成的GUID可能反而變小(但仍然保持唯一)。這在很大程度上提高了索引的性能,但并不能保證所生成的GUID一直增大。 SQL 的這個函數(shù)產(chǎn)生的GUID 很簡單就可以預(yù)測,因此不適合用于安全目的。
a)???????? 只能做為數(shù)據(jù)庫列的DEFAULT VALUE,不能執(zhí)行類似SELECT NEWSEQUENTIALID()的語句.
b)???????? 如何獲得生成的GUID.
如果生成的GUID所在字段做為外鍵要被其他表使用,我們就需要得到這個生成的值。通常,PK是一個IDENTITY字段,我們可以在INSERT之后執(zhí)行 SELECT SCOPE_IDENTITY()來獲得新生成的ID,但是由于NEWSEQUENTIALID()不是一個INDETITY類型,這個辦法是做不到了,而他本身又只能在默認(rèn)值中使用,不可以事先SELECT好再插入,那么我們?nèi)绾蔚玫侥兀坑幸韵聝煞N方法:
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. 標(biāo)記ID字段為ROWGUID(一個表只能有一個ROWGUID)
INSERT
INTO
TABLE1(col1, col2)
VALUES
(
'value1'
,
'value2'
)
--在這里,ROWGUIDCOL其實相當(dāng)于一個別名
SELECT
ROWGUIDCOL
FROM
TABLE1
|
結(jié)論:適合大型應(yīng)用,解決了GUID 無序特性導(dǎo)致索引列插入移動大量記錄的問題。但是在關(guān)聯(lián)表插入時需要返回數(shù)據(jù)庫中生成的GUID ;生成的Id 不夠友好;占據(jù)了32 位。
2)???????? “COMB”(combined guid/timestamp,意思是:組合GUID/時間截)
(感謝: @ ethan-luo ?, @lcs-帥 )
COMB數(shù)據(jù)類型的基本設(shè)計思路是這樣的:既然GUID數(shù)據(jù)因毫無規(guī)律可言造成索引效率低下,影響了系統(tǒng)的性能,那么能不能通過組合的方式,保留GUID的10個字節(jié),用另6個字節(jié)表示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);
}
|
結(jié)論:適合大型應(yīng)用。即保留 GUID 的唯一性的同時增加了 GUID 有序性,提高了索引效率;解決了關(guān)聯(lián)表業(yè)務(wù)問題;生成的 Id 不夠友好;占據(jù)了 32 位。(強烈推薦)
3)???????? 長度問題,使用Base64或Ascii85編碼解決。(要注意的是上述有序性方案在進行編碼后也會變得無序)
如:
GUID:{3F2504E0-4F89-11D3-9A0C-0305E82C3301}
當(dāng)需要使用更少的字符表示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);
?????????????????? 結(jié)論:適合大型應(yīng)用,縮短GUID 的長度。生成的Id 不夠友好;索引效率較低。
7、? GUID TO Int64
對于GUID的可讀性,有園友給出如下方案:(感謝: @黑色的羽翼 )
1
2
3
4
5
6
7
8
|
/// <summary>
/// 根據(jù)GUID獲取19位的唯一數(shù)字序列
/// </summary>
public
static
long
GuidToLongID()
{
????
byte
[] buffer = Guid.NewGuid().ToByteArray();
????
return
BitConverter.ToInt64(buffer, 0);
}
|
即將GUID轉(zhuǎn)為了19位數(shù)字,數(shù)字反饋給客戶可以一定程度上緩解友好性問題。EG:
GUID: cfdab168-211d-41e6-8634-ef5ba6502a22??? (不友好)
Int64: 5717212979449746068????????????????????????????????????? (友好性還行)
不過我的小伙伴說ToInt64后就不唯一了。因此我專門寫了個并發(fā)測試程序,后文將給出測試結(jié)果截圖及代碼簡單說明。
(唯一性、業(yè)務(wù)適合性是可以權(quán)衡的,這個唯一性肯定比不過GUID的,一般程序上都會安排錯誤處理機制,比如異常后執(zhí)行一次重插的方案……)
結(jié)論:適合大型應(yīng)用,生成相對友好的Id (純數(shù)字)-- ----因簡單和業(yè)務(wù)友好性而推薦。
8、? 自己寫編碼規(guī)則
優(yōu)點:全局唯一Id,符合業(yè)務(wù)后續(xù)長遠(yuǎn)的發(fā)展(可能具體業(yè)務(wù)需要自己的編碼規(guī)則等等)。
缺陷:根據(jù)具體編碼規(guī)則實現(xiàn)而不同;還要考慮倘若主鍵在業(yè)務(wù)上允許改變的,會帶來外鍵同步的麻煩。
我這邊寫兩個編碼規(guī)則方案: (可能不唯一,只是個人方案,也請大家提出自己的編碼規(guī)則)
1)???????? 12位年月日時分秒+3位服務(wù)器編碼+3位表編碼+5位隨機碼? (這樣就完全單機完成生成全局唯一編碼)---共23位
缺陷:因為附帶隨機碼,所以編碼缺少一定的順序感。(生成高唯一性隨機碼的方案稍后給給出程序)
2)???????? 12位年月日時分秒+3位服務(wù)器編碼+3位表編碼+5位流水碼? (這樣流水碼就需要結(jié)合數(shù)據(jù)庫和緩存)---共23位
缺陷:因為使用到流水碼,流水碼的生成必然會遇到和MaxId、序列表、Sequence方案中類似的問題
(為什么沒有毫秒?毫秒也不具備業(yè)務(wù)可讀性,我改用5位隨機碼、流水碼代替,推測1秒內(nèi)應(yīng)該不會下99999[五位]條語法)
?
結(jié)論:適合大型應(yīng)用,從業(yè)務(wù)上來說,有一個規(guī)則的編碼能體現(xiàn)產(chǎn)品的專業(yè)成度。(強烈推薦)
?
?
GUID生成Int64值后是否還具有唯一性測試
測試環(huán)境
?
主要測試思路:
- 根據(jù)內(nèi)核數(shù)使用多線程并發(fā)生成Guid后再轉(zhuǎn)為Int64位值,放入集合A、B、…N,多少個線程就有多少個集合。
- 再使用Dictionary字典高效查key的特性,將步驟1中生成的多個集合全部加到Dictionary中,看是否有重復(fù)值。
示例注解:測了 Dictionary<long,bool> 最大容量就在5999470左右,所以每次并發(fā)生成的唯一值總數(shù)控制在此范圍內(nèi),讓測試達到最有效話。
主要代碼:
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]);
}
|
測試數(shù)據(jù)截圖:???????????????????????????????????????????????????????????????????????????
?
?
數(shù)據(jù)一(循環(huán)1000次,測試數(shù):1000*5999470)
?
數(shù)據(jù)二(循環(huán)5000次,測試數(shù):5000*5999470)--跑了一個晚上……
?
?
感謝 @Justany_WhiteSnow 的專業(yè)回答:( 大家分析下,我數(shù)學(xué)比較差,稍后再說自己的理解)
GUID桶數(shù)量:(2 ^ 4) ^ 32 = 2 ^ 128
Int64桶數(shù)量: 2 ^ 64
倘若每個桶的機會是均等的,則每個桶的GUID數(shù)量為:
(2 ^ 128) / (2 ^ 64) = 2 ^ 64 = 18446744073709551616
也就是說,其實重復(fù)的機會是有的,只是概率問題。
樓主測試數(shù)是29997350000,發(fā)生重復(fù)的概率是:
1 - ((1 - (1 / (2 ^ 64))) ^ 29997350000) ≈ 1 - ((1 - 1 / (2 ^ 64)) ^ (2 ^ 32)) < 1 - 1 + 1 / (2 ^ 32) = 1 / (2 ^ 32) ≈ 2.3283064e-10
(唯一性、業(yè)務(wù)適合性是可以權(quán)衡的,這個唯一性肯定比不過GUID的,一般程序上都會安排錯誤處理機制,比如異常后執(zhí)行一次重插的方案……)
(唯一性、業(yè)務(wù)適合性是可以權(quán)衡的,這個唯一性肯定比不過GUID的,一般程序上都會安排錯誤處理機制,比如異常后執(zhí)行一次重插的方案……)
結(jié)論:GUID 轉(zhuǎn)為Int64 值后,也具有高唯一性,可以使用與項目中。
?
Random生成高唯一性隨機碼
我使用了五種Random生成方案,要Random生成唯一主要因素就是種子參數(shù)要唯一。(這是比較久以前寫的測試案例了,一直找不到合適的博文放,今天終于找到合適的地方了)
不過該測試是在單線程下的,多線程應(yīng)使用不同的Random實例,所以對結(jié)果影響不會太大。
- 使用Environment.TickCount做為Random參數(shù)(即Random的默認(rèn)參數(shù)),重復(fù)性最大。
- 使用DateTime.Now.Ticks做為Random參數(shù),存在重復(fù)。
- 使用unchecked((int)DateTime.Now.Ticks)做為Random參數(shù),存在重復(fù)。
- 使用Guid.NewGuid().GetHashCode()做為random參數(shù),測試不存在重復(fù)(或存在性極小)。
- 使用RNGCryptoServiceProvider做為random參數(shù),測試不存在重復(fù)(或存在性極小)。
即:
??????? 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);
??????? }
測試結(jié)果:
?
結(jié)論:隨機碼使用RNGCryptoServiceProvider 或Guid.NewGuid().GetHashCode() 生成的唯一性較高。
?
?
一些精彩評論(部分更新到原博文對應(yīng)的地方)
一、
數(shù)據(jù)庫文件體積只是一個參考值,可水平擴展系統(tǒng)性能(如nosql,緩存系統(tǒng))并不和文件體積有高指數(shù)的線性相關(guān)。
如taobao/qq的系統(tǒng)比拼byte系統(tǒng)慢,關(guān)鍵在于索引的命中率,緩存,系統(tǒng)的水平擴展。
如果數(shù)據(jù)庫很少,你搞這么多byte能提高性能?
如果數(shù)據(jù)庫很大,你搞這么多byte不兼容索引不兼容緩存,不是害自已嗎?
如果數(shù)據(jù)庫要求伸縮性,你搞這么多byte,需要不斷改程序,不是自找苦嗎?
如果數(shù)據(jù)庫要求移植性,你搞這么多byte,移植起來不如重新設(shè)計,這是不是很多公司不斷加班的原因?
?
不依賴于數(shù)據(jù)存儲系統(tǒng)是分層設(shè)計思想的精華,實現(xiàn)戰(zhàn)略性能最大化,而不是追求戰(zhàn)術(shù)單機性能最大化。
?
不要迷信數(shù)據(jù)庫性能,不要迷信三范式,不要使用外鍵,不要使用byte,不要使用自增id,不要使用存儲過程,不要使用內(nèi)部函數(shù),不要使用非標(biāo)準(zhǔn)sql,存儲系統(tǒng)只做存儲系統(tǒng)的事。當(dāng)出現(xiàn)系統(tǒng)性能時,如此設(shè)計的數(shù)據(jù)庫可以更好的實現(xiàn)遷移數(shù)據(jù)庫(如mysql->oracle),實現(xiàn)nosql改造((mongodb/hadoop),實現(xiàn)key-value緩存(redis,memcache)。
?
二、
很多程序員有對性能認(rèn)識有誤區(qū),如使用存儲過程代替正常程序,其實使用存儲過程只是追求單服務(wù)器的高性能,當(dāng)需要服務(wù)器水平擴展時,存儲過程中的業(yè)務(wù)邏輯就是你的噩運。
?
三、
除數(shù)字日期,能用字符串存儲的字段盡量使用字符串存儲,不要為節(jié)省那不值錢的1個g的硬盤而使用類似字節(jié)之類的字段,進而大幅犧牲系統(tǒng)可伸縮性和可擴展性。
不要為了追求所謂的性能,引入byte,使用byte注定是短命和難于移植,想想為什么html,email一直流行,因為它們使用的是字符串表示法,只要有人類永遠(yuǎn)都能解析,如email把二進制轉(zhuǎn)成base64存儲。除了實時系統(tǒng),視頻外,建議使用字符串來存儲數(shù)據(jù),系統(tǒng)性能的關(guān)鍵在于分布式,在于水平擴展。
?
?
本次博文到此結(jié)束,希望大家對本次主題“如何在高并發(fā)分布式系統(tǒng)中生成全局唯一Id”多提出自己寶貴的意見。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
