Как отправлять сообщения Azure SignalR клиентам на всех экземплярах приложения Azure с несколькими экземплярами
Мы оцениваем, как отправлять сообщения подключенным клиентам через SignalR. Наше приложение опубликовано в Azure и имеет несколько экземпляров. Мы можем успешно передавать сообщения клиентам, подключенным к одному экземпляру, но не к другим экземплярам.
Изначально мы смотрели на ServiceBus, но мы (возможно, по ошибке) обнаружили, что AzureSignalR должен быть в основном служебной шиной, которая обрабатывает все бэкенд для нас.
Мы устанавливаем сигнал R в Startup.cs, например:
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 имеет конечную точку для службы Azure), которую она будет обрабатывать функциональность сервисной шины для нас. Мы не уверены, какие шаги предпринять, чтобы это функционировало правильно.
Благодарю.
3 ответа
Причиной этой проблемы является то, что я старательно пытался ограничить трафик сообщений. Я пытался отправлять сообщения только тем клиентам, которые просматривали одну и ту же запись. Однако, поскольку мои объекты были специфичны для экземпляра, он получал бы только идентификаторы соединения от объекта текущего экземпляра.
Дальнейшее тестирование (с использованием соответствия ARR) подтверждает, что при вызове Clients.All() все клиенты, включая клиентов в разных экземплярах, получают сообщение.
Итак, наша установка AzureSignalR представляется правильной.
Текущее решение POC - в настоящее время тестирование
-При регистрации клиента мы будем транслировать всем подключенным клиентам "Какое поле вы заблокировали для этого идентификатора?"
-Если клиент находится на другом идентификаторе, он будет игнорировать сообщение.
-Если у клиента нет заблокированных полей, он будет игнорировать сообщение.
-Если у клиента заблокировано поле, он ответит на сообщение необходимой информацией.
-AzureSignalR затем ретранслирует данные, необходимые для выполнения блокировки.
Это увеличивает количество сообщений, но не значительно. Но это решит несколько экземпляров, содержащих разные проблемы с ClientIds.
Просто мысль, но вы пробовали использовать SignalR Groups? https://docs.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.2
Вы можете попробовать создать группу для каждой комбинации IdRec
а также fieldName
а затем просто транслировать сообщения в группу. Это суть того, как я думаю, что ваш 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}";
}
Я не уверен, что это подойдет вам, но мое приложение использует Redis для этого. Если у меня есть 4 клиента, скажем, 1 и 3 связаны с моим первым экземпляром, 2 и 4 - с моим вторым экземпляром. Экземпляр 1 ничего не знает о 2/4, а Экземпляр 2 ничего не знает о 1/3. Если я подключен к экземпляру 1 и мне нужно взаимодействовать с клиентами 2/4, Redis отправит сообщение этим клиентам.