• Wcf通讯基础框架方案(二)——集中配置


    从这次开始在几个方面简单阐述一下实现,集中配置是这个框架很大的一个目的,首先在数据库中会有这么一些表:

    image

    其实可以看到这些表的结构,应该是和<system.serviceModel>配置节点中的层次有对应的

    1) Service表描述的是服务,主要保存服务行为以及服务的配置。在这里,ServiceConfig是框架内用到的配置,比如各种日志是否要记录等等。服务对应到服务的集群,集群公开一个地址,客户端访问这个地址,也就是一个负载均衡的虚拟IP地址。

    2) 一个Service可以有多个ServiceEndpoint,ServiceEndpoint中定义了契约的版本,类型和行为,以及涉及到地址的端口、端点名字等。客户端和服务端部署的服务契约版本号不一定是一致的,在选择端点的时候优先选择匹配的版本。

    3) 一个ServiceEndpoint对应一个Binding,一个Binding也可以对应多个ServiceEndpoint。Binding表中记录了绑定类型、优先级、以及协议前缀和绑定的Xml配置。在选择服务端点的时候会优先选择优先级别比较高的绑定。

    4) ClientAccess表主要用于限制哪些客户端机器可以访问哪些服务集群。

    5) ClientEndpoint表主要用于设置ClientEndpoint的行为Xml(和ServiceEndpoint行为Xml不能保持一致,对于绑定客户端和服务端是公用的)。

    上述这些逻辑可以体现在配置服务的两个方法实现中:

        public WcfService GetWcfService(string serviceType, string serviceContractVersion, string machineName)
            {
                using (WcfConfigDataContext data = new WcfConfigDataContext())
                {
                    var wcfService = (from service in data.Services
                                      where service.ServiceType == serviceType && (service.ServerMachineName == "*" || service.ServerMachineName == machineName)
                                      select new WcfService
                                      {
                                          ServiceType = serviceType,
                                          ServiceBehaviorXml = service.ServiceBehaviorXml.ToString(),
                                          Endpoints = (from ep in data.ServiceEndpoints
                                                       where ep.ServiceType == serviceType && ep.ServiceContractVersion == serviceContractVersion
                                                       select new WcfServiceEndpoint
                                                       {
                                                           EndpointBehaviorXml = ep.ServiceEndpointBehaviorXml.ToString(),
                                                           EndpointBindingName = ep.ServiceEndpointBindingName,
                                                           EndpointName = ep.ServiceEndpointName,
                                                           EndpointPort = ep.ServiceEndpointPort,
                                                           ServiceContractType = ep.ServiceContractType,
                                                           EndpointBindingType = ep.Binding.BindingType,
                                                           EndpointBindingXml = ep.Binding.BindingXml.ToString(),
                                                           EndpointProtocol = ep.Binding.BindingProtocol
                                                       }).ToArray()
                                      }).SingleOrDefault();
    
    
                    return wcfService;
                }
            }
    
            public WcfClientEndpoint GetWcfClientEndpoint(string serviceContractType, string serviceContractVersion, string machineName)
            {
                using (WcfConfigDataContext data = new WcfConfigDataContext())
                {
                    var wcfClientEndpoint = (from ep in (data.ServiceEndpoints
                                             .Where(s => s.ServiceContractType == serviceContractType).ToList())
                                             where
                                                 (ep.ServiceContractVersion == "" || float.Parse(ep.ServiceContractVersion) >= float.Parse(serviceContractVersion))
                                             orderby ep.ServiceContractVersion ascending, ep.Binding.BindingPriority descending
                                             select new WcfClientEndpoint
                                             {
                                                 EndpointName = ep.ServiceEndpointName,
                                                 EndpointPort = ep.ServiceEndpointPort,
                                                 ServiceContractType = ep.ServiceContractType,
                                                 EndpointBindingType = ep.Binding.BindingType,
                                                 EndpointBindingXml = ep.Binding.BindingXml.ToString(),
                                                 EndpointProtocol = ep.Binding.BindingProtocol,
                                                 EndpointAddress = ep.Service.ServerFarm.ServerFarmAddress,
                                                 ServerFarmName = ep.Service.ServerFarmName,
                                             }).FirstOrDefault();
    
                    var accessableFarmNames = data.ClientAccesses.Where(acc => acc.ClientMachineName == "*" || acc.ClientMachineName == machineName).Select(a => a.AccessServerFarmName).ToList();
                    if (!accessableFarmNames.Contains("*") && !accessableFarmNames.Contains(wcfClientEndpoint.ServerFarmName)) return null;
    
                    var query =
                        (from ce in data.ClientEndpoints
                         where ce.ServiceContractType == wcfClientEndpoint.ServiceContractType
                         select ce.ClientEndpointBehaviorXml).FirstOrDefault();
    
                    wcfClientEndpoint.EndpointBehaviorXml = query != null ? query.ToString() : "";
    
                    return wcfClientEndpoint;
                }
            }

    有了配置,那么客户端和服务端怎么应用上这些ABC呢?先来看看服务端:

    public class WcfServiceHostFactory : ServiceHostFactory
        {
            protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
            {
                return CreateServiceHost(serviceType);
            }
    
            public static ServiceHost CreateServiceHost<T>()
            {
                return CreateServiceHost(typeof(T));
            }
    
            public static ServiceHost CreateServiceHost(Type serviceType)
            {
                string typeName = serviceType.FullName;
                var serviceHost = new ServiceHost(serviceType);
                if (!typeof(IWcfConfigService).IsAssignableFrom(serviceType) && !typeof(IWcfLogService).IsAssignableFrom(serviceType))
                {
                    if (serviceHost.Description.Behaviors.Find<ServiceErrorBehavior>() == null)
                        serviceHost.Description.Behaviors.Add(new ServiceErrorBehavior());
                    if (serviceHost.Description.Behaviors.Find<ActionInterceptBehavior>() == null)
                        serviceHost.Description.Behaviors.Add(new ActionInterceptBehavior());
                    if (serviceHost.Description.Behaviors.Find<UnityServiceBehavior>() == null)
                        serviceHost.Description.Behaviors.Add(new UnityServiceBehavior());
    
                    serviceHost.Description.Endpoints.Clear();
                    var wcfService = GetWcfServiceConfiguration(serviceType);
                    if (wcfService == null)
                        throw new Exception("Can not locate any Wcf service, please check metadata config!");
    
                    var bindingCache = new Dictionary<string, Binding>();
    
                    foreach (var ep in wcfService.Endpoints)
                    {
                        string address = ConfigHelper.CreateAddress(
                            ep.EndpointProtocol,
                            Environment.MachineName,
                            ep.EndpointPort,
                            ep.EndpointName);
    
                        Binding binding;
                        if (!bindingCache.TryGetValue(address, out binding))
                        {
                            binding = ConfigHelper.CreateBinding(ep.EndpointBindingType, ep.EndpointBindingXml);
                            bindingCache[address] = binding;
                        }
    
                        serviceHost.AddServiceEndpoint(ep.ServiceContractType, binding, address);
    
                        if (!string.IsNullOrEmpty(ep.EndpointBehaviorXml))
                        {
                            AddEndPointBehavior(serviceHost.Description.Endpoints.Last(), ep.EndpointBehaviorXml);
                        }
                    }
    
                    foreach (var ep in serviceHost.Description.Endpoints)
                    {
                        ep.Behaviors.Add(new MessageInspectorEndpointBehavior());
                    }
    
                    if (!string.IsNullOrEmpty(wcfService.ServiceBehaviorXml))
                        AddServiceBehavior(serviceHost, wcfService.ServiceBehaviorXml);
    
                    WcfLogManager.InitLog(serviceType, LogType.Service);
    
                    serviceHost.Opened += (sender, o) =>
                    {
                        if (WcfLogManager.Current(serviceType).StartInfo.Server.Enabled)
                        {
                            var log = WcfLogProvider.GetServerStartInfo(serviceType.FullName, "WcfServiceHostFactory.CreateServiceHost", wcfService);
                            WcfServiceLocator.GetLogService().LogWithoutException(log);
                        }
    
                    };
                }
    
                return serviceHost;
            }
    
            private static void AddEndPointBehavior(ServiceEndpoint serviceEndpoint, string behavior)
            {
                var doc = new XmlDocument();
                doc.LoadXml(behavior);
                var endpointBehaviorElement = new EndpointBehaviorElement();
                ConfigHelper.Deserialize(doc.OuterXml, endpointBehaviorElement);
                foreach (var item in endpointBehaviorElement)
                {
                    ConfigHelper.SetBehavior(serviceEndpoint.Behaviors, item);
                }
            }
    
            private static void AddServiceBehavior(ServiceHost serviceHost, string behavior)
            {
                var doc = new XmlDocument();
                doc.LoadXml(behavior);
                var serviceBehaviorElement = new ServiceBehaviorElement();
                ConfigHelper.Deserialize(doc.OuterXml, serviceBehaviorElement);
                foreach (var item in serviceBehaviorElement)
                {
                    ConfigHelper.SetBehavior(serviceHost.Description.Behaviors, item);
                }
            }
    
            private static WcfService GetWcfServiceConfiguration(Type serviceType)
            {
                var version = serviceType.GetInterfaces().First().Assembly.GetName().Version;
                try
                {
                    using (var scf = WcfServiceClientFactory.CreateServiceClient<IWcfConfigService>())
                    {
                        return scf.Channel.GetWcfService(serviceType.FullName, version.Major + "." + version.Minor, Environment.MachineName);
                    }
                }
                catch (Exception ex)
                {
                    LocalLogService.Log(ex.ToString());
                    throw new Exception("Can not get service metadata, error message is " + ex.Message);
                }
            }
        }
    }

    其实没什么复杂的,无非是应用配置,但是这里注意到几点:

    1) 在我们自己的WcfServiceHostFactory里面,我们除了从配置服务读取配置,并且应用之外,还加入了框架本身需要的一些扩展的ServiceBehavior。如果以后有其它需要扩展的,客户端和服务端也只需要更新一下框架的dll即可。

    2) 此外,可以看到在服务启动的时候,根据开关,加上了StartInfo的日志,有关日志部分更详细的东西会在以后介绍。这也是框架的另外一个好处,内置大量的横切关注点的考虑。

    再来看看客户端的代码,其实和服务端差不多:

        private static ChannelFactory<T> CreateChannelFactory<T>(WcfClientEndpoint endpoint)
            {
                if (endpoint == null)
                    throw new Exception("Can not locate any endpoint, please check metadata config!");
    
                var binding = ConfigHelper.CreateBinding(endpoint.EndpointBindingType, endpoint.EndpointBindingXml);
                if (binding is NetNamedPipeBinding)
                    endpoint.EndpointAddress = "localhost";
                var address = ConfigHelper.CreateAddress(endpoint.EndpointProtocol, endpoint.EndpointAddress, endpoint.EndpointPort, endpoint.EndpointName);
                var factory = new ChannelFactory<T>(binding, address);
                factory.Endpoint.Behaviors.Add(new MessageInspectorEndpointBehavior());
                if (!string.IsNullOrEmpty(endpoint.EndpointBehaviorXml))
                    AddEndpointBehavior(factory, endpoint.EndpointBehaviorXml);
                return factory;
            }
    
            private static void AddEndpointBehavior(ChannelFactory factory, string behaviorXml)
            {
                var doc = new XmlDocument();
                doc.LoadXml(behaviorXml);
                var endpointBehaviorElement = new EndpointBehaviorElement();
                ConfigHelper.Deserialize(doc.OuterXml, endpointBehaviorElement);
                foreach (var item in endpointBehaviorElement)
                {
                    ConfigHelper.SetBehavior(factory.Endpoint.Behaviors, item);
                }
            }
    
            private static WcfClientEndpoint GetWcfClientEndpointConfiguration(Type serviceContractType)
            {
                var version = serviceContractType.Assembly.GetName().Version;
                var versionstring = version.Major + "." + version.Minor;
                try
                {
                    using (var scf = WcfServiceClientFactory.CreateServiceClient<IWcfConfigService>())
                    {
                        return scf.Channel.GetWcfClientEndpoint(serviceContractType.FullName, versionstring, WcfLogProvider.MachineName);
                    }
                }
                catch (Exception ex)
                {
                    LocalLogService.Log(ex.ToString());
                    throw new Exception("Can not get client metadata, error message is " + ex.Message);
                }
            }
    
            internal static WcfChannelWrapper<T> CreateServiceClient<T>() where T : class
            {
                string typeName = typeof(T).FullName;
                ChannelFactory cf;
    
                if (!channelFactoryCache.TryGetValue(typeName, out cf))
                {
                    lock (cacheLocker)
                    {
                        if (!channelFactoryCache.TryGetValue(typeName, out cf))
                        {
                            if (typeof(T) == typeof(IWcfConfigService))
                            {
                                var configServiceAddress = ConfigurationManager.AppSettings[WCFCONFIGSERVICE_ADDRESS_KEY];
                                if (string.IsNullOrEmpty(configServiceAddress))
                                    throw new Exception("Please set " + WCFCONFIGSERVICE_ADDRESS_KEY + "appSettings in your config!");
                                var binding = new NetTcpBinding();
                                binding.Security.Mode = SecurityMode.None;
                                var address = string.Format("net.tcp://{0}/WcfConfigService", configServiceAddress);
                                cf = new ChannelFactory<IWcfConfigService>(binding, address);
                            }
                            else if (typeof(T) == typeof(IWcfLogService))
                            {
                                var logServiceAddress = ConfigurationManager.AppSettings[WCFLOGSERVICE_ADDRESS_KEY];
                                if (string.IsNullOrEmpty(logServiceAddress))
                                    throw new Exception("Please set " + WCFLOGSERVICE_ADDRESS_KEY + "appSettings in your config!");
                                var elements = new List<BindingElement>();
                                elements.Add(new BinaryMessageEncodingBindingElement());
                                elements.Add(new TcpTransportBindingElement());
                                var binding = new CustomBinding(elements);
                                var address = string.Format("net.tcp://{0}/WcfLogService", logServiceAddress);
                                cf = new ChannelFactory<IWcfLogService>(binding, address);
                            }
                            else
                            {
                                if (redisSubThread == null)
                                {
                                    CreateRedisSubThread();
                                }
                                var endpoint = GetWcfClientEndpointConfiguration(typeof(T));
                             
                                cf = CreateChannelFactory<T>(endpoint);
    
                                WcfLogManager.InitLog<T>(LogType.Client);
    
                                if (WcfLogManager.Current<T>().StartInfo.Client.Enabled)
                                {
                                    var log = WcfLogProvider.GetClientStartInfo(typeof(T).FullName, "WcfServiceClientFactory.CreateServiceClient", endpoint);
                                    WcfServiceLocator.GetLogService().LogWithoutException(log);
                                }
                            }
    
                            channelFactoryCache[typeName] = cf;
                        }
                    }
                }
    
                return new WcfChannelWrapper<T>((cf as ChannelFactory<T>).CreateChannel());
            }

    在这里要解释几点:

    1) 客户端会根据契约的类型来缓存信道工厂,每一次使用了信道之后都会正确关闭,这个是通过WcfChannelWrapper保证的。更多有关客户端的内容会在下次介绍,本次只是介绍配置集中。

    2) 对于日志服务和配置服务的配置,没有定义在数据库中,而是写死的,只有地址是可以配置的配置文件中的。日志服务和配置服务不但是业务的客户端需要使用,业务的服务端也是需要使用的,所以不管是客户端还是服务端都需要配置两个基础服务的地址。虽然是写死在配置文件中的,但是一般生产环境会配置一个虚拟IP地址,把这2个服务做成负载均衡的服务,地址一般不会变动。

    3) 和服务端一样,也加上了框架本身需要的一些扩展,比如MessageInspectorEndpointBehavior。

    最后,配置集中的好处:

    1) 可以灵活修改各种服务的行为、端点的行为,统一管理地址。修改之后,可以通过发布订阅机制通知到客户端和服务端。

    2) 可以做一些版本控制的路由、权限控制、绑定优先级控制等等。

    3) 可以基于这些元数据做一些监控等。

  • 相关阅读:
    Delphi的几个跨平台小游戏例子。
    Delphi判断某个类是否实现了某个接口
    Delphi RAD Server 应用服务基础平台
    Delphi XE10.1 引用计数
    运行Delphi XE10的MongoDB例程,测试Delphi插入记录性能
    在Windows下编译mongo-c-driver 1.3.x
    Delphi 高效读写锁
    Delphi XE10在 Android下调用静态库a文件
    Delphi 调试连接 任意Android手机/平板/盒子
    Some cool FireMonkey multi-device components
  • 原文地址:https://www.cnblogs.com/lovecindywang/p/2031598.html
Copyright © 2020-2023  润新知