管道和链接复用

    让我们考虑一些常规的程序代码:

    在涉及的步骤方面,这看起来大致是这样:

    1. [c=>s] # network: request one is sent to the server
    2. [server] # server: the server processes request 1
    3. [s=>c] # network: response one is sent back to the client
    4. [resp1] # client: the client library parses response 1
    5. [c=>s]
    6. [server]
    7. [s=>c]
    8. [resp2]

    现在让我们突出客户端正在做的一些事的时间点

    请记住,这是不成比例的 - 如果这是按时间缩放,它将是完全由waiting 控制的。

    • 在稍后的代码块等待直到操作完成(.Wait()
    • 当操作完成时,调度一个后续操作(.ContinueWith(...)await

    例如,要使用过程化(阻塞)代码来借助管道传递这两个 get 操作,我们可以使用:

    1. var aPending = db.StringGetAsync("a");
    2. var bPending = db.StringGetAsync("b");
    3. var a = db.Wait(aPending);
    4. var b = db.Wait(bPending);

    注意,我在这里使用db.Wait,因为它会自动应用配置的同步超时,但如果你喜欢你也可以使用 aPending.Wait()Task.WaitAll(aPending,bPending);
    使用管道技术,我们可以立即将这两个请求发送到网络,从而消除大部分延迟。
    此外,它还有助于减少数据包碎片:单独发送(等待每个响应)的20个请求将需要至少20个数据包,但是在管道中发送的20个请求可以通过少得多的数据包(也许只有一个)。

    管道的一个特例是当我们明确地不关心来自特定操作的响应时,这允许我们的代码在排队操作在后台继续时立即继续。 通常,这意味着我们可以在单个调用者的连接上并发工作。 这是使用 flags 参数来实现的:

    FireAndForget 标志使客户端库正常地排队工作,但立即返回一个默认值(因为 KeyExpire 返回一个 ,这将返回 false ,因为 default(bool)false - 但是返回值是无意义的,应该忽略)。
    这也适用于 *Async 方法:一个已经完成的 Task <T> 返回默认值(或者为 void 方法返回一个已经完成的 Task )。

    因此,StackExchange.Redis不提供的唯一redis特性(不会提供)是“阻塞弹出”(,BRPOP 和 ),因为这将允许单个调用者停止整个多路复用器,阻止所有其他调用者 。
    StackExchange.Redis 需要保持工作的唯一其他时间是在验证事务的前提条件时,这就是为什么 StackExchange.Redis 将这些条件封装到内部管理的 condition 实例中。

    在这里阅读更多关于事务
    如果你觉得你想“阻止出栈”,那么我强烈建议你考虑 发布 / 订阅 代替:

    1. string work = db.ListRightPop(key);
    2. if (work != null) Process(work);
    3. });
    4. //...
    5. sub.Publish(channel, "");

    这实现了相同的目的,而不需要阻塞操作。 注意:

    • 数据 不通过 发布 / 订阅 发送; 发布 / 订阅 API只用于通知处理器检查更多的工作
    • 如果没有处理器,则新项目保持缓存在列表中; 工作不会丢失
    • 只有一个处理器可以弹出单个值; 当消费者比生产者多时,一些消费者会被通知,然后发现没有什么可做的
    • 当你重新启动一个处理器,你应该假设 有工作,以便你处理任何积压的任务

    StackExchange.Redis 的多路复用特性使得在使用常规的不复杂代码的同时在单个连接上达到极高的吞吐量成为可能。