redis-事務

 

Redis 在形式上看起來也差很少,分別是 multi/exec/discard。multi 指示事務的開始,exec 指示事務的執行,discard 指示事務的丟棄。由於 Redis 的單線程特性,它不用擔憂本身在執行隊列的時候被其它指令打攪,能夠保證他們能獲得的「原子性」執行。java

 Redis 的事務根本不能算「原子性」,而僅僅是知足了事務的「隔離性」,隔離性中的串行化——當前執行的事務有着不被其它事務打斷的權利。redis

Redis 爲事務提供了一個 discard 指令,用於丟棄事務緩存隊列中的全部指令,在 exec 執行以前。數據庫

> get books
(nil)
> multi
OK
> incr books
QUEUED
> incr books
QUEUED
> discard
OK
> get books
(nil)

咱們能夠看到 discard 以後,隊列中的全部指令都沒執行,就好像 multi 和 discard 中間的全部指令從未發生過同樣。緩存

Redis 提供了這種 watch 的機制,它就是一種樂觀鎖。有了 watch 咱們又多了一種能夠用來解決併發修改的方法服務器

watch 會在事務開始以前盯住 1 個或多個關鍵變量,當事務執行時,也就是服務器收到了 exec 指令要順序執行緩存的事務隊列時,Redis 會檢查關鍵變量自 watch 以後,是否被修改了 (包括當前事務所在的客戶端)。若是關鍵變量被人動過了,exec 指令就會返回 null 回覆告知客戶端事務執行失敗,這個時候客戶端通常會選擇重試。併發

注意事項spa

Redis 禁止在 multi 和 exec 之間執行 watch 指令,而必須在 multi 以前作好盯住關鍵變量,不然會出錯。線程

下面咱們再使用 Java 語言實現一遍。日誌

import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TransactionDemo {

  public static void main(String[] args) {
    Jedis jedis = new Jedis();
    String userId = "abc";
    String key = keyFor(userId);
    jedis.setnx(key, String.valueOf(5));  # setnx 作初始化
    System.out.println(doubleAccount(jedis, userId));
    jedis.close();
  }

  public static int doubleAccount(Jedis jedis, String userId) {
    String key = keyFor(userId);
    while (true) {
      jedis.watch(key);
      int value = Integer.parseInt(jedis.get(key));
      value *= 2; // 加倍
      Transaction tx = jedis.multi();
      tx.set(key, String.valueOf(value));
      List<Object> res = tx.exec();
      if (res != null) {
        break; // 成功了
      }
    }
    return Integer.parseInt(jedis.get(key)); // 從新獲取餘額
  }

  public static String keyFor(String userId) {
    return String.format("account_%s", userId);
  }

}

redis爲何不支持回滾?code

不支持回滾操做是由於redis是先執行指令而後作日誌,因此即便發生異常,沒有能夠用來執行回滾操做的日誌。因此這也回答了上篇文章的第二問,爲何傳統的數據庫都是先作日誌而後再作操做。

相關文章
相關標籤/搜索