Создание маршрутизатора WCF - Регистрация служб
ОГЛАВЛЕНИЕ
Регистрация служб
Вместо того, чтобы прописывать в коде конечные точки для служб приложений, маршрутизатор может предоставлять конечную точку службы для регистрации и отмены регистрации служб при их подключении к сети и переходе в автономный режим. В отсутствие программного или аппаратного балансировщика нагрузки это снижает дополнительную нагрузку на маршрутизатор в случае необходимости масштабирования служб приложения или при изменении имен машин или портов в соответствующих адресах конечных точек. Для поддержки этой модели следует выполнить следующие действия.
- Реализовать контракт регистрации службы для маршрутизатора и предоставить эту конечную точку служам приложения за брандмауэром.
- Поддерживать для маршрутизатора регистрационный список.
- После инициализации каждого ServiceHost обеспечить регистрацию им конечных точек служб посредством маршрутизатора.
- После сбоя или закрытия всех ServiceHost обеспечить посредством маршрутизатора отмену регистрации конечных точек служб.
Схема на рис. 2 демонстрирует процесс регистрации, в котором осуществляется добавление записей, обеспечивающих сопоставление пространства имен контракта физическому адресу конечной точки.
Рис. 2 Регистрация служб с использованием маршрутизатора
При этом подходе для регистрации требуется только пространство имен контракта и физический адрес всех конечных точек службы. На рис. 3 показан контракт службы IRegistrationService и соответствующие данные RegistrationInfo, передаваемые маршрутизатору для проведения регистрации и ее отмены.
Рис. 3 Контракт службы IRegistrationService с данными контракта
[ServiceContract(Namespace =
"http://www.thatindigogirl.com/samples/2008/01")]
public interface IRegistrationService {
[OperationContract]
void Register(RegistrationInfo regInfo);
[OperationContract]
void Unregister(RegistrationInfo regInfo);
}
[DataContract(Namespace =
"http://schemas.thatindigogirl.com/samples/2008/01")]
public class RegistrationInfo {
[DataMember(IsRequired = true, Order = 1)]
public string Address { get; set; }
[DataMember(IsRequired = true, Order = 2)]
public string ContractName { get; set; }
[DataMember(IsRequired = true, Order = 3)]
public string ContractNamespace { get; set; }
public override int GetHashCode() {
return this.Address.GetHashCode() +
this.ContractName.GetHashCode() +
this.ContractNamespace.GetHashCode();
}
}
Маршрутизатор может хранить по одной записи на контракт, но при этом не допускается более одной службы для каждого контракта. Для поддержки распределения по нескольким записям маршрутизатор должен использовать уникальный ключ для каждой регистрации. В следующем коде используется словарь, который каждой записи однозначно сопоставляет хэш-код экземпляра RegistrationInfo.
// registration list
static public IDictionary<int, RegistrationInfo>
RegistrationList =
new Dictionary<int, RegistrationInfo>();
// to register
if (!RouterService.RegistrationList.ContainsKey(
regInfo.GetHashCode())) {
RouterService.RegistrationList.Add(regInfo.GetHashCode(),
regInfo);
}
// to unregister
if (RouterService.RegistrationList.ContainsKey(
regInfo.GetHashCode())) {
RouterService.RegistrationList.Remove(
regInfo.GetHashCode());
}
Когда маршрутизатор получает сообщения, он должен собрать пространство имен контрактов и отыскать в словаре соответствующий подходящий элемент. Если таких элементов несколько, для перенаправления сообщения к соответствующей конечной точке службы должен использоваться критерий выбора (см. рис. 4).
Рис. 4 Сопоставление сообщений соответствующим конечным точкам
string contractNamespace =
requestMessage.Headers.Action.Substring(0,
requestMessage.Headers.Action.LastIndexOf("/"));
// get a list of all registered service entries for
// the specified contract
var results = from item in RouterService.RegistrationList
where item.Value.ContractNamespace.Contains(contractNamespace)
select item;
int index = 0;
// find the next address used ...
// create the channel
RegistrationInfo regInfo = results.ElementAt<KeyValuePair<int,
RegistrationInfo>>(index).Value;
Uri addressUri = new Uri(regInfo.Address);
Binding binding = ConfigurationUtility.GetRouterBinding (addressUri.Scheme);
EndpointAddress endpointAddress = new EndpointAddress(regInfo.Address);
ChannelFactory<IRouterService> factory = new
ChannelFactory<IRouterService>(binding, endpointAddress)
// forward message to the service ...
Помимо распределения по машинам потребностей служб в балансировке нагрузки, динамическая регистрация может пригодиться в тех ситуациях, когда несколько экземпляров службы могут размещаться на одной машине — для этого требуется несколько назначений портов, если они размещаются в службе Windows.
Чтобы обеспечить это, службы должны выбирать динамическое назначение порта для машины. Для служб TCP эту задачу можно выполнить, задавая в конфигурации конечной точки значение Unique для режима прослушивания URI.
<endpoint address="net.tcp://localhost:9000/ServiceA"
contract=" IServiceA" binding="netTcpBinding"
listenUriMode="Unique"/>
Однако, для именованных каналов и HTTP, это значение не обеспечивает выбор уникального порта. Вместо этого к адресу добавляется GUID.
net.tcp://localhost:64544/ServiceA
http://localhost:8000/ServiceA/66e9c367-b681-4e4f-8d12-80a631b7bc9b
net.pipe://localhost/ServiceA/6660c07e-c9f5-450b-8d40-693ad1a71c6e
Чтобы для конечных точек служб TCP и HTTP обеспечить уникальный порт, в коде можно инициализировать базовые адреса или явные адреса конечных точек.
Uri httpBase = new Uri(string.Format(
"http://localhost:{0}",
FindFreePort()));
Uri tcpBase = new Uri(string.Format(
"net.tcp://localhost:{0}",
FindFreePort()));
Uri netPipeBase = new Uri(string.Format(
"net.pipe://localhost/{0}",
Guid.NewGuid().ToString()));
ServiceHost host = new ServiceHost(typeof(ServiceA),
httpBase, tcpBase, netPipeBase);
На рис. 5 показано несколько служб, размещенных на одной машине, регистрирующихся на маршрутизаторе. Из этой схемы также видно, что для удаления одной точки сбоя маршрутизатора может потребоваться программный или физический балансировщик нагрузки, распределяющий регистрационные вызовы по экземплярам. Безусловно, это подразумевает хранение регистрационного списка в общей базе данных.
Рис. 5 Регистрация служб с динамическими портами посредством маршрутизатора с сбалансированной нагрузкой