mysql分庫分表(二)

一種可以避免數據遷移的分庫分表scale-out擴容模式

一種可以避免數據遷移的分庫分表scale-out擴容方式

目前絕大多數應用採取的兩種分庫分表規則

  1. mod方式
  2. dayofweek系列日期方式(所有星期1的數據在一個庫/表,或所有?月份的數據在一個庫表)

這兩種方式有個本質的特點,就是離散性加週期性。

例如以一個表的主鍵對3取餘數的方式分庫或分表:

那麼隨着數據量的增大,每個表或庫的數據量都是各自增長。當一個表或庫的數據量增長到了一個極限,要加庫或加表的時候,
介於這種分庫分表算法的離散性,必需要做數據遷移才能完成。例如從3個擴展到5個的時候:

需要將原先以mod3分類的數據,重新以mod5分類,不可避免的帶來數據遷移。每個表的數據都要被重新分配到多個新的表
相似的例子比如從dayofweek分的7個庫/表,要擴張爲以dayofmonth分的31張庫/表,同樣需要進行數據遷移。

數據遷移帶來的問題是

  1. 業務至少要兩次發佈
  2. 要專門寫工具來導數據。由於各業務之間的差別,很難做出統一的工具。目前幾乎都是每個業務寫一套
  3. 要解決增量、全量、時間點,數據不一致等問題

如何在數據量擴張到現有庫表極限,加庫加表時避免數據遷移呢?
通常的數據增長往往是隨着時間的推移增長的。隨着業務的開展,時間的推移,數據量不斷增加。(不隨着時間增長的情況,
例如某天突然需要從另一個系統導入大量數據,這種情況完全可以由dba依據現有的分庫分表規則來導入,因此不考慮這種問題。)

考慮到數據增長的特點,如果我們以代表時間增長的字段,按遞增的範圍分庫,則可以避免數據遷移
例如,如果id是隨着時間推移而增長的全局sequence,則可以以id的範圍來分庫:(全局sequence可以用tddl現在的方式也可以用ZooKeeper實現)
id在 0–100萬在第一個庫中,100-200萬在第二個中,200-300萬在第3箇中 (用M代表百萬數據)

或者以時間字段爲例,比如一個字段表示記錄的創建時間,以此字段的時間段分庫gmt_create_time in range

這樣的方式下,在數據量再增加達到前幾個庫/表的上限時,則繼續水平增加庫表,原先的數據就不需要遷移了
但是這樣的方式會帶來一個熱點問題:當前的數據量達到某個庫表的範圍時,所有的插入操作,都集中在這個庫/表了。

所以在滿足基本業務功能的前提下,分庫分表方案應該儘量避免的兩個問題:

1. 數據遷移
2. 熱點

如何既能避免數據遷移又能避免插入更新的熱點問題呢?
結合離散分庫/分表和連續分庫/分表的優點,如果一定要寫熱點和新數據均勻分配在每個庫,同時又保證易於水平擴展,可以考慮這樣的模式:

【水平擴展scale-out方案模式一】

階段一:一個庫DB0之內分4個表,id%4 :

階段二:增加db1庫,t2和t3整表搬遷到db1

階段三:增加DB2和DB3庫,t1整表搬遷到DB2,t3整表搬遷的DB3:

爲了規則表達,通過內部名稱映射或其他方式,我們將DB1和DB2的名稱和位置互換得到下圖:

dbRule: “DB” + (id % 4)
tbRule: “t”  + (id % 4)

這樣3個階段的擴展方案中,每次次擴容只需要做一次停機發布,不需要做數據遷移。停機發布中只需要做整表搬遷。
這個相對於每個表中的數據重新分配來說,不管是開發做,還是DBA做都會簡單很多。

如果更進一步數據庫的設計和部署上能做到每個表一個硬盤,那麼擴容的過程只要把原有機器的某一塊硬盤拔下來,
插入到新的機器上,就完成整表搬遷了!可以大大縮短停機時間。

具體在mysql上可以以庫爲表。開始一個物理機上啓動4個數據庫實例,每次倍增機器,直接將庫搬遷到新的機器上。
這樣從始至終規則都不需要變化,一直都是:

dbRule: “DB” + (id % 4)
tbRule: “t”  + (id % 4)

即邏輯上始終保持4庫4表,每個表一個庫。這種做法也是目前店鋪線圖片空間採用的做法。

上述方案有一個缺點,就是在從一個庫到4個庫的過程中,單表的數據量一直在增長。當單表的數據量超過一定範圍時,可能會帶來性能問題。比如索引的問題,歷史數據清理的問題。
另外當開始預留的表個數用盡,到了4物理庫每庫1個表的階段,再進行擴容的話,不可避免的要從表上下手。那麼我們來考慮表內數據上限不增長的方案:

【水平擴展scale-out方案模式二】

階段一:一個數據庫,兩個表,rule0 = id % 2

分庫規則dbRule: “DB0″
分表規則tbRule: “t” + (id % 2)

階段二:當單庫的數據量接近1千萬,單表的數據量接近500萬時,進行擴容(數據量只是舉例,具體擴容量要根據數據庫和實際壓力狀況決定):
增加一個數據庫DB1,將DB0.t1整表遷移到新庫DB1。
每個庫各增加1個表,未來10M-20M的數據mod2分別寫入這2個表:t0_1,t1_1:

分庫規則dbRule:

“DB” + (id % 2)

分表規則tbRule:

    if(id < 1千萬){
        return "t"+ (id % 2);   //1千萬之前的數據,仍然放在t0和t1表。t1表從DB0搬遷到DB1庫
    }else if(id < 2千萬){
        return "t"+ (id % 2) +"_1"; //1千萬之後的數據,各放到兩個庫的兩個表中: t0_1,t1_1
    }else{
        throw new IllegalArgumentException("id outof range[20000000]:" + id);
    }

這樣10M以後的新生數據會均勻分佈在DB0和DB1; 插入更新和查詢熱點仍然能夠在每個庫中均勻分佈。
每個庫中同時有老數據和不斷增長的新數據。每表的數據仍然控制在500萬以下。

階段三:當兩個庫的容量接近上限繼續水平擴展時,進行如下操作:
新增加兩個庫:DB2和DB3. 以id % 4分庫。餘數0、1、2、3分別對應DB的下標. t0和t1不變,
將DB0.t0_1整表遷移到DB2; 將DB1.t1_1整表遷移到DB3
20M-40M的數據mod4分爲4個表:t0_2,t1_2,t2_2,t3_2,分別放到4個庫中:

新的分庫分表規則如下:

分庫規則dbRule:

  if(id < 2千萬){
      //2千萬之前的數據,4個表分別放到4個庫
      if(id < 1千萬){
          return "db"+  (id % 2);     //原t0表仍在db0, t1表仍在db1
      }else{
          return "db"+ ((id % 2) +2); //原t0_1表從db0搬遷到db2; t1_1表從db1搬遷到db3
      }
  }else if(id < 4千萬){
      return "db"+ (id % 4);          //超過2千萬的數據,平均分到4個庫
  }else{
      throw new IllegalArgumentException("id out of range. id:"+id);
  }

分表規則tbRule:

  if(id < 2千萬){        //2千萬之前的數據,表規則和原先完全一樣,參見階段二
      if(id < 1千萬){
          return "t"+ (id % 2);       //1千萬之前的數據,仍然放在t0和t1表
      }else{
          return "t"+ (id % 2) +"_1"; //1千萬之後的數據,仍然放在t0_1和t1_1表
      }
  }else if(id < 4千萬){
      return "t"+ (id % 4)+"_2";      //超過2千萬的數據分爲4個表t0_2,t1_2,t2_2,t3_2
  }else{
      throw new IllegalArgumentException("id out of range. id:"+id);
  }

隨着時間的推移,當第一階段的t0/t1,第二階段的t0_1/t1_1逐漸成爲歷史數據,不再使用時,可以直接truncate掉整個表。省去了歷史數據遷移的麻煩。

上述3個階段的分庫分表規則在TDDL2.x中已經全部支持,具體請諮詢TDDL團隊。

【水平擴展scale-out方案模式三】

非倍數擴展:如果從上文的階段二到階段三不希望一下增加兩個庫呢?嘗試如下方案:

遷移前:

新增庫爲DB2,t0、t1都放在DB0,
t0_1整表遷移到DB1
t1_1整表遷移到DB2

遷移後:

這時DB0退化爲舊數據的讀庫和更新庫。新增數據的熱點均勻分佈在DB1和DB2
4無法整除3,因此如果從4表2庫擴展到3個庫,不做行級別的遷移而又保證熱點均勻分佈看似無法完成。

當然如果不限制每庫只有兩個表,也可以如下實現:

小於10M的t0和t1都放到DB0,以mod2分爲兩個表,原數據不變
10M-20M的,以mod2分爲兩個表t0_1、t1_1,原數據不變,分別搬遷到DB1,和DB2
20M以上的以mod3平均分配到3個DB庫的t_0、t_2、t_3表中
這樣DB1包含最老的兩個表,和最新的1/3數據。DB1和DB2都分表包含次新的兩個舊錶t0_1、t1_1和最新的1/3數據。
新舊數據讀寫都可達到均勻分佈。

總而言之:
兩種規則映射(函數):

  1. 離散映射:如mod或dayofweek, 這種類型的映射能夠很好的解決熱點問題,但帶來了數據遷移和歷史數據問題。
  2. 連續映射;如按id或gmt_create_time的連續範圍做映射。這種類型的映射可以避免數據遷移,但又帶來熱點問題。

離散映射和連續映射這兩種相輔相成的映射規則,正好解決熱點和遷移這一對相互矛盾的問題。
我們之前只運用了離散映射,引入連續映射規則後,兩者結合,精心設計,
應該可以設計出滿足避免熱點和減少遷移之間任意權衡取捨的規則。

基於以上考量,分庫分表規則的設計和配置,長遠說來必須滿足以下要求

  1. 可以動態推送修改
  2. 規則可以分層級疊加,舊規則可以在新規則下繼續使用,新規則是舊規則在更寬尺度上的拓展,以此支持新舊規則的兼容,避免數據遷移
  3. 用mod方式時,最好選2的指數級倍分庫分表,這樣方便以後切割。

分表分庫後帶來問題(主鍵衝突)

主鍵衝突問題

分庫分表的環境中,數據分佈在不同的分片上,不能再借助數據庫自增長特性直接生成,否則會造成不同分片上的數據表主鍵會重複。
添加數據>主鍵生成中心>分庫決策中心>切換相應庫>執行添加

 

事務問題

在執行分庫分表之後,由於數據存儲到了不同的庫上,數據庫事務管理出現了困難。
如果依賴數據庫本身的分佈式事務管理功能去執行事務,將付出高昂的性能代價;
如果由應用程序去協助控制,形成程序邏輯上的事務,又會造成編程方面的負擔。


跨庫跨表的join問題

在執行了分庫分表之後,難以避免會將原本邏輯關聯性很強的數據劃分到不同的表、不同的庫上。
這時,表的關聯操作將受到限制,我們無法join位於不同分庫的表,也無法join分表粒度不同的表,結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。


數據庫Sharding的基本思想和切分策略

本文着重介紹sharding的基本思想和理論上的切分策略

一、基本思想

      Sharding的基本思想就要把一個數據庫切分成多個部分放到不同的數據庫(server)上,從而緩解單一數據庫的性能問題。不太嚴格的講,對於海量數據的數據庫,如果是因爲表多而數據多,這時候適合使用垂直切分,即把關係緊密(比如同一模塊)的表切分出來放在一個server上。如果表並不多,但每張表的數據非常多,這時候適合水平切分,即把表的數據按某種規則(比如按ID散列)切分到多個數據庫(server)上。當然,現實中更多是這兩種情況混雜在一起,這時候需要根據實際情況做出選擇,也可能會綜合使用垂直與水平切分,從而將原有數據庫切分成類似矩陣一樣可以無限擴充的數據庫(server)陣列。下面分別詳細地介紹一下垂直切分和水平切分.

      垂直切分的最大特點就是規則簡單,實施也更爲方便,尤其適合各業務之間的耦合度非
常低,相互影響很小,業務邏輯非常清晰的系統。在這種系統中,可以很容易做到將不同業
務模塊所使用的表分拆到不同的數據庫中。根據不同的表來進行拆分,對應用程序的影響也
更小,拆分規則也會比較簡單清晰。(這也就是所謂的”share nothing”)。



      水平切分於垂直切分相比,相對來說稍微複雜一些。因爲要將同一個表中的不同數據拆
分到不同的數據庫中,對於應用程序來說,拆分規則本身就較根據表名來拆分更爲複雜,後
期的數據維護也會更爲複雜一些。



      讓我們從普遍的情況來考慮數據的切分:一方面,一個庫的所有表通常不可能由某一張表全部串聯起來,這句話暗含的意思是,水平切分幾乎都是針對一小搓一小搓(實際上就是垂直切分出來的塊)關係緊密的表進行的,而不可能是針對所有表進行的。另一方面,一些負載非常高的系統,即使僅僅只是單個表都無法通過單臺數據庫主機來承擔其負載,這意味着單單是垂直切分也不能完全解決問明。因此多數系統會將垂直切分和水平切分聯合使用,先對系統做垂直切分,再針對每一小搓表的情況選擇性地做水平切分。從而將整個數據庫切分成一個分佈式矩陣。

 

二、切分策略

      如前面所提到的,切分是按先垂直切分再水平切分的步驟進行的。垂直切分的結果正好爲水平切分做好了鋪墊。垂直切分的思路就是分析表間的聚合關係,把關係緊密的表放在一起。多數情況下可能是同一個模塊,或者是同一“聚集”。這裏的“聚集”正是領域驅動設計裏所說的聚集。在垂直切分出的表聚集內,找出“根元素”(這裏的“根元素”就是領域驅動設計裏的“聚合根”),按“根元素”進行水平切分,也就是從“根元素”開始,把所有和它直接與間接關聯的數據放入一個shard裏。這樣出現跨shard關聯的可能性就非常的小。應用程序就不必打斷既有的表間關聯。比如:對於社交網站,幾乎所有數據最終都會關聯到某個用戶上,基於用戶進行切分就是最好的選擇。再比如論壇系統,用戶和論壇兩個模塊應該在垂直切分時被分在了兩個shard裏,對於論壇模塊來說,Forum顯然是聚合根,因此按Forum進行水平切分,把Forum裏所有的帖子和回帖都隨Forum放在一個shard裏是很自然的。

      對於共享數據數據,如果是隻讀的字典表,每個shard裏維護一份應該是一個不錯的選擇,這樣不必打斷關聯關係。如果是一般數據間的跨節點的關聯,就必須打斷。

 

      需要特別說明的是:當同時進行垂直和水平切分時,切分策略會發生一些微妙的變化。比如:在只考慮垂直切分的時候,被劃分到一起的表之間可以保持任意的關聯關係,因此你可以按“功能模塊”劃分表格,但是一旦引入水平切分之後,表間關聯關係就會受到很大的制約,通常只能允許一個主表(以該表ID進行散列的表)和其多個次表之間保留關聯關係,也就是說:當同時進行垂直和水平切分時,在垂直方向上的切分將不再以“功能模塊”進行劃分,而是需要更加細粒度的垂直切分,而這個粒度與領域驅動設計中的“聚合”概念不謀而合,甚至可以說是完全一致,每個shard的主表正是一個聚合中的聚合根!這樣切分下來你會發現數據庫分被切分地過於分散了(shard的數量會比較多,但是shard裏的表卻不多),爲了避免管理過多的數據源,充分利用每一個數據庫服務器的資源,可以考慮將業務上相近,並且具有相近數據增長速率(主表數據量在同一數量級上)的兩個或多個shard放到同一個數據源裏,每個shard依然是獨立的,它們有各自的主表,並使用各自主表ID進行散列,不同的只是它們的散列取模(即節點數量)必需是一致的。


1.事務問題:
解決事務問題目前有兩種可行的方案:分佈式事務和通過應用程序與數據庫共同控制實現事務下面對兩套方案進行一個簡單的對比。
方案一:使用分佈式事務
    優點:交由數據庫管理,簡單有效
    缺點:性能代價高,特別是shard越來越多時
方案二:由應用程序和數據庫共同控制
     原理:將一個跨多個數據庫的分佈式事務分拆成多個僅處
           於單個數據庫上面的小事務,並通過應用程序來總控
           各個小事務。
     優點:性能上有優勢
     缺點:需要應用程序在事務控制上做靈活設計。如果使用   
           了spring的事務管理,改動起來會面臨一定的困難。
2.跨節點Join的問題
      只要是進行切分,跨節點Join的問題是不可避免的。但是良好的設計和切分卻可以減少此類情況的發生。解決這一問題的普遍做法是分兩次查詢實現。在第一次查詢的結果集中找出關聯數據的id,根據這些id發起第二次請求得到關聯數據。

3.跨節點的count,order by,group by以及聚合函數問題
      這些是一類問題,因爲它們都需要基於全部數據集合進行計算。多數的代理都不會自動處理合並工作。解決方案:與解決跨節點join問題的類似,分別在各個節點上得到結果後在應用程序端進行合併。和join不同的是每個結點的查詢可以並行執行,因此很多時候它的速度要比單一大表快很多。但如果結果集很大,對應用程序內存的消耗是一個問題。

相關閱讀:

數據庫分庫分表(sharding)系列(五) 一種支持自由規劃無須數據遷移和修改路由代碼的Sharding擴容方案

數據庫分庫分表(sharding)系列(四) 多數據源的事務處理

數據庫分庫分表(sharding)系列(三) 關於使用框架還是自主開發以及sharding實現層面的考量

數據庫分庫分表(sharding)系列(二) 全局主鍵生成策略

數據庫分庫分表(sharding)系列(一) 拆分實施策略和示例演示

關於垂直切分Vertical Sharding的粒度

數據庫Sharding的基本思想和切分策略

 

數據庫分庫分表(sharding)系列(二) 全局主鍵生成策略

第一部分:一些常見的主鍵生成策略

一旦數據庫被切分到多個物理結點上,我們將不能再依賴數據庫自身的主鍵生成機制。一方面,某個分區數據庫自生成的ID無法保證在全局上是唯一的;另一方面,應用程序在插入數據之前需要先獲得ID,以便進行SQL路由。目前幾種可行的主鍵生成策略有:
1. UUID:使用UUID作主鍵是最簡單的方案,但是缺點也是非常明顯的。由於UUID非常的長,除佔用大量存儲空間外,最主要的問題是在索引上,在建立索引和基於索引進行查詢時都存在性能問題。
2. 結合數據庫維護一個Sequence表:此方案的思路也很簡單,在數據庫中建立一個Sequence表,表的結構類似於:

CREATE TABLE `SEQUENCE` (  
    `tablename` varchar(30) NOT NULL,  
    `nextid` bigint(20) NOT NULL,  
    PRIMARY KEY (`tablename`)  
) ENGINE=InnoDB   

 

每當需要爲某個表的新紀錄生成ID時就從Sequence表中取出對應表的nextid,並將nextid的值加1後更新到數據庫中以備下次使用。此方案也較簡單,但缺點同樣明顯:由於所有插入任何都需要訪問該表,該表很容易成爲系統性能瓶頸,同時它也存在單點問題,一旦該表數據庫失效,整個應用程序將無法工作。有人提出使用Master-Slave進行主從同步,但這也只能解決單點問題,並不能解決讀寫比爲1:1的訪問壓力問題。

除此之外,還有一些方案,像對每個數據庫結點分區段劃分ID,以及網上的一些ID生成算法,因爲缺少可操作性和實踐檢驗,本文並不推薦。實際上,接下來,我們要介紹的是Fickr使用的一種主鍵生成方案,這個方案是目前我所知道的最優秀的一個方案,並且經受了實踐的檢驗,可以爲大多數應用系統所借鑑。

 

第二部分:一種極爲優秀的主鍵生成策略

flickr開發團隊在2010年撰文介紹了flickr使用的一種主鍵生成測策略,同時表示該方案在flickr上的實際運行效果也非常令人滿意,原文連接:Ticket Servers: Distributed Unique Primary Keys on the Cheap 這個方案是我目前知道的最好的方案,它與一般Sequence表方案有些類似,但卻很好地解決了性能瓶頸和單點問題,是一種非常可靠而高效的全局主鍵生成方案。

圖1. flickr採用的sharding主鍵生成方案示意圖

flickr這一方案的整體思想是:建立兩臺以上的數據庫ID生成服務器,每個服務器都有一張記錄各表當前ID的Sequence表,但是Sequence中ID增長的步長是服務器的數量,起始值依次錯開,這樣相當於把ID的生成散列到了每個服務器節點上。例如:如果我們設置兩臺數據庫ID生成服務器,那麼就讓一臺的Sequence表的ID起始值爲1,每次增長步長爲2,另一臺的Sequence表的ID起始值爲2,每次增長步長也爲2,那麼結果就是奇數的ID都將從第一臺服務器上生成,偶數的ID都從第二臺服務器上生成,這樣就將生成ID的壓力均勻分散到兩臺服務器上,同時配合應用程序的控制,當一個服務器失效後,系統能自動切換到另一個服務器上獲取ID,從而保證了系統的容錯。

關於這個方案,有幾點細節這裏再說明一下:

1. flickr的數據庫ID生成服務器是專用服務器,服務器上只有一個數據庫,數據庫中表都是用於生成Sequence的,這也是因爲auto-increment-offset和auto-increment-increment這兩個數據庫變量是數據庫實例級別的變量。
2. flickr的方案中表格中的stub字段只是一個char(1) NOT NULL存根字段,並非表名,因此,一般來說,一個Sequence表只有一條紀錄,可以同時爲多張表生成ID,如果需要表的ID是有連續的,需要爲該表單獨建立Sequence表

3. 方案使用了mysql的LAST_INSERT_ID()函數,這也決定了Sequence表只能有一條記錄。
4. 使用REPLACE INTO插入數據,這是很討巧的作法,主要是希望利用mysql自身的機制生成ID,不僅是因爲這樣簡單,更是因爲我們需要ID按照我們設定的方式(初值和步長)來生成。

5. SELECT LAST_INSERT_ID()必須要於REPLACE INTO語句在同一個數據庫連接下才能得到剛剛插入的新ID,否則返回的值總是0
6. 該方案中Sequence表使用的是MyISAM引擎,以獲取更高的性能,注意:MyISAM引擎使用的是表級別的鎖,MyISAM對錶的讀寫是串行的,因此不必擔心在併發時兩次讀取會得到同一個ID(另外,應該程序也不需要同步,每個請求的線程都會得到一個新的connection,不存在需要同步的共享資源)。經過實際對比測試,使用一樣的Sequence表進行ID生成,MyISAM引擎要比InnoDB表現高出很多!

7. 可使用純JDBC實現對Sequence表的操作,以便獲得更高的效率,實驗表明,即使只使用Spring JDBC性能也不及純JDBC來得快!

實現該方案,應用程序同樣需要做一些處理,主要是兩方面的工作:
1. 自動均衡數據庫ID生成服務器的訪問
2. 確保在某個數據庫ID生成服務器失效的情況下,能將請求轉發到其他服務器上執行。
 

相關閱讀:

數據庫分庫分表(sharding)系列(五) 一種支持自由規劃無須數據遷移和修改路由代碼的Sharding擴容方案

數據庫分庫分表(sharding)系列(四) 多數據源的事務處理

數據庫分庫分表(sharding)系列(三) 關於使用框架還是自主開發以及sharding實現層面的考量

數據庫分庫分表(sharding)系列(二) 全局主鍵生成策略

數據庫分庫分表(sharding)系列(一) 拆分實施策略和示例演示

關於垂直切分Vertical Sharding的粒度

數據庫Sharding的基本思想和切分策略

 

分庫分表(sharding)後主鍵全局唯一性的解決方案

隨着數據量的增大,在數據庫的擴展上通常遇到切分時保證鍵值的唯一性問題,遇到這種情況,通常有如下幾種相對簡單的解決方案:

1  UUID 這種方案的優點是實現和管理簡單,缺點是佔用空間大,查詢效率低下。

2  Sequence Number 優點是實現和管理簡單,確定是有性能瓶頸和單點問題。

3  不同的集羣採用的起始點或者增長間隔不同 這種方案實現簡單,但是後期管理麻煩。

除了上述解決方案之外其實還有很多簡單可行的辦法,但是通用性不太好,在各種解決方案的接觸上,本人總結出一個實現和性能上都很好的解決方案,那就是採用時間戳加毫秒數再加隨機數來解決,存儲字段採用bigint。

下面給出php代碼實現:

function ivan_fetch_unique_bigint_id()
{
    $start_timestamp = 1238119411;
    $ivan_len = 3;
    $time = explode( ‘ ‘, microtime());
    $id = ($time[1] – $start_timestamp) . sprintf(‘%06u’, substr($time[0], 2, 6));
    if ($ivan_len > 0) {
        $id .= substr(sprintf(‘%010u’, mt_rand()), 0, $ivan_len);
    }
    return $id;
}

取模測試均分性很好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章