Redis事務的分析及改進

jopen 9年前發布 | 19K 次閱讀 Redis NoSQL數據庫

Redis事務的分析及改進

Redis的事務特性

數據ACID特性滿足了幾條?
為了保持簡單,redis事務保證了其中的一致性和隔離性;
不滿足原子性和持久性;

原子性

redis事務在執行的中途遇到錯誤,不會回滾,而是繼續執行后續命令;(違反原子性)

事務可以理解為一個打包的批量執行腳本,但批量指令并非原子化的操作;
中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成后續的指令不做;
比如:

redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK

如果在set b bbb處失敗,set a已成功不會回滾,set c還會繼續執行;

持久性

事務不過是用隊列包裹起了一組 Redis 命令,并沒有提供任何額外的持久性功能,所以事務的持久性由 Redis 所使用的持久化模式決定:

  • 在單純的內存模式下,事務肯定是不持久的。
  • 在 RDB 模式下,服務器可能在事務執行之后、RDB 文件更新之前的這段時間失敗,所以 RDB 模式下的 Redis 事務也是不持久的。
  • 在 AOF 的“總是 SYNC ”模式下,事務的每條命令在執行成功之后,都會立即調用 fsync 或 fdatasync 將事務數據寫入到 AOF 文件。但是,這種保存是由后臺線程進行的,主線程不會阻塞直到保存成功,所以從命令執行成功到數據保存到硬盤之間,還是有一段非常小的間隔,所以這種模式下的事務也是不持久的。
  • 其他 AOF 模式也和“總是 SYNC ”模式類似,所以它們都是不持久的。
  • </ul>

    隔離性和一致性

    redis事務在執行的過程中,不會處理其它命令,而是等所有命令都執行完后,再處理其它命令(滿足隔離性)
    redis事務在執行過程中發生錯誤或進程被終結,都能保證數據的一致性;(詳見參考資料1)

    redis事務的缺陷

    除了不保證原子性和持久性,在實際使用中還有以下問題:

    1) 遇到有查詢的情況穿插在事務中間,不會返回結果;
    設置事務開始標志后,所有的命令都是queued,即使是查詢指令;
    如果后續的更新操作需要依賴于前面的查詢指令,那redis事務就無法有效的完成任務;
    例如:

    redis 127.0.0.1:7000> multi
    OK
    redis 127.0.0.1:7000> set a aaa
    QUEUED
    redis 127.0.0.1:7000> get b
    QUEUED
    業務邏輯...
    redis 127.0.0.1:7000> set c ccc
    QUEUED
    redis 127.0.0.1:7000> exec
    1) OK
    2) bbb
    3) OK

    第二步 get a 返回的是queued,并不是a的查詢結果,
    如果后續的set操作依賴于get的結果(存在依賴業務邏輯),就不能將get操作放在事務操作中;

    2) 事務中的每條命令都與redis服務器進行了一次網絡交互;
    redis 事務指定開始后,執行一個事務返回的都是queued,那這個入隊操作是在客戶端實現,還是在服務器端實現的?
    查看源碼,很容易發現是在服務器端實現;
    在Redis.c中有這么一段:

    int processCommand(redisClient c) {
    ...
        / Exec the command */
        if (c->flags & REDIS_MULTI &&
            c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
            c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
        {
            queueMultiCommand(c); // 將事務中的命令都放入到隊列中,然后返回"QUEUED"
            addReply(c,shared.queued);
        } else {
            if (server.vm_enabled && server.vm_max_threads > 0 &&
                blockClientOnSwappedKeys(c)) return REDIS_ERR;

        //調用該命令函數來處理命令
        call(c);
    }
    return REDIS_OK;
    

    }</pre>

    這里就涉及到客戶端與服務器端的多次交互,明明是需要一次批量執行的n條命令,還需要通過多次網絡交互,有些浪費;

    更新操作中的查詢實現

    如果有這樣的需求:在事務開始后,中間穿插有查詢邏輯;
    那么使用redis事務(單庫),無法滿足這個要求;

    可能的解決方案:

    1. 可以考慮使用多個庫,讀寫分離,查詢庫只用來查詢,更新庫用來開事務做寫操作;

      </li>

    2. 不再使用redis的事務指令,自己在客戶端將待執行的命令批量打包,決定是否回滾還是全部執行;這樣可以在更新的間隙執行查詢邏輯;而不需要將查詢邏輯提前到事務指令multi之前;

      </li>

    3. 將查詢業務邏輯提前;嚴格規范代碼編寫要求,所有的redis查詢邏輯都放在事務之外:

      redis 127.0.0.1:7000> get b
       bbb
       業務邏輯...
       redis 127.0.0.1:7000> multi
       OK
       redis 127.0.0.1:7000> set a aaa
       QUEUED
       redis 127.0.0.1:7000> set c ccc
       QUEUED
       redis 127.0.0.1:7000> exec
       1) OK
       2) OK
      </li> </ol>

      優化網絡特性

      將多個命令打包批量發送到redis服務器執行,減少網絡交互,優化性能,可能的解決方案:

      1. 對于所有的get/set操作,可使用現有的mget/mset指令;
      2. 對于各種不同類型的更新操作,可使用lua腳本將命令打包后,發送到服務器端一次執行;
      3. </ol>

        參考

        http://redisbook.readthedocs.org/en/latest/feature/transaction.html

        來自:

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!