首頁  >  文章  >  後端開發  >  詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

高洛峰
高洛峰原創
2017-03-19 10:05:352335瀏覽

本文主要介紹了.NET客戶端實作Redis中的管道(PipeLine)與事物(Transactions)的相關知識。具有很好的參考價值,以下跟著小編一起來看下吧

序言

Redis中的管道(PipeLine)特性:簡述一下就是,Redis如何從客戶端一次發送多個命令,服務端到客戶端如何一次回應多個命令。

Redis使用的是客戶端-伺服器模型和請求/回應協定的TCP伺服器,這就意味著一個請求要有以下步驟才能完成:1、客戶端向伺服器發送查詢命令,然後通常以阻塞的方式等待伺服器對應。 2、伺服器處理查詢命令,並將對應傳回客戶端。這樣便會透過網路連接,如果是本地回環介面那麼就能特別迅速的回應,但是如果走外網,甚至外網再做一系列的層層轉發,那就顯的格外蛋疼。無論網路延遲是多少,那麼都將佔用整體回應的時間。這樣一來如果一次發送1個指令,網路延遲為100ms,我們不得不做。那如果1次發1000個指令,那麼網路延時100*1000ms就很難容忍啦。

針對與上面的問題,Redis在2.6版本以後就都提供啦管道(Pipeline)功能。他可以使客戶端在沒有讀取舊的回應時,處理新的請求。這樣便可以向伺服器發送多個命令,而不必等待答复,直到最後一個步驟中讀取答复。這被稱為管線(PipeLine),並且是幾十年來廣泛使用的技術。例如,許多POP3協定實作已經支援此功能,大大加快了從伺服器下載新電子郵件的過程。

那麼事務這個詞彙,常常遇到,就不多唧唧啦,目標要一致就好,便是一組操作怎麼做成原子性操作,使他去不了終點,回到原點。

簡述wireshark抓包工具

為啦讓大家對管線有更形象的感觀,這一節我們先說說Wireshark抓包工具,他會讓你看到客戶端到伺服器透過tcp協定發送的redis命令的過程與詳細。

wireshark能夠撲捉到系統發送和接受的每一個報文,我們這裡只做一些過濾的簡述。下圖就是他的樣子,你打開後可以摸他的用法。

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

簡述幾個過濾規則:

1、ip過濾:目標ip過濾:ip.dst==172.18.8.11,來源ip位址過濾:ip.src==192.168.1.12;

2、埠過濾:tcp.port==80,這條規則是把來源埠和目的埠為80的都過濾出來。使用tcp.dstport==80只過濾目的埠為80的,tcp.srcport==80只過濾來源埠為80的套件;

3、協定過濾:直接在fiter框中輸入協定名稱即可,如:http,tcp,udp,...

4、http模式過濾:過濾get包,http.request.method=="GET",過濾post包,http.request.method= ="POST";

5、如果使用多條件過濾,則需要加連接符號,and。例如ip.src==192.168.1.12 and http.request.method=="POST" and tcp.srcport==80

##StackExchange.Redis實作Redis管線(Pipeline)

上兩張圖片管線便一目了然啦。

客戶端對redis伺服器進行多次請求的話,一般普通模式是這樣子的

客戶端對redis伺服器進行多次請求的話,管道模式是這樣子的

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

一般模式我們上程式碼:


public static void GetNoPipelining()
  {
   for (var i = 0; i < 3; i++)
   {
    var key = "name:" + i;
    db.StringAppend(key, "张龙豪");
   }
  }


詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

查看tcp請求封包的data

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions#

这样你自己做的过程中,可以看到我圈起来的3个tcp请求的key分别为name:0,name:1,name:2这样子。

那么我们使用管道模式

public static void GetPipelining()
  {
   var batch = db.CreateBatch();
   for (int i = 0; i < 3; i++)
   {
    var key = "mename:" + i;
    batch.StringAppendAsync(key, "张龙豪");
   }
   batch.Execute();
  }


再来看下请求

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

这样很明显就能看出来是1个请求发送出来啦多个命令。那么我们不用createBatch()也是可以实现这样的效果的。

var a = db.StringAppendAsync("zlh:1", "zhanglonghao1");
   var b = db.StringAppendAsync("zlh:2", "zhanglonghao2");
   var c = db.StringAppendAsync("zlh:3", "zhanglonghao3");
   var aa = db.Wait(a);
   var bb = db.Wait(a);
   var cc = db.Wait(a);


在接下来我们做一个简单的性能比较。代码如下:

static void Main(string[] args)
  {
   Stopwatch watch = new Stopwatch();
   Stopwatch watch1 = new Stopwatch();
   watch.Start();
   GetNoPipelining();
   Console.WriteLine("一般循环耗时:" + watch.ElapsedMilliseconds);
   watch.Stop();
   watch1.Start();
   GetPipelining();
   Console.WriteLine("Pipelining插入耗时:" + watch1.ElapsedMilliseconds);
   watch1.Stop();
   Console.ReadLine();
  }
  public static void GetNoPipelining()
  {
   for (var i = 0; i < 5000; i++)
   {
    var key = "name:" + i;
    db.StringAppend(key, "张龙豪");
   }
  }
  public static void GetPipelining()
  {
   var batch = db.CreateBatch();
   for (int i = 0; i < 5000; i++)
   {
    var key = "mename:" + i;
    batch.StringAppendAsync(key, "张龙豪");
   }
   batch.Execute();
  }


结果如下:

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

到此我还要说一下StackExchange.Redis的三种命令模式,其中使用2和3的模式发送命令,会默认被封装在管道中,不信的话,你可以做个小demo测试下:

1、sync:同步模式,会直接阻塞调用者,但不会阻塞其他线程。

2、async:异步模式,使用task模型封装。

3、fire-and-forget:发送命令,然后完全不关心最终什么时候完成命令操作。在Fire-and-Forget模式下,所有命令都会立即得到返回值,该值都是该返回值类型的默认值,比如操作返回类型是bool将会立即得到false,因为false = default(bool)。

StackExchange.Redis实现Redis事务(Transactions)

这个看官方文档,我只能说实现的很奇怪吧。我先描述下我的环境,就是准备一个空redis库,然后一步一步往下走,我们写代码看结果,来搞一搞这个事务。

static void Main(string[] args)
  {
   var tran = db.CreateTransaction();
   tran.AddCondition(Condition.ListIndexNotEqual("zlh:1",0,"zhanglonghao"));  
   tran.ListRightPushAsync("zlh:1", "zhanglonghao"); 
   bool committed = tran.Execute();
   Console.WriteLine(committed);
   Console.ReadLine();
  }


执行结果为:true。数据库中结果如下,说明我们插入成功。

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

即:如果key为:zlh:1的list集合在索引0初的value!=zhanglonghao的话,我们从链表右侧插入一条数据key为zlh:1value为zhanglonghao,成功。因为第一次操作为空库。0处确实不为张龙豪。

数据不清空,继续上代码。

static void Main(string[] args)
  {
   var tran = db.CreateTransaction();
   tran.AddCondition(Condition.ListIndexNotEqual("zlh:1",0,"zhanglonghao"));  
   tran.ListRightPushAsync("zlh:1", "zhanglonghao1");   
   bool committed = tran.Execute();
   Console.WriteLine(committed);
   Console.ReadLine();
  }


结果为false,数据库没有增减数据。已久与上图的数据保持一致。

原因分析:0处此时为zhanglonghao,所以ListIndexNotEqual("zlh:1",0,"zhanglonghao")为假命题,直接回滚,不执行下面的插入命令。

数据不清空,继续上代码:

static void Main(string[] args)
  {
   var tran = db.CreateTransaction();
   tran.AddCondition(Condition.ListIndexEqual("zlh:1",0,"zhanglonghao"));  
   tran.ListRightPushAsync("zlh:1", "zhanglonghao1");   
   bool committed = tran.Execute();
   Console.WriteLine(committed);
   Console.ReadLine();
  }


结果为true,数据结果如下,增长一条值为zhanglonghao1的数据:

詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions

原因分析:ListIndexEqual("zlh:1",0,"zhanglonghao")为真命题,执行下面的操作,提交事物。

数据不删继续上代码:

static void Main(string[] args)
  {
   var tran = db.CreateTransaction();
   tran.AddCondition(Condition.ListIndexEqual("zlh:1",0,"zhanglonghao"));  
   tran.ListRightPushAsync("zlh:1", "zhanglonghao2");
   tran.AddCondition(Condition.ListIndexNotEqual("zlh:1", 0, "zhanglonghao"));
   tran.ListRightPushAsync("zlh:1", "zhanglonghao3");
   bool committed = tran.Execute();
   Console.WriteLine(committed);
   Console.ReadLine();
  }


结果为false,数据库数据已久与上面的保持一致,不增不减。

分析原因:Condition.ListIndexEqual("zlh:1",0,"zhanglonghao")为true,但是到下面的ListIndexNotEqual("zlh:1", 0, "zhanglonghao")为false。故整个事物的操作回滚,不予执行,故数据库没有变化。

到此,我就不写多余的代码啦,但我要说几个注意点:

1、执行命令的操作需为异步操作。

2、在事物中执行的命令,都不会直接看到结果,故此结果也不能用于下面代码做判断,因为当前的异步命令在Execute()之前是不会对数据库产生任何影响的。


以上是詳解.NET客戶端實作Redis中的管道PipeLine與事物Transactions的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn