이 글에서는 Redis에서 PipeLine과 Transaction을 구현하기 위한 .NET 클라이언트 관련 지식을 주로 소개합니다. 매우 좋은 참고값을 가지고 있으니 아래 에디터로 살펴보겠습니다
서문
Redis의 PipeLine 기능: 간단히 설명하자면 Redis 전송 방법 클라이언트에서 한 번에 여러 명령을 수행하는 방법, 서버에서 클라이언트로 한 번에 여러 명령에 응답하는 방법 등을 설명합니다.
Redis는 클라이언트-서버 모델과 요청/응답 프로토콜의 TCP 서버를 사용합니다. 즉, 요청을 완료하려면 다음 단계를 거쳐야 합니다. 1. 클라이언트가 요청을 다음으로 보냅니다. 서버 명령을 쿼리한 다음 일반적으로 차단 방식으로 서버의 응답을 기다립니다. 2. 서버는 쿼리 명령을 처리하고 클라이언트에 응답을 다시 보냅니다. 이런 방식으로 네트워크를 통해 연결되면 로컬 루프백 인터페이스인 경우 매우 빠르게 응답할 수 있습니다. 그러나 외부 네트워크로 이동하거나 외부 네트워크에서도 일련의 레이어를 수행합니다. 전달하는 것은 특히 고통스러울 것입니다. 네트워크 지연이 무엇이든 전체 응답 시간을 차지합니다. 이런 식으로 한 번에 하나의 명령을 보내면 네트워크 지연이 100ms가 되는데 그렇게 해야 합니다. 따라서 1000개의 명령이 동시에 전송된다면 100*1000ms의 네트워크 지연은 견디기 어려울 것입니다.
위 문제에 대응하여 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. 포트 필터링: tcp.port==80. 이 규칙은 소스 포트와 대상 포트 80을 모두 필터링합니다. tcp.dstport==80을 사용하면 대상 포트가 80인 패킷만 필터링할 수 있고, tcp.srcport==80을 사용하면 소스 포트가 80인 패킷만 필터링할 수 있습니다.
3. 필터 상자의 프로토콜 이름(예: http, tcp, udp,...
4. http 모드 필터링: 필터 가져오기 패킷, http.request.method=="GET", 필터 게시물 packets, http.request.method= ="POST";
5. 여러 조건을 사용하여 필터링하는 경우 연결 기호를 추가해야 합니다. 예를 들어, ip.src==192.168.1.12 및 http.request.method=="POST" 및 tcp.srcport==80
StackExchange.Redis는 Redis 파이프라인(파이프라인)을 구현합니다.
위의 두 이미지 파이프라인은 한 눈에 명확합니다.
클라이언트가 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!