博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
管道和多路复用器
阅读量:5812 次
发布时间:2019-06-18

本文共 3195 字,大约阅读时间需要 10 分钟。

延迟情况是难以忍受的。现代计算机能以惊人的速度生成数据,并且高速互联网(经常是在重要的服务器之间有多个并行连接)提供了极大的带宽,但是这可恶的延迟意味着电脑花了大量时间等待数据。基于延续的编程变得越来越流行的几个原因之一。让我们考虑一些规则的程序代码:

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

按照这些关联的步骤,这看起来像:

[req1]                         # 客户端 : 客户端库构造出一个请求1         [c=>s]                    # 网络   : 请求1被发送到服务器              [server]             # 服务器 : 服务器处理请求1                     [s=>c]        # 网络 : 响应1被发送回客户端 [resp1] # 客户端 : 客户端库解析响应数据1 [req2] [c=>s] [server] [s=>c] [resp2]

现在让我们突出客户端处理的部分:

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

记住这是不可测量的,如果是用时间来衡量,那么会一直等待下去(一直耗费时间在等待处理)。

管道

由于这个原因,很多Redis客户端允许你利用管道,处理发送多个消息而无需等待每一个的回复,并且当消息进来的时候,回复的处理将会延后。在.NET中,一个操作可以被初始化且尚未完成;在完成后或者发生错误后由通过 / 的API封装。本质上,Task<T> 表示的是一个"将来可能的T类型的值"(非泛型 Task 实际上是 Task<void>)。你可以二选其一:

  • .Wait() (阻塞执行,直到任务完成)
  • .ContinueWith(...)或者await (创建一个在目标任务完成时异步执行的延续任务)

例如:下面是Redis客户端利用管道的示例代码:

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个请求只需要少数几个包(甚至只需要一个包)。

即发即弃

一个特别的管道案例是当我们不关心操作的响应,允许代码继续执行且排队操作是在后台处理的时候。这通常意味着我们能把并发工作放在来自一个单独调用的连接中。我们可以使用 flags 参数来实现:

// 可调期限db.KeyExpire(key, TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget);var value = (string)db.StringGet(key);

FireAndForget 标记会使客户端库去正常的排队工作,但是会立即返回一个默认值(KeyExpire 会返回一个 bool 类型,这将返回 false,因为默认值是 false - 然而返回的是毫无意义的值,我们应该忽略)。*Async 方法也会返回一个已完成的 Task<T>作为默认值(或者一个已完成的 Task 作为 void 返回 )。

多路复用(Multiplexing)

使用管道处理技术是非常好的,但是我们经常单独使用阻塞代码仅去取一个单独的值(或者可能只执行一些操作,这取决于各自的需要)。这意味着我们仍然有这样一个问题:我们花费大量的时间去等待数据从客户端传输到服务器端。现在我们考虑一个繁忙的应用,这可能是一个web服务。这类应用通常都具有高并发性,当你有20个并行应用请求所有需要的数据,你可能想旋转(spinning up)这20个连接,或者你可以同步访问一个单独连接(这意味着最后的调用者需要等待前面19个全部执行完成才开始)。或者作为一个妥协方式,也许是个出租5个连接的连接池--不管你怎么做,都会有大量的等待操作。StackExchange.Redis 不需要那样做;反而,它为你做了大量的工作,通过多路复用单个连接,使你可以有效的利用空余的时间。当不同的调用方同时访问时,它会自动使用管道分离访问请求,所以无论使用阻塞方式或者异步方式访问,这些工作都是被管道处理的。因此我们可以有10或者20个先前的"get a and b"的场景(来自不同应用的请求),并且它们会尽快的取得连接。从本质上讲,它填补了 waiting 时间与其他调用方的工作。

因此,StackExchange.Redis不会提供(并将永远不会提供)"阻塞弹出(blocking pops)"(,  以及 ) - 因为这将允许一个单独的调用方拖延整个多路复用器,进而阻塞所有的调用方。 StackExchange.Redis 需要保持的工作是为了验证某个事务的前提条件,这就是为什么StackExchange.Redis封装了这样的条件在内部管理 Condition 实例。。如果你想要"阻塞弹出(blocking pops)",那么我强烈建议你考虑使用发布/订阅功功能:

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

注意:无需阻塞操作即可达到相同的目的有:

  • 数据不是通过发布/订阅发送的;发布/订阅API仅被用来通知工人来检查更多的工作

  • 如果没有工人,那么新项仍然在缓冲列表中,工作不会执行

  • 仅有一个工人能弹出一个值;当消费者多于生产者,一些消费者将得到通知然后发现没有什么可做的

  • 当你重新启动工人,你应该假设有积压工作可以处理

  • 除此之外,对于阻塞弹出的语义是相同的

并发

应该注意的是管道/多路复用器/future-value 等方式与基于 延续的异步代码也是做得非常好的;例如:

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

转载于:https://www.cnblogs.com/carldai/p/5497031.html

你可能感兴趣的文章
+++++++子域授权与编译安装(一)
查看>>
编程修养(七)
查看>>
数学函数
查看>>
使用route-map 配置BGP本地优先级
查看>>
asp.net怎样在URL中使用中文、空格、特殊字符
查看>>
ISA2006实战系列之二:实战ISA三种客户端部署方案(下)
查看>>
Linux后门入侵检测工具,附bash漏洞最终解决方法
查看>>
ASA5585-S20测试方案
查看>>
利用for循环打印实心棱形和空心棱形
查看>>
路由器发布服务器
查看>>
实现跨交换机VLAN间的通信
查看>>
oracle import & export 操作相关脚本
查看>>
LVS集群的体系结构,构建强壮的体系结构里负载均衡层、真实服务器层、后端共享存储层都是相辅相成...
查看>>
DBCC PAGE
查看>>
ant 实现批量打包android应用
查看>>
域环境下如何保护重要资料文件的安全(二)---IRM&RMS(上)
查看>>
Visual Studio 2010 Ultimate敏捷功能特性(上)
查看>>
HP ADG双循环阵列
查看>>
数据结构---二分查找算法
查看>>
OpenStack Juno系列之网络节点搭建
查看>>