需求:按照隨機策略生成一個不重複的邀請碼(字母+數字),既要不重複又要保證效能。每個使用者對應一個邀請碼,必須做到唯一性。邀請碼的需要手動輸入所以長度不能太長,同時不能讓使用者猜到邀請碼的生成邏輯,所以邀請的生成邏輯也必須要隨機。
目的 :生成不重複的邀請碼
1.如何做到不重複
-
方案一:
利用生成邀請碼時間戳進行生成,並且加上隨機數,能保證不同時間生成的邀請碼大部分不同(不排序小概率碰撞)。為了減少碰撞率,可以精確到毫秒顆粒度,但同時也增長了邀請碼的長度。
弊端:秒/毫秒顆粒度的時間長度(10/13位)+ 隨機碼(4位)會導致邀請碼的長度達到10+位,這對於使用者的體驗是極其不好的,而且也容易被猜到邀請碼的生成邏輯,因此此方案不推薦。
-
方案二:
1.通過方案一的弊端,我們知道生成一個合格的邀請碼我們需要解決的第一個問題就是長度。長度的設定是關乎可生成邀請碼的數量的範圍,設定太少的話邀請碼的範圍有限,設定太長的碼邀請碼對於使用者的體驗不好。所以考慮這兩個情況,我們可以將邀請碼的長度定義6位。
2.要解決的第二個問題就是重複的問題。要保證不重複,簡單來說最好的方式就是自增,例如1,2,3,....這樣子就能保證絕對唯一,可以將使用者的自增id作為邀請碼的自增值做繫結。這樣做的好處就是能夠保證不重複,但是很容易被使用者猜測到生成邏輯以及一些使用者的敏感資料。既然自增能夠保證唯一性,那我們不妨換一種思維去思考,可不可以在自增的基礎上面將規律給打亂,這樣就能保證邀請碼唯一且無規律了。
做法:一個自增點會導致數量連續有規律,那麼我們可以設定多個池子,每個池子維護一個自增點,隨機去一個池子通過自增id生成,就能夠做到隨機了。因為邀請碼的長度有限制,所以我們需要算出來邀請碼最大的範圍值有多少,然後將最大的範圍值分成若干個池子。
6位(數字+字母)長度的邀請碼,根據可取字元的數量,我們將邀請碼的形式設定為36進位制數,這樣邀請碼的範圍值就是為000000~
zzzzzz,也就是等於十進位制的2176782335,相當於我們最多能夠生成2176782335個邀請碼。如果此數量不滿足業務上的需求,可以根據業務要求適當增刪。我們假設每個池子能夠儲存邀請碼的數量為10000個,則會有217678個池子。我們每個自增點就等於:邀請碼 = (池子number * 10000 + 自增點)轉成32進位制 的字串。
用個圖來表示以上的過程:
要注意紅色字型,每個池子有最大自增點限制,為該池子能夠儲存邀請的最大數量。通過上述過程就能做到生成一個唯一且無明顯規律的邀請碼。當然要想最大程度無規律的話,可以將前幾個池子給剔除掉,這樣子就不會出現000001這樣子的邀請碼
程式碼實踐(PHP版本):主要思路是利用redis集合的特點進行儲存自增點以及池子。
<?php
class Code{
protected Redis $redis;
private $codeIncrementSet = 'code_increment_set';
private $codeAvailableSet = 'code_available_set';
public function __construct(){
$this->redis = new Redis();
$this->init();
}
//初始化
private function init(){
if (0 === $this->redis->exists($this->codeIncrementSet)){
//新增1-217678序號的池子,並且自增點設定為0
$this->redis->zAdd($this->codeIncrementSet,array_fill(1,217678),0);
//儲存1-217678序號的可用池子
$this->redis->sAddArray($this->codeAvailableSet,range(1,217678))
}
}
public function getCode(){
//隨機取一個可用的邀請碼池子
if (is_null($index = $this->redis->srandmember($codeAvailableSet))){
throw new BusinessException('可用邀請碼碼數為空');
}
//獲取對應需要池子的自增點
$increment = (int)$this->redis->zScore($this->codeIncrementSet,$index)
//計算code值
$number = (int)index * 10000 + $increment;
//維護自增點
$this->redis->zIncrBy($this->codeIncrementSet,1,$index);
//該池子被用完了
if (9999 === $increment){
$this->redis->sRem($this->codeAvailableSet,$index);
}
//返回36進位制的邀請碼,用0部位
return str_pad(base_convert($number, 10, 36), 6, '0', STR_PAD_LEFT);
}
}
以上就是整體程式碼的實現,具體的一些細節可根據業務自定義調整。
2.如何做到高效能
生成使用者邀請碼是一個頻繁的操作,如果每次實時生成的話效能會比較低。可用利用空間換時間的思維。預先生成一批邀請碼然後在需要的時候直接拿出來用即可。這種思路在開發中很常見,非同步處理,用空間換取時間。通過這種方式就能快速的生成邀請碼。
以上就是如何生成唯一無規律邀請碼的策略,方法很簡單卻很有效。將思路輸出成文章的形式,能夠更好地加深理解,以及能夠讓更多的人看到,指出其中的不足。
互勉:保持空杯心態,做一個有態度的程式設計師。
個人原創,轉載需取得作者同意。個人部落格地址:https://www.goldenleek.top