【译】StackExchange.Redis 中文文档(三)流水线与多路复用

流水线与多路复用

延迟很糟糕。 现代计算机以惊人的速度处理数据,高速网络(通常在重要服务器之间具有多个并行链接)提供了巨大的带宽,但是延迟意味着计算机要花费大量时间等待数据,这就是 continuation-based programming 变得越来越流行的几个原因之一。让我们看一下以下代码:

string a = db.StringGet("a");
string b = db.StringGet("b");

执行过程如下所示:

[req1]                         # client: the client library constructs request 1
     [c=>s]                    # network: request one is sent to the server
          [server]             # server: the server processes request 1
                 [s=>c]        # network: response one is sent back to the client
                      [resp1]  # client: the client library parses response 1
                            [req2]
                                 [c=>s]
                                      [server]
                                             [s=>c]
                                                  [resp2]

在客户端角度,操作过程如下所示:

[req1]
     [====waiting=====]
                      [resp1]
                            [req2]
                                 [====waiting=====]
                                                  [resp2]

请谨记,这不是按比例缩放的。

流水线(Pipelining)

许多 redis 客户端都允许使用流水线。通过流水线发送多个消息,无需等待每个消息的回复,通常消息回复会在后续进行处理。在 .NET 中,可以通过基于 TPLTask / Task<T> APIs 创建未执行的 Task。然后:

  • 可调用 .Wait() 等待 Task 完成。
  • 可调用 .ContinueWith(...) 或者 await 安排 Task 的完成后执行的 Task

例如,通过流水线获取两个字符串:

var aPending = db.StringGetAsync("a");
var bPending = db.StringGetAsync("b");
var a = db.Wait(aPending);
var b = db.Wait(bPending);

在这里使用 db.Wait,会自动使用配置里的同步超时。也可以使用 aPending.Wait()Task.WaitAll(aPending,bPending)。流水线可以让我们同时发送多个请求,从而减轻延迟。

此外,它还有助于减少数据包碎片:单独发送20个请求(等待每个响应)将至少需要20个数据包,但是在流水线中发送的20个请求使用更少的数据包(可能只用一个)。

Fire and Forget

流水线的一种特殊情况是我们明确不需要操作结果,不阻塞主程序,由后台排队处理。

// sliding expiration
db.KeyExpire(key, TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget);
var value = (string)db.StringGet(key);

FireAndForget 标志让操作不阻塞主程序,并且立即返回默认值(无需理会)。这也适用于 *Async 方法:使用默认值返回一个已经完成的 Task<T>

多路复用(Multiplexing)

无论是单连接同步调用还是多连接并发调用,都会产生很多等待时间。而 StackExchange.Redis 通过多路复用把多个连接合并,利用流水线进行传输,有效利用空闲时间。无论是阻塞访问还是异步访问,都将通过流水线传输。

StackExchange.Redis 唯一没有实现 redis 特性:"blocking pops"(BLPOPBRPOPBRPOPLPUSH)。因为这允许单个调用者停止整个多路复用,从而阻塞 所有其他调用者。

StackExchange.Redis 唯一需要保留工作的时间是验证事务的前提条件,这就是为什么 StackExchange.Redis 将此类条件封装到内部托管的 Condition 实例中的原因。 查看更多事务(transactions)。 如果想要 "blocking pops",建议使用 pub/sub:

sub.Subscribe(channel, delegate {
    string work = db.ListRightPop(key);
    if (work != null) Process(work);
});
//...
db.ListLeftPush(key, newWork, flags: CommandFlags.FireAndForget);
sub.Publish(channel, "");

这样就可以达到相同的目的,而无需阻塞操作。

  • 数据不是通过 pub/sub 发送的;pub/sub API 仅用于通知工作程序检查更多工作。
  • 如果没有工作程序,新工作保留在列表中;工作将不会执行。
  • 只有一个工作程序可以 pop 一个值;当消费者比生产者多时,将通知一些消费者没有工作处理。
  • 当您重新启动工作程序时,应该假设有工作,以便您处理所有积压工作。
  • 除此之外,其语义与 "blocking pops" 相同

StackExchange.Redis 的多路复用在单个连接上达到极高的吞吐量。

并发(Concurrency)

流水线 / 多路复用 / 延迟值在 continuation-based asynchronous code 中也可以很好地发挥作用。 例如:

string value = await db.StringGetAsync(key);
if (value == null) {
    value = await ComputeValueFromDatabase(...);
    db.StringSet(key, value, flags: CommandFlags.FireAndForget);
}
return value;

原文地址:Pipelines and Multiplexers

原文地址:https://www.cnblogs.com/liang24/p/13847075.html