当前位置:网站首页>.net core 关于redis的pipeline以及事务

.net core 关于redis的pipeline以及事务

2022-07-07 11:36:00 小兜全糖(Cx)

Redis本身是基于Request/Response协议(停等机制)的,正常情况下,客户端发送一个命令,等待Redis返回结果,Redis接收到命令,处理后响应。在这种情况下,如果同时需要执行大量的命令,那就是等待上一条命令应答后再执行,这中间不仅仅多了RTT(Round Time Trip),而且还频繁调用系统IO,发送网络请求。为了提升效率,这时候pipeline出现了,它允许客户端可以一次发送多条命令,而不等待上一条命令执行的结果,这和网络的Nagel算法有点像(TCP_NODELAY选项)。pipeline不仅减少了RTT,同时也减少了IO调用次数(IO调用涉及到用户态到内核态之间的切换)

要支持Pipeline,其实既要服务端的支持,也要客户端支持。对于服务端来说,所需要的是能够处理一个客户端通过同一个TCP连接发来的多个命令,可以理解为,这里将多个命令切分,和处理单个命令一样(之前老生常谈的黏包现象),Redis就是这样处理的。而客户端,则是要将多个命令缓存起来,缓冲区满了或者达到发送条件就发送出去,最后才处理Redis的应答。

简单描述其实就是一次性发送多个redis命令道服务器,减少了请求次数

在这里插入图片描述
Redis的Pipeline和Transaction(Redis事务)不同,Transaction会存储客户端的命令,最后一次性执行,而Pipeline则是处理一条(批次),响应一条,从二者的不同处理机制来看,Redis事务中命令的执行是原子的(注意,其中一部分命令出现错误后续命令会继续执行,这里的原子说的是命令执行是完整的,中间不会被其他Redis命令所打断),而pipeline中命令的执行不一定是原子的。但是这里却有一点不同,就是pipeline机制中,客户端并不会调用read去读取socket里面的缓冲数据(除非已经发完pipeline中所有命令),这也就造成了,如果Redis应答的数据填满了该接收缓冲(SO_RECVBUF),那么客户端会通过ACK,WIN=0(接收窗口)来控制服务端不能再发送数据,那样子,数据就会缓冲在Redis的客户端应答缓冲区里面。所以需要注意控制Pipeline的大小

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"));
        }

redis 的事务有ACID四个特性

redis的事务实际上是运行了multi 命令,然后运行想要运行的命令,最后执行exec命令做一次提交就可以

StackExchange.Redis中的事物控制

multi命令只是保证一个事物中的所有命令可以在一起执行,显然只是实现这一点的话对于大部分的业务都是无法满足的。

所以Redis提供了Watch命令来监控一个key以达到乐观锁的效果。
在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"));
        }

.net core 中的使用

using CoreRedis.Config;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

namespace CoreRedis.RedisDataType
{
    
    
    public class RedisPipelineTranc
    {
    
        private ConnectionMultiplexer connectionMultiplexer;

        private readonly RedisConfig _redisConfig;

        private IDatabase db;

        public RedisPipelineTranc(IOptionsMonitor<RedisConfig> optionsMonitor)
        {
    
            _redisConfig = optionsMonitor.CurrentValue;
            connectionMultiplexer = ConnectionMultiplexer.Connect(_redisConfig.Value);
            db = connectionMultiplexer.GetDatabase();

        }

        public async Task<string>  testPipelien()
        {
    
            // intialize key with empty string
            await db.StringSetAsync("key", "");

            // create transaction that utilize multiplexing and pipelining
            ITransaction transacton = db.CreateTransaction();
            Task<long> appendA = transacton.StringAppendAsync("key", "a");
            Task<long> appendB = transacton.StringAppendAsync("key", "b");
            Task<long> appendc = transacton.StringAppendAsync("key", "c");

            if (await transacton.ExecuteAsync()) // sends "MULTI APPEND KEY a APPEND KEY b EXEC
                                                 // in single request to redis server
            {
    
                 order here doesn't matter, result is always - "abc". 
                 'a' and 'b' append always together in isolation of other Redis commands
                 'c' appends to "ab" because transaction is already executed successfully
                //await appendA;
                //await db.StringAppendAsync("key", "c");
                //await appendB;

                Task.WaitAll(appendA, appendB, appendc);
            }

            string value = db.StringGet("key"); // value is "abc"

            return value;
        }
    }
}

原网站

版权声明
本文为[小兜全糖(Cx)]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_43632687/article/details/125255598