今日頭條極速版邀請碼是什麼?實現思路是怎麼樣的?

互聯網公司推出新產品的時候,經常使用邀請碼(C8RB8DXF)作爲一種運營手段,在一定的時間內,只有具有邀請資格的用戶,才能參與到早期產品的體驗中去。
這麼做,一方面爲了體現產品體驗的稀缺性,鼓勵用戶搶購,造成供不應求的輿論影響。這點小米做的尤爲突出,效果也是相當不錯的。另一方面,先讓一部分比較有熱情的早期種子用戶進來,也能夠充分的對產品的易用性和體驗流程進行早期的驗證,方便手機用戶的反饋之後迭代修改。·
我們首先來看看根據需求邀請碼服務需要提供出哪些接口:

單個或批量的生成邀請碼,要求六位,數字與字符的混合,供運營和用戶使用;
給用戶激活邀請碼;
校驗用戶是否已經激活邀請碼;
批量給已註冊用戶綁定幾個可用的邀請碼,方便二次傳播;
查詢用戶名下已經綁定的邀請碼和其被激活的情況,驗證二次傳播的效果

根據需求,現有的技術實現方案有很多,核心來講的話主要有兩點:

邀請碼生成算法與唯一性保證
邀請碼高效查詢與激活

另外還有一些其他的點,會在文中慢慢道來。
邀請碼的生成方式
產品的要求是六位,同時字母和數字混合的方式,一共會發放10000個左右。一開始的思路是使用純算法的方式實現,通過生成唯一的ID,增加一些共性的信息,再根據信息摘要的方式,映射成字母與數字的混合,不過試了一下,算法實現的成本比較高,而且還有藉助額外的唯一ID生成的工具,所以有些得不償失。
另外由於不能是純數字,所以十六進制映射的方案也無法進行。而且也不能是一長串的字符,這樣md5的方案也無法實施了。
所以在這樣的前提之下,決定使用簡單算法+redis來做整個方案。
首先是邀請碼的生成,由於生成的過程一般是後臺或者手動觸發,所以對性能的要求並不太高,也就是說,允許一定程度的碰撞。所謂的碰撞,就是算法算出來的邀請碼,和已經生成的邀請碼重複了。所以我們來算一下概率:
一共是六位的邀請碼,每一位的組成是大小寫字母+阿拉伯數字,也就是26+26+10=62種可能性。所以六位也就是一共能夠產生出62^6=56,800,235,584即560億個邀請碼,產品的要求是一共放出去一萬個,所以即使現在已經有一萬個邀請碼被生成了,那麼生成一個邀請碼和現有邀請碼重複的概率只是10000/560億, 非常低的概率,如果真的中了大獎,那麼就進行二次生成,再次碰撞的概率,恐怕就更低了。
同時爲了保證邀請碼看起來更真實,再增加一個連續兩位不能重複的驗證,同時每次生成結束之後,使用redis驗證是否唯一,即可實現整個邀請碼的生成過程。
數據結構的設計
上文說到爲了保證邀請碼的唯一性,必須使用redis,redis中的集合數據結構就提供了很好支持。每次生成完成後,只需要調用isMember validatorSet方法,即可根據返回判斷是否已經生成了邀請碼。
同時使用集合存儲邀請碼,也能夠滿足統計的需求,只需要定時執行smembers validatorSet,即可統計出所有生成出來的邀請碼。
另外,由於存在用戶激活邀請碼,和查詢是否已經激活邀請碼的尋求,所以必須對此設計專門的數據結構。查詢需求是高頻需求,在用戶量比較大的情況下,會有比較大的訪問壓力。同時爲了擴展可能存儲的用戶其他信息,所以使用散列的數據類型進行存儲。
每次查詢用戶是否已經激活,只需要執行hget user:$guid validator 即可獲取其中的邀請碼信息,從而判斷出用戶是否已經激活。
而用戶激活的過程,涉及的數據結構稍多,每次激活的過程,都需要分成幾步:
第一步是查詢這個邀請碼是否是合法有效的邀請碼,需要使用ismember validatorSet進行判斷;
如果合法有效,第二步是設置用戶的激活狀態,即上文中所用到的這個散列hset user:$guid validator {$validator};
第三步則是設置邀請碼本身的狀態,即這個邀請碼被哪個用戶用了,方便之後查詢 set validator:{$validator}:status {'userName':ted,'avatar':'http://test.com'}
由於第二步和第三步必須保證數據的冪等性,因此需要使用redis的事務來進行執行,保證兩者能夠同時成功的被執行:
$redis->Multi()
->set validator:{$validator}:status {'userName':ted,'avatar':'http://test.com'}
->hset user:$guid validator {$validator}
->exec()

但是除了使用事務,還需要規避的就是兩個用戶同時取到了同一個邀請碼,在同一時間進行了激活的操作,在高併發的情況下,會有原子性的問題。所以必須引入redis中的watch命令,監控邀請碼的status這個key,保證一旦它被變更了,那麼當前的事務就不再執行,從而避免給用戶帶來錯誤的反饋:
redis->watch validator:{$validator}:status
由此,就完成了用戶邀請碼的激活過程。
除此之外,還有個小功能,那就是給一批種子用戶綁定幾個邀請碼,並在邀請碼被激活之後,展示出相應的狀態。剛剛說到了用戶爲維度的那個哈希表結構,是時候給它再加一個field了,這個field就叫boundValidators,專門用來存儲用戶名下被綁定的邀請碼:
hset user:$guid boundValidators $validator1|$validator2...
那麼查詢的時候只需要進行兩次查詢:
// 查詢用戶名下綁定的邀請碼
hset user:$guid boundValidators
// 查詢邀請碼是否以及被誰激活了
mget $boundValidators

爲了降低網絡延時,完全可以通過一個pipeline實現,從而使得網絡交互從兩次變成一次。
接口性能驗證
由此已經大功告成,簡單的使用wrk測試一下性能,服務器相比之前的差一些,一共是4核16G內存。使用wrk -t12 -c100 -d10s http:ip:port/queryActivate,
海外內網、同步的redis調用:

QPS在5000左右,完全滿足了本次邀請碼的需求,事實上,如果能超過這個量,估計睡覺都要笑醒了:)

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