この記事では主に、Redis で PipeLine とトランザクションを実装するための .NET クライアントの関連知識を紹介します。非常に優れた参照値です。以下のエディターで見てみましょう
はじめに
Redis の PipeLine 機能: Redis がどのように複数のコマンドを一度にクライアントからサーバーに送信するかを簡単に説明します。クライアントは複数のコマンドに同時に応答します。
Redis は、クライアントサーバー モデル と、リクエスト/レスポンス プロトコルの TCP サーバーを使用します。これは、リクエストを完了するには次の手順が必要であることを意味します: 1. クライアントはクエリ コマンドをサーバーに送信し、通常はブロックする このメソッドはサーバーの応答を待ちます。 2. サーバーはクエリ コマンドを処理し、応答をクライアントに送り返します。このように、ローカル ループバック インターフェイスの場合は、非常に高速に応答できます。ただし、外部ネットワークに接続する場合は、レイヤーごとに一連の処理が行われます。転送すると、特に苦痛になります。ネットワーク遅延がどのようなものであっても、全体の応答時間に影響を与えます。このように、一度に 1 つのコマンドを送信すると、ネットワーク遅延は 100ms になります。これを実行する必要があります。したがって、1000 個のコマンドが一度に送信された場合、100*1000 ミリ秒のネットワーク遅延を許容するのは困難になります。
上記の問題に対応して、Redis はバージョン 2.6 からパイプライン機能を提供しています。これにより、クライアントは古い応答を読み取ることなく新しい要求を処理できるようになります。これにより、最後のステップで応答が読み取られるまで応答を待つことなく、複数のコマンドをサーバーに送信できるようになります。これは PipeLine と呼ばれ、何十年にもわたって広く使用されているテクノロジーです。たとえば、多くの POP3 プロトコル実装はすでにこの機能をサポートしており、サーバーから新しい電子メールをダウンロードするプロセスが大幅に高速化されています。
その後、「トランザクション」という用語がよく出てくるので、あまり文句を言う必要はありません。目標は同じであるはずです。つまり、一連の操作をどのようにしてエンドポイントに到達できないようにアトミック操作にするかです。そして原点に戻る。
Wireshark パケット キャプチャ ツールの簡単な紹介
パイプラインをより鮮明に理解できるように、このセクションではまず Wireshark パケット キャプチャ ツールについて説明します。これにより、TCP を確認できるようになります。クライアントからサーバーに送信される redis コマンドのプロセスと詳細。
Wireshark は、システムによって送受信されるすべてのメッセージをキャプチャできます。ここでは、一部のフィルタリングについて簡単に説明します。開いた後の画像は次のとおりです。使用方法を確認できます。
いくつかのフィルタリング ルールを簡単に説明します:
1. IP フィルタリング: ターゲット IP フィルタリング: ip.dst==172.18.8.11、ソース IP アドレス フィルタリング: ip.src==192.168.1.12;
2. filtering: tcp.port==80、このルールは送信元ポートと宛先ポート 80 の両方を除外します。宛先ポートが 80 のパケットのみをフィルタリングするには tcp.dstport==80 を使用し、送信元ポートが 80 のパケットのみをフィルタリングするには tcp.srcport==80 を使用します。
3. プロトコル フィルタリング: プロトコル名を直接入力します。フィルタ ボックス (例: http、tcp、udp、...
) 4. http モード フィルタリング: 取得パケットのフィルタ、http.request.method=="GET"、ポスト パケットのフィルタ、http.request.method== POST";
5 、複数条件フィルタリングを使用する場合は、接続記号とを追加する必要があります。たとえば、ip.src==192.168.1.12、http.request.method=="POST"、および tcp.srcport==80
StackExchange.Redis は Redis パイプライン (Pipeline) を実装します
パイプラインは上の 2 つの写真を見てください。
クライアントが Redis サーバーに複数のリクエストを行う場合、通常モードは次のようになります
クライアントが Redis サーバーに複数のリクエストを行う場合、パイプライン モードは次のようになります
のコードを見てみましょう通常モード:
public static void GetNoPipelining() { for (var i = 0; i < 3; i++) { var key = "name:" + i; db.StringAppend(key, "张龙豪"); } }
TCPリクエストメッセージのデータを表示
这样你自己做的过程中,可以看到我圈起来的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(); }
再来看下请求
这样很明显就能看出来是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(); }
结果如下:
到此我还要说一下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。数据库中结果如下,说明我们插入成功。
即:如果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的数据:
原因分析: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()之前是不会对数据库产生任何影响的。
以上がRedis での PipeLine とトランザクションの .NET クライアント実装の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。