如何在多实例Azure应用程序的所有实例上向客户端发送Azure SignalR消息

时间:2018-12-04 19:39:34

标签: azure signalr multiple-instances azure-signalr

我们正在评估如何通过SignalR向连接的客户端发送消息。我们的应用程序在Azure中发布,并且具有多个实例。我们能够成功地将消息传递给连接到同一实例的客户端,而不是其他实例。

我们最初是在查看ServiceBus,但我们(可能是错误地)发现AzureSignalR应该基本上是一种服务总线,可以为我们处理所有后端事务。

我们在Startup.cs中设置signalR,例如:

public void ConfigureServices(IServiceCollection services)
{
    var signalRConnString = Configuration.GetConnectionString("AxiomSignalRPrimaryEndPoint");
    services.AddSignalR()
    .AddAzureSignalR(signalRConnString)
    .AddJsonProtocol(options =>
   {
       options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
   });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
 app.UseAzureSignalR(routes =>
 {
    routes.MapHub<CallRegistrationHub>("/callRegistrationHub");
    routes.MapHub<CaseHeaderHub>("/caseHeaderHub");
    routes.MapHub<EmployeesHub>("/employeesHub");
 });
}

问题
我们必须存储一些可能应该在服务总线上的对象,而不是存储在单独的实例中。但是,我不确定如何告诉集线器对象应该在总线上,而不是集线器特定实例的内部对象,如下所示:

public class EmployeesHub : Hub
{
    private static volatile List<Tuple<string, string, string,string, int>> UpdateList = new List<Tuple<string, string, string,string,int>>();
    private static volatile List<Tuple<string, int>> ConnectedClients = new List<Tuple<string, int>>();
}

我们具有一些功能,需要将消息发送到正在查看当前记录的所有已连接客户端,而不管它们位于什么实例中:

public async void LockField(string fieldName, string value, string userName, int IdRec)
{
    var clients = ConnectedClients.Where(x => x.Item1 != Context.ConnectionId && x.Item2 == IdRec).Select(x => x.Item1).Distinct().ToList();
    clients.ForEach(async x =>
    {
        await Clients.Client(x).SendAsync("LockField", fieldName, value, userName, true);
    });
    if (!UpdateList.Any(x=> x.Item1 == Context.ConnectionId && x.Item3 == fieldName && x.Item5 == IdRec))
    {               
        UpdateList.Add(new Tuple<string, string, string,string,int>(Context.ConnectionId,userName, fieldName, value, IdRec));
    }
}

这不适用于不同的实例(这是有道理的,因为每个实例将有其自己的对象。。但是,我们希望通过使用AzureSignalR而不是SignalR(AzureSignalR conn字符串具有Azure服务的终结点)来实现这一点它将为我们处理服务总线功能。我们不确定要采取什么步骤才能使该功能正常运行。

谢谢。

3 个答案:

答案 0 :(得分:0)

我不确定这是否适合您,但是我的应用程序为此使用Redis。如果我有4个客户端,则假设1和3连接到我的第一个实例,2和4连接到我的第二个实例。实例1对2/4一无所知,而实例2对1/3一无所知。如果我已连接到实例1,并且需要与客户端2/4进行交互,则Redis会将消息广播到这些客户端。

https://stackexchange.github.io/StackExchange.Redis/

答案 1 :(得分:0)

此问题的原因是我正在抢先尝试限制邮件流量。我试图只将邮件发送给正在查看同一记录的客户端。但是,由于我的对象是特定于实例的,因此它只会从当前实例的对象中获取连接ID。

进一步的测试(使用ARR关联性)证明,在Clients.All()调用中,所有客户端(包括不同实例中的客户端)都收到了消息。

因此,我们的AzureSignalR设置似乎正确。

当前POC解决方案-当前正在测试
-当客户注册时,我们将向所有连接的客户广播“您为此ID锁定了哪个字段?”
  -如果客户端使用不同的ID,则它将忽略该消息。
  -如果客户端没有锁定任何字段,它将忽略该消息。
-如果客户端的字段已锁定,则会使用所需的信息来响应该消息。
-AzureSignalR随后将重新广播执行锁定所需的数据。

这会增加消息数,但不会显着增加。但是它将解决多个实例,这些实例持有不同的已连接ClientIds问题。

答案 2 :(得分:0)

只是一个想法,但是您是否尝试过使用SignalR Groups? https://docs.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.2#groups-in-signalr

您可以尝试为IdRecfieldName的每种组合创建一个组,然后将消息广播到该组。这是我认为您的LockField函数外观的要点:

public async void LockField(string fieldName, string value, string userName, int IdRec)
{
    string groupName = GetGroupName(IdRec, fieldName);
    await Clients.Group(groupName).SendAsync("LockField", fieldName, value, userName, true);
    await this.Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}

您可以随意实现GetGroupName方法,只要它产生唯一的字符串即可。一个简单的解决方案可能是

public string GetGroupName(int IdRec, string fieldName)
{
    return $"{IdRec} - {fieldName}";
}