SignalR 1.x 的横向扩展简介

作者 :Patrick Fletcher

警告

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

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

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

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

服务器横向扩展时客户端面临的问题的屏幕截图是,由于服务器已连接到一台服务器,因此不会接收从另一台服务器发送的消息。

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

使用名为“底板”的组件在服务器之间转发消息的解决方案的屏幕截图。

SignalR 目前提供三个底板:

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

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

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

实现

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

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

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

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

限制

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

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

为 SignalR Scaleout 启用跟踪

若要为背板启用跟踪,请将以下部分添加到根 配置 元素下的 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>