在 《企業應用架構模式》 中 提到了 樂觀鎖定,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 鎖定 + 數據庫 行鎖定 事務 (行級事務) + 數據庫 使用 固態硬盤
這樣 來 應對 大併發, 你以爲呢 ?