真假唯一數

  • 2019 年 10 月 8 日
  • 筆記

6分鐘

速讀僅需3分鐘

在真實的業務中生成唯一數是常見的功能,也是面試必考題。今天說說在面試過程中面試官在問這個問題時最想得到怎樣的答案。

大部分編程語言都提供了唯一數生成函數,可惜大部分並不好用,原因是使用條件不符合使用場景。比如你需要生成唯一的數字並且是按順序增長的,但系統函數只能生成字符串,最後只能另闢蹊徑。所以面試官會問通常都有哪些生成唯一數的方法?

一. 散列+時間+隨機值

md5(time() . mt_rand(1,1000000));

如上 `time()` 函數獲取當前時間戳,`mt_rand(1,1000000)` 函數獲取一個1到1000000的隨機值,看着好像生成的數是唯一的,但這行代碼問題非常多。

在操作系統中時間是很不靠譜的參數,因為CPU計算太快,沒有對應的時間單位。如果CPU 1秒內處理了2個請求,那麼`time()`字段毫無意義。

在編程語言中隨機數也並不隨機,常見的隨機數都需要有隨機種子,而為了保證種子不被猜到,編程語言默認會使用當前系統時間作為種子。又變成了依賴時間的一個參數,所以這種方案不可取。

二. 微秒+進程編號

uniqid();

`uniqid()`函數可以得到一個基於微秒和進程編號的唯一ID。對於php-fpm來說,每個請求都獨佔一個進程,一個進程會串行的處理多個請求。所以通過進程編號+微秒看上去能生成唯一ID。但深究之後發現並不靠譜。

1秒等於100萬微秒,現在問題會變成一個進程能在百萬分之一秒內處理多個請求嗎?答案是可以的,用當前最普通的CPU來說,單核心1秒就可以計算20億次,1微秒可以計算2千次。從操作系統調度的角度來說,2千次同時處理到一個進程的兩個請求是完全可能的。所以又變成了依賴時間的一個參數,這種方案也不可取。

你還能使用納秒,皮秒等精度更高的時間參數,但以發展的遠光看問題,未來CPU的算力會越來越快,依賴時間的唯一性會越來越差。無論怎麼努力,只依靠編程語言自身得到唯一ID是非常困難的,也是我不推薦的。

三. 數據庫

靠編程語言自己走不通,那就通過第三方工具,這種方案是比較靠譜的。實現起來也有很多種方法。

LOCK TABLES id_maker;  //拿到id  select id from id_maker;  //加1更新  update id_maker set id=id+1;  UNLOCK id_maker;

這是我曾經見過的一種id_maker用法,每次調用得到的id都是唯一併且有序的。但問題是需要鎖表,性能不高,在高並發下很容易發生資源搶佔導致數據庫崩潰。

//id_maker表有自增id和內容data兩個字段  insert into id_maker(data) values('');  select last_insert_id();

這種方式性能就高很多,insert語句不會鎖表,自增id百分百保證唯一性且有序。唯一的問題是需要定期刪除歷史數據,對於大部分項目我都建議使用這種方式生成唯一ID。

除了MySQL還有MongoDB,Redis等其他數據庫方案,方法大同小異。但是這個方案有局限性,當我們的業務發展到成千上萬台服務器時通過一個數據庫的一張表去生成ID會導致性能下降拖垮其他服務,還會形成單點依賴。

四. 微秒+服務器編號+計數器

Twitter開源了他的唯一ID生成方案,名字叫SnowFlake,這種方案的原理非常簡單,很容易基於SnowFlake算法做擴展。比如PHP並不會常駐內存,計數器的實現比較麻煩,但只要原理清楚了使用擴展或者數據庫很容易實現。

這個方案的最大優點就是在龐大的集群中,每個服務靠自己就能算出全局的唯一ID。微秒能保證ID有序,服務器編號能確定到具體機器,計數器(可以理解為只為當前服務器提供的`id_maker`)能保證當前機器所有請求的唯一性,通過這些參數生成的唯一編號有序並且無懈可擊。

如上四點寫的其實是一種思路,處理問題都是由簡單到複雜,由一到二。如果直接給面試官說終極方案,面試官會基於終極方案問更多複雜的問題,更多離業務非常貼近的問題,如果沒接觸過相關業務直接就pass了。所以由簡單到複雜,講清楚技術原理和思考過程,offer離你就不遠了。