最近因為一個投票項目的需求,為了在一定時間範圍内限制使用者的投票數量,我們采用了redis緩存使用者投票總數和曆史來達到限制使用者在一定時間範圍内的投票操作。
項目中我們采用redis的zset形式來儲存使用者的投票時間範圍統計,score記錄了使用者id,value記錄了使用者的時間範圍和目前已投票數。在測試過程中沒有發現問題,上生産以後,一個巨大的坑出現了。當一個使用者在一個時間範圍内,達到投票限制,我們很好的達到了限制投票的需求。但是過了一會兒,該使用者又可以繼續投票了。經過檢查生産和測試跟蹤,發現當A使用者的投票記錄儲存以後,達到限制值後,A使用者不會再被允許投票。但是此時,當B使用者也達到投票峰值後,A使用者的投票統計記錄消失了。此時就會出現A使用者可以無限制刷票。
通過斷點跟蹤代碼後我們發現,當B使用者在相同時間範圍的投票統計值和A使用者相同時,A使用者的記錄就會被B使用者幹掉。後查詢了Redis的相關ZSET特性說明,發現如下定義:
Redis 有序集合和集合一樣也是string類型元素的集合,且不允許重複的成員。
不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。
有序集合的成員是唯一的,但分數(score)卻可以重複。
也就是說,當初想通過score記錄使用者唯一id來區分記錄的方式是錯誤的。在Redis的ZSET中,score隻能用來做自動排序,而不能作為唯一性鍵值。為解決此問題,我們在每個使用者的value中,增加了使用者的id,以保證value的唯一性。
至此,bug解決。但還有一個隐性漏洞,當score值由于某種極端情況,比如高并發情況下,系統可能會記錄兩個score值相同,但value值不同或相同的記錄。這個問題留待有時間徹底解決一下。
總結這次填坑經曆,還是對redis的一些特性不深知,是以給自己挖了大坑。後續要仔細研究一下。
本内容來自本人在CSDN的部落格:https://blog.csdn.net/perlxs/article/details/103024967