基於redis的cas實現

来源:https://www.cnblogs.com/anti-archs/archive/2018/01/27/redis_cas.html
-Advertisement-
Play Games

cas是我們常用的一種解決併發問題的手段,小到CPU指令集,大到分散式存儲,都能看到cas的影子。本文假定你已經充分理解一般的cas方案,如果你還不知道cas是什麼,請自行百度 我們在進行關係型資料庫的更新操作時,基於cas的更新常常是保證數據業務邏輯語義下的一致性的終極手段,一般用來解決“寫偏序” ...


  cas是我們常用的一種解決併發問題的手段,小到CPU指令集,大到分散式存儲,都能看到cas的影子。本文假定你已經充分理解一般的cas方案,如果你還不知道cas是什麼,請自行百度

  

  我們在進行關係型資料庫的更新操作時,基於cas的更新常常是保證數據業務邏輯語義下的一致性的終極手段,一般用來解決“寫偏序”問題。關係型資料庫有基於where的條件更新,一些NoSQL也都有對cas的支持,可為什麼redis在原生語義上不支持cas操作呢?例如:

setcas key oldvalue newvalue

 

  很多人不理解,redis處理速度本就很快,還需要cas麽?我承認redis對於單個指令的處理速度很快,但很多時候我們要解決的是網路問題,和應用程式STW(stop the world,一般指java那種長時間GC)

  一旦發生這種問題,形成了 get->判斷->停頓 ->set,就可能出現寫偏序或者更新丟失,redis也沒辦法幫你了

  為什麼redis不支持原生的cas?

  這種功能對redis來說實現起來幾乎不費力氣:原本對數據處理的操作就是基於單線程的,壓根不會出現像其他語言的那種記憶體不可見問題,或者什麼性能損失

  我找到了09年redis的一個mail list (要FQ),redis的作者Salvatore Sanfilippo 開始解釋了為什麼他不想加入cas功能,理由是至少沒法說服我,社區中很多人也表示“我們只需要關於string類型的cas操作就好啦”。然而時至今日你依舊沒有在redis.io的command列表中找到cas操作的蹤跡

  幸好,我們有兩種方式可以自己實現cas,且並不費力

  基於Lua腳本的cas實現

  目前我們使用的redis版本,都支持lua腳本的執行,並且性能非常好。甚至對於比較複雜的功能,redis-cli還提供了lua腳本的調試工具。下麵是我自己實現的一個string的cas功能,相信已經能滿足大多數場景了:

local v = redis.call("get", KEYS[1]) local r = 1 if v == KEYS[2] or v == false then redis.call("set", KEYS[1], KEYS[3]) else r = 0 end return {r, v}

  不好意思,我用空格代替了換行,因為語句實現在是太簡單。此腳本中的KEYS[1](lua的數組從1開始)代表你要修改的key, KEYS[2]代表原值,KEYS[3]代表要修改為的值。最終返回兩個值:第一個值為1或者0,1代表修改成功,0代表修改失敗,無論成功失敗,第二個值會返回原值,這是為了方便你直接在cas失敗後重新進行計算,而不需要再get一下

  調用時依照一下方式:

eval 'lua腳本' 3 key oldvalue newvalue

  但我更建議你將這個腳本載入到redis中,在shell中執行:

> redis-cli script load 'lua腳本'
> "74ff40a09af2913b2651bfbc68d7bab7220daecd"

  第二行返回的就是這個腳本的sha1的哈希碼,下次調用這個腳本你可以直接:

evalsha 74ff40a09af2913b2651bfbc68d7bab7220daecd 3 key oldvalue newvalue

  你可能疑惑腳本中 v==false的意義,原因是,如果你調用redis.call去獲取一個不存在的key,會返回false。由於我使用的go-redis中無法把nil作為old value發送給redis (redis-clie也不行),所以這個腳本會在key不存在的情況下cas成功,無論你把oldvalue賦予了什麼值。我想這在大多數場景中都不成問題。對於任意語言的redis框架,對應參數傳個空字元串就可以了。對於第二個返回值,這種情況下會返回nil, 能被框架成功解析成對應語言的null值(比如go就是nil)

  以下是實際的例子, 在redis-cli下:

> evalsha 74ff40a09af2913b2651bfbc68d7bab7220daecd 3 nosuchkey a b
> 1) (integer) 1
> 2) (nil)
> evalsha 74ff40a09af2913b2651bfbc68d7bab7220daecd 3 nosuchkey b c
> 1) (integer) 1
> 2) "b"

   

  基於Watch和Multi的cas實現

   如果你嘗試過自己搜索一下redis cas的解決方案,我想你看到的大多數文章都是基於“redis 事務”的,即watch和multi。曾經我做面試官的時候,詢問面試者一個他們解決方案,我說既然用到了redis,為什麼不嘗試用“redis 事務”解決一下這個問題。他表示“不知道redis 事務”,而且根據“事務”二字順理成章的認為“事務會大大影響redis性能”

  實際上所謂的redis事務並不像關係型資料庫的事務那麼複雜,舉個例子, 使用了redis 某種語言框架的偽代碼:

client = redis.newClient() //創建客戶端
client.watch("teacher") // 對應redis的指令 watch teacher
client.multi() // 對應redis的指令 multi
a = client.get("teacher") // 對應redis的指令 get teacher
if a == "annie"
  client.set("teacher", "joe") // 對應redis的指令 set teacher joe
else
  client.set("teacher", "han") // 對應redis的指令 set teacher han
client.exec() // 對應redis的指令 exec

  伺服器為每一個被watch的key維護了一個鏈,當你的客戶端執行到watch teacher時,會被加到這個鏈上去。之後exec之前的所有get, set操作其實僅僅是進入了一個指令隊列,待到exec時,如果watch 的key 沒發生變更,則一起執行,否則不執行

  拿這種機制與資料庫事務對比,會發現無論這個所謂的"redis事務"中間隔了多長時間,其實也並不影響其他指令或者事務,而且一旦隊列中的指令執行,也是無法插入其他指令的,保證了隔離性

  

  性能上的對比

  好了,現在我們有兩個方案了,那個更好一點呢?我傾向於lua腳本的方案,一是因為這個腳本相對易讀,通用,減小開發人員代碼量。二就是因為性能。我進行了兩個簡單的實驗, 基於我的筆記本上的虛擬機中的docker...,虛擬機分配了2核2G記憶體

  單線程實驗

  三種交互方式:set——直接對測試key進行set操作, cas——通過lua腳本進行set,並且故意設計成一半成功一半不成功,watch——先watch,再set,最後exec

  併發數:1

  迴圈次數:10000

  跑了若幹次的結果:

  

set  cas watch
2.0s-2.3s 2.1s-2.9s 4.3s-4.9s

  併發實驗

  三種交互方式:set——對測試key先get後set操作, cas——先get,再通過lua腳本進行set,watch——先watch,再get,再set,最後exec

  併發數:500

  迴圈次數:1000

  跑了若幹次的結果:

 

set cas watch
1m13s-1m33s 1m30s-1m49s 2m23s-2m32s

   

  從以上結果可以看出來,在模擬對一個key進行高併發的操作時,lua腳本會略微比set耗時一些,但事務的方式要遠高於其他兩個

  對於這個試驗我要做個說明:

  1. 為了減小語言本身多線程併發的開銷,我選擇了go語言
  2. 測試前做了預熱
  3. 沒把建立連接的時間算進去
  4. 看似500併發的測試,其實還是受物理機CPU核數影響比較大,所以並不能真正模擬出實際高併發的場景
  5. 兩個結果中,網路的延遲應該比redis處理速度占時更多,甚至遠多於
  6. 這是一個非正式的測試結果,僅供橫向對比
  7. 即使4,5兩條成立,依舊不會影響lua腳本更好的結論,因為畢竟同樣的功能都跑了50w次,lua要比事務省時間

  最後留下測試代碼以供參考: github地址

  

  作者:cz

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • RAC類關係圖: RAC 信號源: 需要導入的頭文件: 冷信號 結果: 熱信號 文本框監聽 結果: Map映射 用於將一個事件流的值操作後的結果產生一個新的事件流。 方法一: 輸入: Hello123 結果為: 方法二: 結果為: Filter函數可以按照之前預設的條件過濾掉不滿足的值 方法一: 輸 ...
  • 申請百度API 1、打開網頁 http://lbsyun.baidu.com/index.php?title=首頁 選擇功能與服務中的地圖,點擊左邊的獲取密匙,然後按照要求申請即可,需要手機和百度賬號及郵箱認證。 激活後可看到後臺頁面,現在可以開始創建應用了,這裡請求校驗方式有兩種,一種是白名單IP ...
  • Spring Boot中除了對常用的關係型資料庫提供了優秀的自動化支持之外,對於很多NoSQL資料庫一樣提供了自動化配置的支持,包括:Redis, MongoDB, 等。 Redis簡單介紹 Redis是Redis是Remote DIctionary Server的縮寫,是目前業界使用最廣泛的記憶體數 ...
  • 1、字元串 主要print的時候‘ ’和“ ”的 結果都是樣的,要註意的就是如果句子中含有’或”是要交叉使用,例如I'am a man.裡面就有’,那麼print的時候就要用雙引號“I'am a man.”,如果是“Apple”is an fruit.就要用單引號‘“Apple” is an fru ...
  • 1、文件上傳原理 將客戶端的文件上傳到伺服器,再將伺服器的臨時文件上傳到指定目錄 2、客戶端配置 提交表單 表單的發送方式為post 添加enctype="multipart/form-data" 3、伺服器端配置 file_uploads = On,支持HTTP上傳 uoload_tmp_dir ...
  • 本文是筆者創建項目 一系列java示常式序的總結。項目位置在 "SimplestJavaDemos" ,歡迎訪問. 以下為正文:   作為一個偽完美主義+拖延癌患者,每次要學習新技術的時候,總是要把它們看的很重很大很難,總是要挑一個最完美的時刻,擁有最完美的心情的時候才開始一個新 ...
  • Redis官方給出兩種思路 第一種:SET key value [EX seconds] [PX milliseconds] NX 第二種:SETNX+GETSET 首先,分別看一下這幾個命令 SET命令 SETNX命令 GETSET命令 接著,看第一種方式 官方給出的思路是這樣的 還有一處,也是類 ...
  • Struts2------Result處理&獲取頁面請求參數&API ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...