你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
Azure SignalR 服务中的复原能力和灾难恢复
复原能力和灾难恢复是联机系统的常见需求。 Azure SignalR 服务已提供 99.9% 的可用性,但它仍然是一个区域性的服务。 由于你的服务实例始终在一个区域中运行,因此在出现区域范围的服务中断时不会故障转移到另一个区域。
对于区域灾难恢复,建议采用以下两种方法:
- 启用异地复制(简便方法)。 此功能将自动处理区域故障转移。 启用后,将仅保留一个 Azure SignalR 实例,并且不会引入任何代码更改。 有关详细信息,请查看异地复制。
- 利用 Service SDK 中的多个终结点。 我们的服务 SDK 支持多个 SignalR 服务实例,并在其中一些实例不可用时自动切换到其他实例。 发生灾难时,可以使用此功能进行恢复,但仍需要自行设置正确的系统拓扑。 本文档将介绍此操作。
SignalR 服务的高可用性体系结构
若要确保 SignalR 服务的跨区域复原能力,需要在不同的区域中设置多个服务实例。 这样,当某个区域出现故障时,可将其他区域用作备用区域。 当应用服务器连接到多个服务实例时,有两个角色:主要角色和辅助角色。 主要实例负责接收联机流量,而辅助实例充当完全正常运行的回退实例。 在 SDK 实现中,协商仅返回主要终结点,因此在正常情况下客户端只连接到主要终结点。 但当主要实例出现故障时,协商会返回辅助终结点,因此客户端仍可建立连接。 主要实例和应用服务器通过正常的服务器连接进行连接,但辅助实例和应用服务器通过一种称作“弱连接”的特殊连接进行连接。 弱连接的一个不同特征是它无法接受客户端连接路由,因为辅助实例位于另一个区域中。 将客户端路由到另一个区域不是最佳选择(会增大延迟)。
一个服务实例在连接到多个应用服务器时可以有不同的角色。 跨区域方案的一种典型设置是使用两对或更多对 SignalR 服务实例和应用服务器。 在每一对中,应用服务器和 SignalR 服务位于同一区域,SignalR 服务作为主要角色连接到应用服务器。 在每对之间,应用服务器和 SignalR 服务也会建立连接,但是,在连接到另一区域中的服务器时,SignalR 将变成辅助角色。
使用此拓扑时,来自一台服务器的消息仍可传送到所有客户端,因为所有应用服务器和 SignalR 服务实例是互连的。 但是,客户端在连接后,会路由到同一区域中的应用服务器,以实现最佳网络延迟。
下图阐释了这种方案:
配置多个 SignalR 服务实例
应用服务器和 Azure Functions 都支持多个 SignalR 服务实例。
在每个区域中创建 SignalR 服务和应用服务器/Azure Functions 后,可将应用服务器/Azure Functions 配置为连接到所有 SignalR 服务实例。
通过配置
你应该已经知道如何通过环境变量/应用设置/web.cofig 在名为 Azure:SignalR:ConnectionString
的配置项中设置 SignalR 服务连接字符串。
如果有多个终结点,可在多个配置项中设置这些终结点,每个项采用以下格式:
Azure:SignalR:ConnectionString:<name>:<role>
在 ConnectionString 中,<name>
是终结点的名称,<role>
是其角色(主要或辅助角色)。
名称是可选的,但如果你想要进一步自定义多个终结点之间的路由行为,则名称非常有用。
通过代码
如果你偏向于将连接字符串存储到其他位置,则也可以在代码中读取连接字符串,并在调用 AddAzureSignalR()
(在 ASP.NET Core 中)或 MapAzureSignalR()
(在 ASP.NET 中)时将其用作参数。
下面是示例代码:
ASP.NET Core:
services.AddSignalR()
.AddAzureSignalR(options => options.Endpoints = new ServiceEndpoint[]
{
new ServiceEndpoint("<connection_string1>", EndpointType.Primary, "region1"),
new ServiceEndpoint("<connection_string2>", EndpointType.Secondary, "region2"),
});
ASP.NET:
app.MapAzureSignalR(GetType().FullName, hub, options => options.Endpoints = new ServiceEndpoint[]
{
new ServiceEndpoint("<connection_string1>", EndpointType.Primary, "region1"),
new ServiceEndpoint("<connection_string2>", EndpointType.Secondary, "region2"),
};
可以配置多个主要或次要实例。 如果有多个主要实例和/或次要实例,则协商会按以下顺序返回终结点:
- 如果有至少一个主要实例处于联机状态,则会返回一个随机的联机主要实例。
- 如果所有主要实例都停机,则会返回一个随机的联机次要实例。
对于 Azure Functions SignalR 绑定
要启用多个 SignalR 服务实例,应:
使用
Persistent
传输类型。默认传输类型为
Transient
模式。 应将以下条目添加到local.settings.json
文件或 Azure 上的应用程序设置。{ "AzureSignalRServiceTransportType":"Persistent" }
注意
从
Transient
模式切换到Persistent
模式后,可能会发生 JSON 序列化行为更改,因为在Transient
模式下,Newtonsoft.Json
库用于序列化中心方法的参数,但在模式Persistent
下,System.Text.Json
库将用作默认值。System.Text.Json
在默认行为方面与Newtonsoft.Json
存在一些关键差异。 如果要在Persistent
模式下使用Newtonsoft.Json
,可以在local.settings.json
文件或 Azure 门户上的Azure__SignalR__HubProtocol=NewtonsoftJson
中添加配置项"Azure:SignalR:HubProtocol":"NewtonsoftJson"
。在配置中配置多个 SignalR 服务终结点条目。
我们使用
ServiceEndpoint
对象来表示 SignalR 服务实例。 可以使用服务终结点在条目键中的<EndpointName>
和<EndpointType>
以及条目值中的连接字符串来定义服务终结点。 键采用以下格式:Azure:SignalR:Endpoints:<EndpointName>:<EndpointType>
<EndpointType>
是可选的,默认值为primary
。 请参阅以下示例:{ "Azure:SignalR:Endpoints:EastUs":"<ConnectionString>", "Azure:SignalR:Endpoints:EastUs2:Secondary":"<ConnectionString>", "Azure:SignalR:Endpoints:WestUs:Primary":"<ConnectionString>" }
对于 [管理 SDK]
通过配置添加多个终结点
使用 SignalR 服务连接字符串的键 Azure:SignalR:Endpoints
进行配置。 该键应采用格式 Azure:SignalR:Endpoints:{Name}:{EndpointType}
,其中 Name
和 EndpointType
是 ServiceEndpoint
对象的属性,并且可以从代码访问。
可以使用以下 dotnet
命令添加多个实例连接字符串:
dotnet user-secrets set Azure:SignalR:Endpoints:east-region-a <ConnectionString1>
dotnet user-secrets set Azure:SignalR:Endpoints:east-region-b:primary <ConnectionString2>
dotnet user-secrets set Azure:SignalR:Endpoints:backup:secondary <ConnectionString3>
通过代码添加多个终结点
ServiceEndpoint
类描述 Azure SignalR 服务终结点的属性。
使用 Azure SignalR 管理 SDK 时,可通过以下方式配置多个实例终结点:
var serviceManager = new ServiceManagerBuilder()
.WithOptions(option =>
{
options.Endpoints = new ServiceEndpoint[]
{
// Note: this is just a demonstration of how to set options.Endpoints
// Having ConnectionStrings explicitly set inside the code is not encouraged
// You can fetch it from a safe place such as Azure KeyVault
new ServiceEndpoint("<ConnectionString0>"),
new ServiceEndpoint("<ConnectionString1>", type: EndpointType.Primary, name: "east-region-a"),
new ServiceEndpoint("<ConnectionString2>", type: EndpointType.Primary, name: "east-region-b"),
new ServiceEndpoint("<ConnectionString3>", type: EndpointType.Secondary, name: "backup"),
};
})
.BuildServiceManager();
故障转移序列和最佳做法
现已设置正确的系统拓扑。 每当某个 SignalR 服务实例出现故障时,联机流量都会路由到其他实例。 下面是当主要实例出现故障时(以及在一段时间后进行恢复时)发生的情况:
- 主要服务实例出现故障,此实例上的所有服务器连接都会断开。
- 连接到此实例的所有服务器都会将此实例标记为脱机,协商会停止返回此终结点,开始返回辅助终结点。
- 此实例上的所有客户端连接也会关闭,然后客户端会重新进行连接。 由于应用服务器现在返回辅助终结点,因此客户端会连接到辅助实例。
- 现在,辅助实例将接收所有联机流量。 由于辅助实例已连接到所有应用服务器,因此从服务器发往客户端的所有消息仍可传送。 但是,从客户端发往服务器的消息只能路由到同一区域中的应用服务器。
- 主要实例恢复并重新联机后,应用服务器将与它重新建立连接,并将其标记为联机。 协商现在会再次返回主要终结点,因此,新客户端会重新连接到主要实例。 但现有客户端不会掉线,在断开自身连接之前仍会路由到辅助客户端。
下图演示了 SignalR 服务中如何实现故障转移:
图 1 故障转移之前
图 2:故障转移之后
图 3 主实例恢复后的短时间内
可以看到,在正常情况下,只有主要应用服务器和 SignalR 服务包含联机流量(以蓝色表示)。 故障转移后,辅助应用服务器和 SignalR 服务也处于活动状态。 主要 SignalR 服务重新联机后,新客户端将连接到主要 SignalR。 但是,现有客户端仍连接到辅助实例,因此这两个实例都包含流量。 所有现有客户端断开连接后,系统将会恢复正常(图 1)。
可以使用两种主要模式来实现跨区域的高可用性体系结构:
- 第一种模式是使用一对应用服务器和 SignalR 服务实例来接收所有联机流量,并使用另一对作为备用实例(称为主动/被动配置,如图 1 所示)。
- 另一种模式是使用两对(或更多对)应用服务器和 SignalR 服务实例,其中每个实例接收一部分联机流量,并充当其他对的备用实例(称为主动/主动配置,类似于图 3)。
SignalR 服务支持这两种模式,主要差别在于实现应用服务器的方式。 如果应用服务器采用主动/被动配置,则 SignalR 服务也采用主动/被动配置(因为主要应用服务器仅返回其主要 SignalR 服务实例)。 如果应用服务器采用主动/主动配置,则 SignalR 服务也采用主动/主动配置(因为所有应用服务器都将返回其自己的主要 SignalR 实例,因此它们都可以获得流量)。
请注意,无论选择使用什么模式,都需要将每个 SignalR 服务实例作为主要实例连接到应用服务器。
另外,由于 SignalR 连接的性质(远距离连接),发生灾难和故障转移时,客户端会遇到连接断开的情况。 需要在客户端上处理此类情况,使其对最终客户透明。 例如,关闭连接后不要重新连接。
如何测试故障转移
按照以下步骤触发故障转移:
- 在门户中主要资源的“网络”选项卡中,禁用公用网络访问。 如果资源已启用专用网络,请使用访问控制规则来拒绝所有流量。
- 重启主要资源。
后续步骤
在本文中,你已了解如何配置应用程序以实现 SignalR 服务的复原能力。 若要更详细地了解 SignalR 服务中的服务器/客户端连接和连接路由,请阅读此文,其中介绍了 SignalR 服务的内部情况。
对于使用多个实例一起处理大量连接的缩放方案(例如分片),请阅读如何缩放多个实例。
有关如何使用多个 SignalR 服务实例配置 Azure Functions 的详细信息,请阅读 Azure Functions 中的多个 Azure SignalR 服务实例支持。