SignalR 的横向扩展简介

作者 :Patrick Fletcher

警告

本文档不适用于最新版本的 SignalR。 查看 ASP.NET Core SignalR

本主题中使用的软件版本

本主题的早期版本

有关 SignalR 早期版本的信息,请参阅 SignalR 旧版本

问题和评论

请留下反馈,说明你对本教程的喜爱程度,以及我们可以在页面底部的评论中改进的内容。 如果你有与本教程不直接相关的问题,可以将其发布到 ASP.NET SignalR 论坛StackOverflow.com

一般情况下,可通过两种方式缩放 Web 应用程序: 纵向扩展横向扩展

  • 纵向扩展意味着使用更大的服务器 (或更大的 VM) 更多的 RAM、CPU 等。
  • 横向扩展意味着添加更多服务器来处理负载。

纵向扩展的问题在于,你很快就会达到计算机大小的限制。 除此之外,还需要横向扩展。但是,横向扩展时,客户端可以路由到不同的服务器。 连接到一个服务器的客户端不会接收从另一个服务器发送的消息。

显示箭头从“客户端”到“负载均衡器”到“服务器”的关系图。

一种解决方案是使用称为 底板的组件在服务器之间转发消息。 启用底板后,每个应用程序实例都会向底板发送消息,而底板会将消息转发到其他应用程序实例。 (在电子产品中,底板是一组并行连接器。类比,SignalR 底板连接多个服务器。)

显示从 Signal R App Server 到 Backplane 到 Signal R App Server 到计算机的箭头的关系图。

SignalR 目前提供三个底板:

  • Azure 服务总线。 服务总线是一种消息传送基础结构,允许组件以松散耦合的方式发送消息。
  • Redis。 Redis 是内存中键值存储。 Redis 支持用于发送消息的发布/订阅 (“pub/sub”) 模式。
  • SQL Server。 SQL Server底板将消息写入 SQL 表。 底板使用 Service Broker 进行高效的消息传送。 但是,如果未启用 Service Broker,它也有效。

如果在 Azure 上部署应用程序,请考虑使用 Azure Redis 缓存的 Redis 底板。 如果要部署到自己的服务器场,请考虑SQL Server或 Redis 背板。

以下主题包含每个底板的分步教程:

实现

在 SignalR 中,每条消息都通过消息总线发送。 消息总线实现 IMessageBus 接口,该接口提供发布/订阅抽象。 底板的工作原理是将默认 的 IMessageBus 替换为为该底板设计的总线。 例如,Redis 的消息总线是 RedisMessageBus,它使用 Redis 发布/订阅 机制来发送和接收消息。

每个服务器实例通过总线连接到底板。 发送消息时,消息将转到底板,底板将消息发送到每个服务器。 当服务器从底板获取消息时,它会将消息放入其本地缓存中。 然后,服务器将消息从其本地缓存传递到客户端。

对于每个客户端连接,使用游标跟踪客户端读取消息流的进度。 (游标表示消息流中的位置。) 如果客户端断开连接,然后重新连接,它会要求总线提供客户端游标值之后到达的任何消息。 当连接使用 长时间轮询时,也会发生同样的情况。 长时间轮询请求完成后,客户端将打开一个新连接,并请求在游标之后到达的消息。

即使客户端在重新连接时路由到其他服务器,游标机制也有效。 底板可识别所有服务器,客户端连接到哪个服务器并不重要。

限制

使用底板时,最大消息吞吐量低于客户端直接与单个服务器节点通信时的最大消息吞吐量。 这是因为底板将每条消息转发到每个节点,因此底板可能会成为瓶颈。 此限制是否是问题取决于应用程序。 例如,下面是一些典型的 SignalR 方案:

  • 服务器广播 (例如,股票代码) :底板适用于此方案,因为服务器控制发送消息的速率。
  • 客户端到客户端 (例如聊天) :在此方案中,如果消息数随客户端数而增加,则底板可能是瓶颈;也就是说,如果消息速率随着更多客户端加入而成比例增长。
  • 高频实时 (例如实时游戏) :此方案不建议使用底板。

为 SignalR 横向扩展启用跟踪

若要为底板启用跟踪,请将以下部分添加到根 配置 元素下的 web.config 文件中:

<configuration>
  <system.diagnostics>
    <sources>
      <source name="SignalR.SqlMessageBus">
        <listeners>
          <add name="SignalR-Bus" />
        </listeners>
      </source>
      <source name="SignalR.ServiceBusMessageBus">
        <listeners>
          <add name="SignalR-Bus" />
        </listeners>
      </source>
      <source name="SignalR.ScaleoutMessageBus">
        <listeners>
          <add name="SignalR-Bus" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="SignalRSwitch" value="Verbose" />
      <!-- Off, Critical, Error, Warning, Information, Verbose -->
    </switches>
    <sharedListeners>
      <add name="SignalR-Bus" 
          type="System.Diagnostics.TextWriterTraceListener" 
          initializeData="bus.log.txt" />
    </sharedListeners>
    <trace autoflush="true" />
  </system.diagnostics>
  . . .
</configuration>