一. 并發更新方案
采用在Redis中判斷并更新庫存(庫存值可增可減),由Redis保證庫存的正確性,由Kafka與MongodDB資料庫事務保證最終的一致性,


二. Redis使用lua腳本來保證并發更新的正確性
1. Redis會將整個lua腳本作為一個整體執行,中間不會被其它命令插入,保證了原子性,執行緒安全,且不需要事務控制,
2. Redis中的值,假設正確范圍在0 <= x <= max(通過腳本傳參)
3. 腳本的作用是,檢測key指定的值與increment之和counter是否在正確范圍內;如果在正確范圍內則更新為counter,更新成功回傳1,更新失敗回傳0;如果不在正確范圍內則回傳0,
4. Redis執行腳本成功,不論后續發送訊息成功如否,都要給業務呼叫方回傳成功,如果執行腳本失敗,直接給業務呼叫方回傳失敗,無后續動作,

三. Kafka配置與訊息主要內容
生產者采用異步發送加回呼的方式,若發送失敗或出現例外,則必須在回呼函式里保證訊息一定要發送成功,
生產者的acks設定為all,保證不丟訊息,
訊息內容:{
key:long型別,kafka訊息key,雪花演算法唯一ID,
orderId:long型別,訂單ID,雪花演算法唯一ID,
skuId:long型別,庫存商品ID,雪花演算法唯一ID,
increment:int型別,增加或減少庫存,
timestamp:long型別,時間戳
}
消費者配置手動提交ACK,消費訊息的介面邏輯如下:
介面開啟MongoDB事務(REQUIRED_NEW,rollbackFor=Exception.class)
1)插入防重表,MongoDB的集合id設定為訊息的key或orderId都可以,保證唯一就行,
2)更新庫存表,使用MongoDB的inc指令對庫存值做原子操作,
3)回傳更新結果,
因為MongoDB中的WriteConflict問題,上述介面需要重試直到保證MongoDB更新成功,消費介面呼叫成功后才能ACK,
重復消費的問題:
事務中需要插入防重表,重復的訊息key會插入失敗,MongoDB事務會回滾,保證了資料庫中庫存值的正確性,
四. 并發更新與補償邏輯驗證
假設庫存值的正確范圍在0至1000,即0 <= x <= 1000
| 時序 | 執行緒A+1 | 執行緒B-1 | Redis值 | DB值 | 備注 |
| 999 | 999 | ||||
| 執行lua腳本+1 | 1000 | 999 | |||
| 發送+1訊息成功 | 執行lua腳本-1 | 999 | 1000 | ||
| 發送-1訊息成功 | 999 | 999 | 最終一致 |
| 時序 | 執行緒A+1 | 執行緒B-1 | 執行緒C+1 | Redis值 | DB值 | 備注 |
| 999 | 999 | |||||
| 執行lua腳本+1成功 | 1000 | 999 | ||||
| 發送+1訊息成功 | 執行lua腳本+1,超出范圍失敗 | 1000 | 1000 | |||
| 給業務回傳成功 | 執行lua腳本-1 | 給業務回傳失敗 | 999 | 1000 | ||
| 發送-1訊息成功 | 999 | 999 | ||||
| 給業務回傳成功 | 999 | 999 | 最終一致 |
| 時序 | 執行緒A+1 | 執行緒B-1 | 執行緒C+1 | 執行緒D+1 | Redis值 | DB值 | 備注 |
| 999 | 999 | ||||||
| 執行lua腳本+1成功 | 1000 | 999 | |||||
| 發送+1訊息成功 | 執行lua腳本+1,超出范圍失敗 | 1000 | 1000 | ||||
| 給業務回傳成功 | 執行lua腳本-1 成功 | 給業務回傳失敗 | 999 | 1000 | |||
| 發送-1訊息失敗 | 執行lua腳本+1 成功 | 1000 | 1000 | 訊息發送失敗,插入訊息發送重試表,異步重試直到成功發送, | |||
| 給業務回傳成功 | 發送+1訊息成功 | 1000 | 1001 | ||||
| 異步發送-1訊息最終成功 | 給業務回傳成功 | 1000 | 1000 | 最終一致 |
| 時序 | 執行緒A+1 | 執行緒B-1 | 執行緒C+1 | 執行緒D+1 | Redis值 | DB值 | 備注 |
| 999 | 999 | ||||||
| 執行lua腳本+1 成功 | 1000 | 999 | |||||
| 發送+1訊息成功 | 執行lua腳本+1, 超出范圍失敗 | 1000 | 1000 | ||||
| 給業務回傳成功 | 執行lua腳本-1成功 | 給業務回傳失敗 | 999 | 1000 | |||
| 發送-1訊息失敗 | 執行lua腳本+1 成功 | 1000 | 1000 | 補償redis,incrby+1 | |||
| 補償incrby+1 | 發送訊息+1 | 1001 | 1001 | ||||
| 給業務回傳失敗 | 給業務回傳成功 | 1001 | 1001 | 超賣,說明發送訊息失敗不能補償redis |
說明:
1.如果用Redis保證正確性,Redis操作成功則必須給業務回傳成功,后續出現錯誤或例外時是不能反向操作補償Redis的,
2.異步發送訊息失敗時,回呼方法里只能使用其它方式保證訊息一定要發送成功,例如異步執行緒無限重試,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/433336.html
標籤:其他
