論 大併發 下的 樂觀鎖定 Redis鎖定 和 新時代事務

在 《企業應用架構模式》 中 提到了 樂觀鎖定,html

 

用 時間戳 來 斷定 交易 是否有效, 避免 傳統事務 的 表鎖定 形成 的 瓶頸 。前端

 

在 如今的 大併發 的 大環境下, 傳統事務 及其 表鎖定 以及 事務帶來 的 性能消耗, 確實 不能適應 當今 的 大併發 的 場景 了 。算法

 

感受 傳統事務 也就只能用在 辦公系統 了,   哈哈哈哈 。數據庫

 

可是 傳統事務 的 表鎖定 是 合理的, 表鎖定 使得 事務中 其它 線程 不能 讀寫 表 。緩存

不能 寫, 這個容易理解, 不能 讀 是怎麼回事 ?多線程

由於 讀取表的結果 會 做爲 系統 決策行爲 的 依據, 因此 也不能 讀 。架構

好比, 一個商品已經賣出去了, 就不能再賣給其它用戶 。併發

 

那麼, 用 樂觀鎖定 能解決這些問題嗎 ?負載均衡

樂觀鎖定 的 提出 是 頗有意義的,分佈式

可是不太靠得住,  爲何呢 ?

 

時間戳 一般 取到 毫秒, 但 若是 併發密度 超過了 毫秒級, 達到了 好比 微秒級,  那 ?

固然 理論上 能夠隨機的取 其中 一個用戶 做爲 成功者, 其餘用戶做爲 失敗者(犧牲者?) 。

不過 理論上, 這還不是一種 完備 的 作法 。

 

咱們再來看看 Redis 鎖定,

Redis 提供了 對 數據(對象) 的 鎖(Lock), 以及 隊列 Queue 等 數據類型, 以及 對 Queue 的 Block 操做 。

咱們能夠利用 Redis 來鎖定 某個 ID 的 業務實體 (某個 訂單號 的 訂單), 而後 對 這個 業務實體(訂單) 進行 相關的操做(事務 / 交易),

這樣 達到 多個 線程(用戶) 對 同一個 訂單 的 操做(交易) 順序進行  。

 

這種作法 的 實質 是 行鎖定 ,  那爲何不經過 數據庫 來實現 行鎖定 呢  ?

由於 我 不知道 數據庫 行鎖定 的 語法,,,,,,

其次, 數據庫 行鎖定 的 實現 會 更加 複雜 和 重量,

Redis 的 對象鎖 則 簡單 而 輕量 。

 

事實上, 若是 把 上述過程 簡化 下來,

咱們 只是 須要 有一個  「Flag」  在 Redis 裏 能夠 供 多臺 Server 的 線程間 同步協做 便可 。

嚴格的講, 在 負載均衡 (集羣), 即 多臺 Server  的 情形下 才須要使用  Redis (分佈式緩存) ,

若是 是 單臺 Server ,  則 使用 進程內 的 變量 來 做爲 「Flag」  Lock  便可 。

此時, 狀況 則 退化爲 進程內 多線程 之間的 同步協做 。

 

同時, 嚴格的講, Redis 只須要提供一個 「Flag」, 或者說, Redis 只須要提供一個 鎖機制 ,

並不須要 將 具體的 業務數據(對象) 保存 到 Redis, 

進行 一筆交易 時, 不須要 到 Redis 查找 業務對象(好比 訂單), 若是查不到再到 數據庫 查, 更新時 先更新 Redis, 再更新 數據庫 ,

沒有必要這樣 。

Redis 只要提供 「Flag」 Lock 來 確保 順序進入(同步協做),  具體的操做直接 讀寫 數據庫 便可 。

 

因此, Redis 的 真正意義 在於 共享內存, 而不是 數據緩存 。

能夠看看我昨天寫的  《論 業務系統 架構 的 簡化 (二) 用 關係數據庫 做 緩存》   http://www.javashuo.com/article/p-bkmtepbb-by.html

 

除了 鎖, 事務 還有 另外一方面,  數據完整性 。

好比, 更新 A, B, C 三張表, A, B 成功, C 失敗, 因而事務會回滾, A, B 恢復原來的數據 。

要實現 數據完整性, 須要 表鎖定 和 事務日誌(這會帶來 性能消耗),

可見,

數據完整性 一樣也會成爲 大併發 的 瓶頸 。

 

因此,咱們這裏 提出 一個  「樂觀事務」  概念,

即 對於 每次交易, 都是 Insert , 而不會 反覆 的 去 Update 。

假如一個交易 要 更新 3 個表, A 表 爲 主表, B, C 表經過 A 表 的 ID 關聯, 

那麼, 這個交易 對 A,B,C  3 個表都是  Insert  操做, 在 最後 事務成功 時 將 A 表裏的 「生效」 欄位 更新 爲  「Y」,

以此 表示 事務成功 。

 

顯然, 這種作法 並非對 全部場合 都適用, 它會讓一些 小場景 變得麻煩 。

可是, 對於 前端 海量 用戶 海量 併發 的 場景, 可使用 這種作法 。

 

P :   咱們大概能夠把   每秒 100萬 ~ 1000萬 的 交易量 稱爲 「海量」,  把  每秒 1000萬 以上 的 交易量 稱爲 「天量」  。

 

Redis 鎖  +  樂觀事務  =  新時代事務

 

咱們來看一下 新時代事務 處理 3 個場景 :

1  訂單(交易)

2  秒殺

3  商品庫存計數

 

1  訂單(交易),

    用 Redis 鎖 來 鎖定, 用 樂觀事務 執行 更新數據,  具體的 你們 本身想象 吧

2  秒殺

    用 Redis 存一個 對象 記錄 被 哪一個 用戶秒殺, 同時 加上 Redis 鎖, 這樣 用戶 就能夠 順序 的 獲取這個對象,

    第一個得到這個對象的用戶 就 秒殺 成功,  並更新這個 對象 的 狀態 表示 秒殺成功 。

3  商品庫存計數

    同 Redis 存一個 對象 記錄 庫存量, 用戶將 商品 放入 購物車 則 對象.庫存量 - 1, 用戶將 商品 從 購物車 刪除 則 對象.庫存量 + 1 ,

    經過 對象鎖 來 確保 用戶順序 獲取對象 查看 和 更新 庫存量 。

 

能夠看看我前幾天寫的  《一個相似 Twitter 雪花算法 的 連續序號 ID 產生器 SeqIDGenerator》  http://www.javashuo.com/article/p-xgirusjt-bo.html

 

一般, 一個實體 會 對應一張表, 

咱們以 訂單 爲例,

一筆 訂單 對應 訂單表 裏的 一筆資料,

訂單表 會有一張 對應的 訂單_Trans  表,  用來記錄 發生 在 訂單 上的 樂觀事務,

訂單_Trans 表 會有一個 ID ,  表示  Transaction ID ,

訂單_Trans 表 同時還包含  訂單 須要更新 的 欄位,

這樣, 發生 一次 事務 時,  會向 訂單_Trans 表 insert 一筆資料,  欄位 的 值 是 訂單 本次 更新的 欄位 的 值,

事務 成功 後, 會將 訂單 表 裏的  「trans」   欄位 更新爲 此次 事務 的 ID,  即 訂單_Trans 表 的  ID ,

這樣, 經過      訂單.Trans = 訂單_Trans.ID      關聯, 能夠查詢到 訂單 在 本次 事務 更新後的 欄位 的 值 。

 

一筆訂單 在 訂單_Trans 表 中能夠對應 多筆 事務記錄,  這些 事務記錄,  有成功的, 也有不成功的 。

 

樂觀事務 的 關鍵 在於 最後只能 更新 一張表 的  (一個)欄位 來 決定 事務是否成功, 

若是要 更新 多個 表, 那又 回到 傳統事務 了 。

因此, 這就看 針對性 的 設計 。

 

對於  淘寶 天貓   這樣的 海量購物, 應該設計爲 單項遞增數據 的 架構, 也就是 只 insert ,  不 update ,

但問題是 若是 一個 交易 裏 要 insert 多個 表 呢  ?

也許能夠用 樂觀事務     A 表 關聯 B 表, B 表 關聯 C 表,  ……

這樣只須要 最後 更新 A 表裏的一個 生效 欄位 ,  就能夠 表示 事務 是否成功 。

由於 能夠 根據 A 表 關聯 到 B 表 , B 表 關聯 到 C 表 ,

或者有一個  Trans 表,  經過 Trans 表 的 ID  (Trans ID)  來 關聯  A, B, C  表 ,

這樣能夠 查詢出     某一個 事務(Trans ID)     在  A, B, C 表 裏  insert  的 資料,

 

然鵝   。

 

不過 說不定 這種方法 真的 有人在用 喔  ~!

 

數據完整性 最好 還有由 數據庫 來實現,  這纔是合理的 。

關鍵在於,

數據庫 應該使用    行鎖定   來 實現 數據完整性 。

就是說, 咱們應該讓 數據庫 用 行鎖定 來 執行 事務 。

 

淘寶 天貓 應該 本身 已經對 數據庫 作過這種 優化了 。

也就是說, 淘寶 天貓 的 數據庫 的 事務 是用 行鎖定 的 方式 執行的 ,

固然, 這只是個人 猜想,   啊哈哈哈哈 。

 

Redis 鎖定       +        數據庫 行鎖定 事務 (行級事務)      +        數據庫 使用 固態硬盤

 

這樣 來 應對 大併發,  你以爲呢 ?

相關文章
相關標籤/搜索