2.解決服務之間的通信

做者

微信:tangy8080
電子郵箱:914661180@qq.com
更新時間:2019-06-28 14:25:40 星期五node

歡迎您訂閱和分享個人訂閱號,訂閱號內會不按期分享一些我本身學習過程當中的編寫的文章
如您在閱讀過程當中發現文章錯誤,可添加個人微信 tangy8080 進行反饋.感謝您的支持。
git

文章主題

介紹多個服務之間進行通信算法

前置條件

[無]數據庫

正文

服務之間應該儘可能較少調用.以減小耦合度,若是彼此調用鏈過於頻繁.可能會引發整個調用鏈的異常.
https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/architect-microservice-container-applications/communication-in-microservice-architecture
The microservice community promotes the philosophy of "smart endpoints and dumb pipes" This slogan encourages a design that's as decoupled as possible between microservices, and as cohesive as possible within a single microservice. As explained earlier, each microservice owns its own data and its own domain logic. But the microservices composing an end-to-end application are usually simply choreographed by using REST communications rather than complex protocols such as WS-* and flexible event-driven communications instead of centralized business-process-orchestrators.
微信

但服務之間的調用有時候會變得"隨其天然",以上一章利用consul實現k8s服務自動發現
結尾的兩個服務爲例說明
app

  • configcenter 做爲整個集羣甚至對外(這個時候,咱們牽涉到一個概念:集中受權中興 若是不受權,那集羣外部訪問配置.豈不是變得很是危險了嗎?該問題咱們會在後面說起)的配置中心,(它以consul的KV爲存儲)提供統一的配置
  • terminal 做爲終端服務,它提供對集羣外部終端的集中管控,例如通知終端更新,向終端發送通知等等

terminal須要數據庫的配置信息(ip,port等)以存儲各個終端的數據,這些配置存放在configcenter中.那麼terminal服務如何從configcenter中取出配置呢? 這時候就涉及到了服務間的通信了框架

服務之間如何通信

凡是涉及到通信的,通常都會涉及到兩個概念asp.net

  • 通信協議 TCP,UDP,HTTP等?
  • 數據鍥約 JSON,XML又或者相似根據服務端協議自動生成客戶端代碼
通信協議

在微服務之間通信目前比較流行的有兩種 TCP和HTTPdom

  • 在TCP通信的基礎上有一些框架可供您選擇:Thrift,GRPC等
  • HTTP函數

    基於Consul和HTTP協議手擼一個微服務之間的庫

    這裏,我選擇本身手擼一個簡單的微服務之間的調用的庫,它的工做模式以下

在ConsulCaller中,咱們使用了Consul庫進行服務發現.當發現了服務實例時,程序會根據隨機算法選取實例.而後返回給調用方.

源碼地址

git://gitblit.honeysuckle.site/public/Honeysuckle.git

實現方式講解
  1. 擴展IServiceCollection,增長擴展函數AddConsulCaller.該函數經過調用方傳入Consul的配置信息來進行服務配置.
    IConsulConfiguration 中定義了Consul的配置參數,包括consul的地址,端口,Token 因爲簡單這裏不過多說明.
public static void AddConsulCaller(this IServiceCollection services, Action<ConsulCallerOptions> optionsAction)
        {
            var consulConfiguration=new ConsulConfiguration();
            services.AddSingleton<IConsulConfiguration>(consulConfiguration);
            services.AddSingleton<IServiceDiscover, ServiceDiscover>();

            services.AddSingleton<IConsulClientFactory, ConsulClientFactory.ConsulClientFactory>();
            services.AddTransient<IConsulCaller, ConsulCaller>();

            var consulCallerOptions = new ConsulCallerOptions(consulConfiguration);
            optionsAction.Invoke(consulCallerOptions);
        }
  1. 我定義了一個IConsulClientFactory接口,它包含一個Get函數.用於返回IConsulClient對象.該對象支持發現consul中的服務實例
public IConsulClient Get(IConsulConfiguration config)
        {
            return new ConsulClient(c =>
            {
                c.Address = new Uri($"http://{config.Host}:{config.Port}");

                if (!string.IsNullOrEmpty(config?.Token))
                {
                    c.Token = config.Token;
                }
            });
        }

3.另一個重要的接口是IServiceDiscover,它包含一個GetServices的函數,該函數根據服務名稱返回服務實例列表

private readonly IConsulClient _consul;
        private const string VersionPrefix = "version-";

        public ServiceDiscover(IConsulClientFactory consulClientFactory, IConsulConfiguration consulConfiguration)
        {
            _consul = consulClientFactory.Get(consulConfiguration);
        }

        public  List<Service> GetServices(string key)
        {
            var queryResult =  _consul.Health.Service(key, string.Empty, true).Result;
            
            var services = new List<Service>();
            foreach (var serviceEntry in queryResult.Response)
            {
                if (IsValid(serviceEntry))
                {
                    var nodes =  _consul.Catalog.Nodes().Result;
                    if (nodes.Response == null)
                    {
                        services.Add(BuildService(serviceEntry, null));
                    }
                    else
                    {
                        var serviceNode = nodes.Response.FirstOrDefault(n => n.Address == serviceEntry.Service.Address);
                        services.Add(BuildService(serviceEntry, serviceNode));
                    }
                }
                else
                {
                    Console.WriteLine($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
                }
            }
            return services;
        }


        private static Service BuildService(ServiceEntry serviceEntry, Node serviceNode)
        {
            return new Service(
                serviceEntry.Service.Service,
                new ServiceHostAndPort(serviceNode == null ? serviceEntry.Service.Address : serviceNode.Name, serviceEntry.Service.Port),
                serviceEntry.Service.ID,
                GetVersionFromStrings(serviceEntry.Service.Tags),
                serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
        }


        private static bool IsValid(ServiceEntry serviceEntry)
        {
            if (string.IsNullOrEmpty(serviceEntry.Service.Address) || serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
            {
                return false;
            }

            return true;
        }


        private static string GetVersionFromStrings(IEnumerable<string> strings)
        {
            return strings
                ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
                .TrimStart(VersionPrefix);
        }

4.最後咱們提供了一個IConsulCaller接口,它提供一個Call接口,輸入服務名稱和一個回調函數.

如何使用?

1.在asp.net core引用中添加了ConsulCaller服務

//使用ConsulCaller服務
            services.AddConsulCaller(options =>
            {
                options.ConsulConfiguration.Host = Configuration["Consul:Host"];
                options.ConsulConfiguration.Port = Convert.ToInt32(Configuration["Consul:Port"]);
            });

2.調用集羣中的其餘服務

const string serviceName = "configcenter";
            _consulCaller.Call(serviceName, (endpoint, httpclient) =>
            {
                try
                {
                    var uri = $"http://{endpoint.DownstreamHost}:{endpoint.DownstreamPort}/Consul/Get";
                   //根據返回的服務實例,實現本身的調用邏輯
                }
                catch (Exception ex)
                {
                    var errorMsg = $"{nameof(TerminalServerDbContext)}.{nameof(OnConfiguring)} _consulCaller.Call Error ";
                    Console.WriteLine(errorMsg + ex.Message);
                    Logger.Error(errorMsg, ex);
                }
                finally
                {
                    httpclient.Dispose();
                }
            });
  • IConsulCaller 對象在第一步已經被注入到IServiceCollection容器中

引用連接

[無]

相關文章
相關標籤/搜索