您好,登錄后才能下訂單哦!
PM 說有一個類似于搶購的小需求,我們第一反應就想到是典型的防止庫存超賣場景,于是理所因當地選用了 Redis 方案。只要保證是原子操作,即可防止庫存超賣,自然想到使用 Incr/Decr 這類原子操作。
查看 PHP 的 Redis 擴展關于 Incr 方法的說明:
/**
* Increment the number stored at key by one.
*
* @param string $key
* @return int the new value
* @link http://redis.io/commands/incr
*
*/
public function incr( $key ) {}
可見,Incr 方法返回的是 key 操作后的新值,即 ++1 后的值,于是我們寫出了如下代碼:
$num = $redis->incr($key);
if ($num < $max) {
//入搶購成功隊列,異步去執行搶購成功邏輯
} else {
//不好意思呢,已經被搶完了
}
不知道你有沒有聞到這段代碼的壞味道,在大部分情況下會如你所想地運行,但是特殊場景下會 出現判斷失效 的邏輯問題,例如:
1、key 由于某些原因失效了;
2、Incr 操作失敗了,不會拋異常并返回 false;
上述兩種情況,都會導致$num < $max條件成立,進而導致更嚴重的邏輯問題,最終超賣。
我們就搶購開始后就遇到了上述的第二種情況,下面描述整個過程。先通過 Cat 監控平臺觀察到訪問量急劇上升,開始擔心應用服務坑不住,隨后日志平臺報警 Incr 操作存在異常幾率,再然后就出現超賣情況,緊急情況只能關閉業務開關。是什么原因導致判斷條件成立?
通過日志定位到 Incr 操作問題,便 Telnet 連接到線上 Redis 服務,發現了異常情況:
\# 查看值
GET key
100
# 嘗試修改
INCR key
READONLY You can't write against a read only slave
INFO
# Replication
role:slave
可以看出來,該連接的機器目前處于從機狀態,不可寫操作,所以 Incr 操作返回 false,同時 PHP 不同類型比較會存在隱式轉化,所以false < $num恒成立,導致計數器失效。而這一切又是由于 Redis 高可用不完善,當主從切換后,VIP 未能成功漂移,這部分是運維的鍋,研發代碼不夠健壯,這鍋同樣要背 >﹏<。
首先,修改代碼使其更加健壯,增加計數器容錯處理:
$num = $redis->incr($key);
if ($num > 0 && $num < $max) {
//入搶購成功隊列,異步去執行搶購成功邏輯
} else {
//不好意思呢,已經被搶完了
}
然后,切換 Redis 源到高可用集群(Codis),測試并重新上線,第二日的搶購已經正常,看著 Cat 上流量逐漸平穩,心里也踏實了。
查看來源
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。