1. 程式人生 > >StackExchange.Redis學習筆記(四) 事務控制和Batch批量操作

StackExchange.Redis學習筆記(四) 事務控制和Batch批量操作

成了 pan arp 展示 關於 public 連續 因此 用戶

Redis事物

Redis命令實現事務

Redis的事物包含在multiexec(執行)或者discard(回滾)命令中

和sql事務不同的是,Redis調用Exec只是將所有的命令變成一個單元一起執行,期間不會插入其他的命令。

這種方式不保證事務的一致性,即使中間有一條命令出錯了,其他命令仍然可以正常執行,並且無法回滾

下面的例子演示了一個基本的事務操作

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name mike
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379> get name
"mike"

可以看到,直到調用Exec命令時,才開始執行之前的所有命令,同時會返回兩個結果,discard 命令類似,就不貼代碼了。

下面模擬一個會報錯的命令來看一下

127.0.0.1:6379> get age
"20"
127.0.0.1:6379> get name
"mike"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) (integer) 21
127.0.0.1:6379> get age
"21"
127.0.0.1:6379>

我們同時將用戶name和age 進行自增1操作,然而name不是數字類型,結果執行失敗,但是age的自增操作仍然成功了。這無疑是個很令人不舒服的弊端,所以在寫相關代碼時要註意

樂觀鎖

前面說到通過multi命令只是保證一個事物中的所有命令可以在一起執行,顯然只是實現這一點的話對於大部分的業務都是無法滿足的。

所以Redis提供了Watch命令來監控一個key以達到樂觀鎖的效果。關於樂觀鎖的原理有不了解的小夥伴可以抽十分鐘去科普一下

下面展示一個樂觀鎖實例:

技術分享圖片 技術分享圖片

這裏模擬了兩個客戶端同時操作一個相同的鍵

左邊為client1,我們用watch監控了name和age兩個鍵,然後分別設置name和age的值。在exec命令之前,通過另一個客戶端client2設置了name的值。

client1執行exec命令時,Redis檢測到name的值已經被其他客戶端改過了,因此在事物中的所有命令都會回滾。

watch命令是對整個連接有效的,用完之後可以用discard、unwatch、exec命令清除監視

StackExchange.Redis中的事物控制

在StackExchange.Redis是無法用watch multi命令來執行的,因為在並發環境下,會產生多個watch multi命令,全混在一起就亂套了。

但是StackExchange.Redis提供了一套非常簡單易懂的創建事物的方式 ,下面為示例代碼

 public void TestTran()
        {
            IDatabase db = StackExchangeRedisHelper.GetDatabase();
            string name = db.StringGet("name");
            string age = db.StringGet("age");
            Console.WriteLine("NAME:" + name);
            Console.WriteLine("Age:" + age);
            var tran = db.CreateTransaction();
            tran.AddCondition(Condition.StringEqual("name", name));
            Console.WriteLine("tran begin");
            tran.StringSetAsync("name", "leap");
            tran.StringSetAsync("age", 12);
            Thread.Sleep(4000);
            bool result = tran.Execute();
            Console.WriteLine("執行結果:" + result);
            Console.WriteLine("Age:" + db.StringGet("age"));
            Console.WriteLine("Name:" + db.StringGet("name"));
        }

這裏通過CreateTransaction函數(multi)來創建一個事物,調用其Execute函數(exec)提交事物,其中的 "Condition.StringEqual("name", name)" 就相當於Redis命令中的watch name。

其中睡眠四秒是我需要在事物提交之前打開另一個客戶端來修改name的值.最終的執行結果如下

NAME:leo
Age:20
tran begin
執行結果:False
Age:20
Name:mike

  在程序睡眠期間我用另一個客戶端將name改成了mike,所以事物最終執行失敗

通過查詢Redis的慢日誌。其調用的命令也是watch multi exec。(慢日誌沒有記錄Exec命令,實際上是執行了的)

我們可以通過設置redis.windows-service.conf文件中的slowlog-log-slower-than的值為0讓Redis記錄所有的命令日誌

127.0.0.1:6379> slowlog get 100
 1) 1) (integer) 293
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "GET"
       2) "name"
 2) 1) (integer) 292
    2) (integer) 1511257634
    3) (integer) 0
    4) 1) "GET"
       2) "age"
 3) 1) (integer) 291
    2) (integer) 1511257634
    3) (integer) 3
    4) 1) "SELECT"
       2) "0"
 4) 1) (integer) 290
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "SET"
       2) "age"
       3) "12"
 5) 1) (integer) 289
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "SET"
       2) "name"
       3) "leap"
 6) 1) (integer) 288
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "MULTI"
 7) 1) (integer) 287
    2) (integer) 1511257634
    3) (integer) 3
    4) 1) "GET"
       2) "name"
 8) 1) (integer) 286
    2) (integer) 1511257634
    3) (integer) 11
    4) 1) "WATCH"
       2) "name"
 9) 1) (integer) 285
    2) (integer) 1511257634
    3) (integer) 4
    4) 1) "GET"
       2) "age"
10) 1) (integer) 284
    2) (integer) 1511257634
    3) (integer) 6
    4) 1) "GET"
       2) "name"

  這裏可能大家會有個疑惑,既然tran是直接調用的watch multi等命令,為什麽不會有並發的順序問題?

這是因為Tran開啟後,所做的watch,stringset等操作,都會再調用Exec函數時把相應的命令封裝成一個請求發送給Redis一起執行。這樣每個事務之間都是獨立的,就不會有問題了。

Batch批量操作

StackExchange.Redis中對於連續多次的緩存等請求,我們會多次調用相關的函數來執行Redis命令。然而這種方式有個弊端就是每一次的請求都需要等待返回結果

如果在網絡狀況不好的情況下,可能會造成不好的用戶體驗。

對於這種問題可以用StackExchange.Redis提供的CreateBatch()解決

 public void TestPipeLine()
        {
            IDatabase db = StackExchangeRedisHelper.GetDatabase();
            var batch = db.CreateBatch();
            Task t1 = batch.StringSetAsync("name", "bob");
            Task t2 = batch.StringSetAsync("age", 100);
            batch.Execute();
            Task.WaitAll(t1, t2);
            Console.WriteLine("Age:" + db.StringGet("age"));
            Console.WriteLine("Name:" + db.StringGet("name"));
        }

batch會把所需要執行的命令打包成一條請求發到Redis,然後一起等待返回結果。這樣批量操作的速度就大大提升啦!

StackExchange.Redis學習筆記(四) 事務控制和Batch批量操作