<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      .Net微服務實戰之負載均衡(下)

      系列文章

       相關源碼:https://github.com/SkyChenSky/Sikiro

      前言

        2020.1.10,陪我老婆到她所屬的千億企業的科技部值班,順便參觀了一下他們IT部門,舒適的環境讓我靈感大發,終于把這篇拖了半年的博文完成了。

        上一篇文章《.Net微服務實戰之負載均衡(上)》從DNS、LVS和Nginx講解如何在實戰中結合使用,那么以上三種負載方式離開發人員相對來說比較遠,平常也不容易接觸到,更多是由團隊的運維或者技術Leader關注的比較多。

        該篇主要講解在微服務架構中,如何使用我們耳熟能詳的API網關+服務注冊中心進行負載均衡的請求。讓大家在實際工作中知道,如何將拆分后的微服務應用銜接起來,如何在微服務應用之間跨主機的訪問容器進行請求。

        下文的中間件的部署與使用,我將以下面的網絡拓撲圖的形式大家進行演示。在實際開發項目中,是以Docker Overlay的網絡方式部署的,有些中間件為了開放給開發人員使用并且在文章中很好的展示給各位讀者,我是把容器端口映射到了宿主,大家可以根據自生的實際情況進行定義。該文雖然是說.Net的微服務,但是實際上這幾個中間件可以使用到其他各種平臺,也是比較開源界相對熱門、穩定的。

        其次我也把在日常和同行溝通的時候,討論得最多的問題給整理了出來,也方便入門微服務的讀者能解答心中的疑惑,只有基礎、理論理解清楚了,才能很好的進行實施。

      我被問得多的那些問題

      服務之間的調用關系容易混亂,該怎么劃分?

        下圖的架構分層圖是我當時實施后的應用分層,在這張圖有幾個關鍵點我給大家列一下:

      •   自頂向下的分為UI層、聚合API層、公共API層,
      •   每一層只會依賴于下一層,不會跨層依賴,也不會同一層之間存在調用依賴
      •   聚合API層與公共API層都是屬于內網環境,無法被外網直接訪問,聚合API層如果需要被UI層調用或者外網訪問則由API網關暴露出去,公共API層需要被聚合API層訪問則由RPC、Consul與Fabio進行銜接請求。
      •        縱向維度的從物理視角出發,分層目的是為了職責分離,避免調用關系亂套,循環依賴的問題
      •   水平維度的從業務視角出發,分解目的是為了解耦與復用

      在微服務架構里前后端分離后不知道如何調用API接口?

        該問題其實跟微服務無關,也就是前后端分離的基本問題。提出的人應該是屬于做單體系統多了,然后去了解微服務的時候發現概念多中間件多,什么API網關、服務注冊中心、RPC的直接把他們搞暈了。

        對于該問題的回答就是,客戶端與API之間是使用HTTP協議進行交互的,甚至是微服務內的服務與服務之間都是以HTTP協議進行交互,因為馬丁福勒在他的博客里說了個重要的單詞【輕量】,該詞就是指輕量級的通信協議也就是HTTP。

        那么對于該問題的一個衍生問題就是,我怎么知道該接口怎么調用呢?答案就是Swagger,Swagger擔任的服務描述的重任,他描述了,接口路徑、協議類型、參數結構,只要有了以上3者是不是就很好讓前后端人員對接了。

        清楚上面的問題后,再引入API網關,API網關其實就是把原本零散的API服務給整合起來,形成統一的流量入口,由API網關進行路由轉發,如下圖:

      微服務里服務與服務之間是怎么通信的?

        首先協議跟上面的問題一致是HTTP的,那么在.Net里HTTP API是不是可以通過HttpClient進行請求?但是HttpClient調用API時是需要關注很多參數的細節,那么RPC的優勢就來了。

        RPC主要工作是像調用內部方法一樣做遠程調用和隱藏請求細節。在.Net里WebApiClient和gRPC都是不錯的RPC框架。

        此外,RPC框架也是我認為做微服務第一個考慮選型并且慎重選型的組件。

      從服務注冊中心拿到的是服務地址列表,該怎么做負載均衡請求?

        我們從服務注冊中心拿到某個服務信息是一組ip+port的集合,那么需要對該集合的某一項進行請求。

        有兩種解決方式:

      •   調用端RPC集成,從注冊中心獲取服務地址列表,然后使用負載均衡算法選擇其中一個IP+Port讓RPC進行請求
      •   使用中間件,該中間件是與注冊中心集成的,例如Consul+Fabio,調用端會通過RPC框架請求Fabio,Fabio會從Consul獲取健康的地址請求轉發。

        下面的使用我主要以中間件的方式來解決上述的問題,主要.Net多數RPC是沒有集成注冊中心,如果由開發人員整合起來,改動相對會花精力與時間。

      PS:上面的提到的API網關、Fabio請求轉發如果把大家繞暈了話,你們可以把他們兩個當成類似Nginx功能(不完全一樣)的中間件。

        那么經過上面問題講述后,那么就可以開始接下來的Kong、Consul、Fabio與.Net Core的集成使用。

      Docker環境的準備

      所有服務器關閉防火墻,不然下面使用Overlay2后,容器之間也無法ping通,如果原本已經啟動了防火墻后再關閉的后需要重啟docker。

      #關閉防火墻
      systemctl disable firewalld
      
      #重啟docker
      systemctl restart docker

      在Server A初始化Docker Swarm

      docker swarm init --advertise-addr 192.168.88.138

      然后在其他worker節點Server B和Server C執行上面反饋的指令加入Docker Swarm集群

      docker swarm join --token SWMTKN-1-0odogegq3bwui4o76aq5v05doqqvuycb5jmuckjmvzy4bfmm59-ewht2cz6fo0r39ky44uv00aq5 192.168.88.138:2377

      在Server A上可以查看Docker Swarm節點信息

      docker node ls

      在Server A創建Overlay2網絡覆蓋,方便后續創建的容器之間可以跨主機訪問

      docker network create -d overlay --attachable overlay

       測試容器之間是否可以跨主機訪問

      #創建nginx集群
      docker service create -d --network=overlay --replicas 3  --name=nginx nginx
      
      #查找出某個實例的Ip
      docker inspect 1af2984adda9
      
      #進入另外容器實例嘗試請求跨主機請求
      docker exec -it 1af2984adda9 /bin/bash
      
      curl 10.0.1.8

      有以下響應結果就是網絡環境OK了。

      Kong與KongA的部署

      對于中間件的部署,我建議在docker run的指令里指定【--ip】,避免每次啟動的時候IP不一致,因此在應用配置需要指定。

      安裝postgres數據庫

      docker run -d --name kong-database  --network=overlay  -p 5432:5432  -e  "POSTGRES_USER=kong" -e "POSTGRES_PASSWORD=kong"  -e  "POSTGRES_DB=kong"  postgres:9.6

      初始化kong數據庫

      docker run --rm    -e "KONG_DATABASE=postgres"  -e  "KONG_PG_HOST=192.168.88.144"  -e "KONG_PG_USER=kong"  -e  "KONG_PG_PASSWORD=kong"  -e  "KONG_CASSANDRA_CONTACT_POINTS=postgres"  kong:2.2 kong migrations  bootstrap

      啟動kong應用

      docker run -d --ip=10.0.1.111 --name kong --network=overlay -e "KONG_DATABASE=postgres" -e "KONG_PG_HOST=192.168.88.144" -e "KONG_PG_USER=kong" -e "KONG_PG_PASSWORD=kong" -e "KONG_CASSANDRA_CONTACT_POINTS=postgres" -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" -e "KONG_PROXY_ERROR_LOG=/dev/stderr" -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" -p 8000:8000 -p 8443:8443 -p 8001:8001 -p 8444:8444 kong:2.2

      請求看看kong是否部署成功

      curl -i http://192.168.88.144:8001/services

      安裝konga

      docker run  -d --ip=10.0.1.112 -p 1337:1337 --network=overlay --restart=always -e "TOKEN_SECRET=chengong1218" -e "DB_ADAPTER=postgres" -e "DB_HOST=192.168.88.144" -e "DB_USER=kong" -e "DB_PASSWORD=kong" -e "DB_DATABASE=kong" -e "NODE_ENV=development" --name konga pantsel/konga:0.14.9

      KongA的使用

      初始化配置與儀表盤

      Kong的一些基本概念

        Service。顧名思義,就是我們自己定義的上游服務,通過Kong匹配到相應的請求要轉發的地方, Service 可以與下面的Route進行關聯,一個Service可以有很多Route,匹配到的Route就會轉發到Service中。Service服務,通過Kong匹配到相應的請求要轉發的地方(eg: 理解nginx 配置文件中server),等同于下面nginx的配置:

      http {
          server {
              listen 80;
              location / {
                  proxy_pass http://msg.upstream;
              }
          }
      }

        Route。實體定義匹配客戶端請求的規則. 每個路由都與一個服務相關聯,而服務可能有多個與之相關聯的路由. 每一個匹配給定路線的請求都將被提交給它的相關服務。Route 路由相當于nginx 配置中的location

      http {
          server {
              listen 80;
              server_name api.service.com;
              location /test{
                  proxy_pass https://msg.upstream;
              }
          }
      }

        Upstream。用來配置轉發真實地址的集合,類似于Nginx的Upstream模塊。

      upstream upstream.api {
          server www.jd.com:443 weight=100;
          server www.baidu.com443 weight=100;
      }

      添加Service

        把圈起來的4項填寫好,在實際場景可以根據自己的技術情況填寫Protocol=http,Port=80,下面我將有taobao.com和baidu.com所以暫時用https和443.

      添加Route

      把Route模塊的Name、Path和Methods填寫好,在這里需要注意的是Path和Methods每填寫一項得回車一次,不然保存后是沒有效果的。

      添加Upstream

      Kong的Upstream設置要添加Target,注意Upstream Detail的Name需要與Service Detail配置的Host一致。

      效果圖

      Kong.Net組件的使用

        經過上面的操作后,使用是沒有多大問題的了,但是應用基于Docker啟動后容器IP也是不固定的,那么手動添加的場景肯定不方便,不靈活。國人開源了一款Kong.Net-https://github.com/lianggx/Kong.Net,讓微服務應用在啟動后把他本身的信息注冊到Kong,這樣Kong也不需要與Consul做整合,可以理解成微服務應用通過Kong.Net把IP+Port注冊到了kong里。

      / This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
      public void Configure(IApplicationBuilder app, IHostingEnvironment env, KongClient kongClient)
      {
          UseKong(app, kongClient);
      
          if (env.IsDevelopment())
          {
              app.UseDeveloperExceptionPage();
          }
      
          app.UseMvc();
      }
      
      public void UseKong(IApplicationBuilder app, KongClient kongClient)
      {
          var upStream = Configuration.GetSection("kong:upstream").Get<UpStream>();
          var target = Configuration.GetSection("kong:target").Get<TargetInfo>();
          var uri = new Uri(Configuration["server.urls"]);
          // This target is your host:port
          target.Target = uri.Authority;
          app.UseKong(kongClient, upStream, target);
      }

       

      Consul的部署

      在Server C執行以下指令

      #Server模式
      docker run -d --ip=10.0.1.101 --net=overlay -p 8500:8500  -p 8600:8600/udp --name=consul-server-c consul agent -server -ui -node=consul-server-c -bootstrap-expect=1 -advertise=10.0.1.101 -bind=10.0.1.101 -client=0.0.0.0
      
      #Client模式
      docker run -d --ip=10.0.1.102 --net=overlay --name=consul-client-c consul agent -node=consul-client-c -advertise=10.0.1.102 -bind=10.0.1.102 -client=0.0.0.0 -join=10.0.1.101

      在Server A執行下面指令

      #Server模式
      docker run -d --ip=10.0.1.103 --net=overlay -p 8500:8500  -p 8600:8600/udp --name=consul-server-a consul agent -server -ui -node=consul-server-a -bootstrap-expect=1 -advertise=10.0.1.103 -bind=10.0.1.103 -client=0.0.0.0 -join=10.0.1.101
      
      #Client模式
      docker run -d --ip=10.0.1.104 --net=overlay --name=consul-client-a consul agent -node=consul-client-a -advertise=10.0.1.104 -bind=10.0.1.104 -client=0.0.0.0 -join=10.0.1.101

      在Server B執行以下指令

      #Server模式
      docker run -d --ip=10.0.1.105 --net=overlay -p 8500:8500  -p 8600:8600/udp --name=consul-server-b consul agent -server -ui -node=consul-server-b -bootstrap-expect=1 -advertise=10.0.1.105 -bind=10.0.1.105 -client=0.0.0.0 -join=10.0.1.101
      
      #Client模式
      docker run -d --ip=10.0.1.106 --net=overlay --name=consul-client-b consul agent -node=consul-client-b -advertise=10.0.1.106 -bind=10.0.1.106 -client=0.0.0.0 -join=10.0.1.101

       

      服務注冊

      .Net Core應用注冊到Consul,需要注意的是得在應用啟動后把服務注冊到Consul(lifetime.ApplicationStarted),不然是無法拿到微服務應用在Overlay2的地址,微服務默認是用HTTP因為是內網應用,所以不需要HTTPS,端口也是默認80,因為Docker會給每個容器分配一個獨立的IP。此外Tags是Fabio約定的格式,主要讓Fabio路由用的。

            /// <summary>
              /// Consul服務注冊
              /// </summary>
              /// <param name="app"></param>
              /// <param name="lifetime"></param>
              /// <param name="configuration"></param>
              /// <returns></returns>
              public static IApplicationBuilder UseConsul(this IApplicationBuilder app, IHostApplicationLifetime lifetime, IConfiguration configuration)
              {
                  var option = configuration.GetSection("Consul").Get<ConsulOption>();
                  option.ThrowIfNull();
      
                  //創建Consul客戶端
                  var consulClient = new ConsulClient(x => x.Address = new Uri(option.ConsulHost));//請求注冊的 Consul 地址
      
                  AgentServiceRegistration registration = null;
                  lifetime.ApplicationStarted.Register(() =>
                  {
                      var selfHost = new Uri("http://" + LocalIpAddress + ":" + option.SelfPort);
                      //注冊服務
                      registration = new AgentServiceRegistration
                      {
                          Checks = new[] { new AgentServiceCheck
                          {
                              Interval = TimeSpan.FromSeconds(option.HealthCheckInterval),
                              HTTP = $"{selfHost.OriginalString}/health",//健康檢查地址
                              Timeout = TimeSpan.FromSeconds(3)
                          } },
                          ID = selfHost.OriginalString.EncodeMd5String(),
                          Name = option.ServiceName,
                          Address = selfHost.Host,
                          Port = selfHost.Port,
                          Tags = new[] { $"urlprefix-/{option.ServiceName} strip=/{option.ServiceName}" }//添加 urlprefix-/servicename 格式的 tag 標簽,以便 Fabio 識別  
                      };
                      consulClient.Agent.ServiceRegister(registration).Wait();
                  });
      
                  //反注冊服務
                  lifetime.ApplicationStopping.Register(() =>
                  {
                      if (registration != null)
                          consulClient.Agent.ServiceDeregister(registration.ID).Wait();
                  });
                  return app;
              }

      .Net微服務配置

      {
        "Logging": {
          "LogLevel": {
            "Default": "Warning",
            "Microsoft": "Information"
          }
        },
        "AllowedHosts": "*",
        "redisUrl": "",
        "MongoDbUrl": "",
        "Consul": {
          "ServiceName": "Msg",
          "ConsulHost": "http://10.0.1.101:8500",
          "SelfPort": 80
        },
        "IsDebug": false
      }

      Docker構建.Net Core微服務

      Docker File

      FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
      WORKDIR /app
      EXPOSE 80
      
      FROM base AS final
      WORKDIR /app
      COPY ./ /app
      ENV TZ=Asia/Shanghai
      RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

      Docker構建指令

      docker build -t msgserver .
      docker run -d -p 8801:80   --network=overlay  --name msgserver  msgserver

      Fabio的部署

      registry_consul_addr為Consul的Overlay2的IP,可以通過docker inspect指令進行查看。

      docker run -d --net=overlay -p 443:443 -p 9998:9998 -p 9999:9999 --name=fabio -e 'registry_consul_addr=10.0.1.101:8500' magiconair/fabio

      部署成功后,可以通過fabio_ip+9998端口查看服務注冊的情況。

      然后可以fabio_ip+9999進行請求轉發,下面GIF效果圖

      RPC集成使用

      在該篇文章,我主要使用了中間件代理的方式處理了微服務內部的負載均衡請求,那么在RPC的層面基本上就不需要花多余的功夫進行集成與擴展。

      下面以WebApiClient作為例子展示如何做微服務調用(按需可以使用gRPC,思路與實現方式差不多)

      調用端

      注冊到IOC

      /// <summary>
              /// 注冊消息服務內部api
              /// </summary>
              /// <param name="services"></param>
              /// <param name="configuration"></param>
              private static void AddMsgApi(this IServiceCollection services, IConfiguration configuration)
              {
                  services.AddHttpApi<ITest>().ConfigureHttpApiConfig(c =>
                  {
                      c.HttpHost = new Uri("http://192.168.88.143:9999/Msg/");
                      c.FormatOptions.DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
                  });
              }

      RPC API調用

           private readonly IUser _iUser;
              private readonly ICode _iCode;
              private readonly IId _id;
      
              public UserController(IUser iUser, ICode iCode, IId id, IHttpContextAccessor httpContextAccessor)
              {
                  _iUser = iUser;
                  _iCode = iCode;
                  _id = id;
              }
      
              #region 無登錄驗證請求
      
              /// <summary>
              /// 注冊
              /// </summary>
              /// <param name="registerRequest"></param>
              /// <returns></returns>
              [HttpPost("Register")]
              [AllowAnonymous]
              public async Task<ApiResult<UserLogonResponse>> RegisterUser(UserRegisterRequest registerRequest)
              {
                  //手機驗證
                  var codeVaildResult = await _iCode.Vaild(registerRequest.CountryCode + registerRequest.Phone, registerRequest.Code);
                  if (codeVaildResult.Failed)
                      return codeVaildResult.ToApiResult<UserLogonResponse>();
      
                  registerRequest.UserNo = await _id.Create("D4");
                  var registerResult = await _iUser.RegisterUser(registerRequest.MapTo<RegisterUserRequest>());
      
                  if (registerResult.Failed)
                      return ApiResult<UserLogonResponse>.IsFailed("注冊成功");
      
                  var token = BuildJwt(registerResult.Data.MapTo<AdministratorData>());
                  var response = registerResult.Data.MapTo<UserLogonResponse>();
                  response.Token = token;
      
                  return ApiResult<UserLogonResponse>.IsSuccess("注冊成功", response);
              }

      服務端

      Api SDK提供

       public interface ITest : IHttpApi
          {
              /// <returns></returns>
              [HttpGet("Test/Index")]
              ITask<ServiceResult> Test();
          }

      Api邏輯

          [Route("[controller]/[action]")]
          [ApiController]
          public class TestController : Controller
          {
              [HttpGet]
              public string Index()
              {
                  var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
      
                  string localIp = NetworkInterface.GetAllNetworkInterfaces()
                      .Select(p => p.GetIPProperties())
                      .SelectMany(p => p.UnicastAddresses)
                      .FirstOrDefault(p => p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address))?.Address.ToString();
      
                  var result = new List<string>();
      
                  var b = NetworkInterface.GetAllNetworkInterfaces()
                      .Select(p => p.GetIPProperties())
                      .SelectMany(p => p.UnicastAddresses)
                      .Where(p => p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address))
                      .Select(a =>
                      {
                          return new
                          {
                              Address = a.Address.ToStr()
                          };
                      }).ToList();
      
      
                  return b.ToJson() + "------" + localIp;
              }
          }

       結束

        該篇主要講解了API網關、注冊中心怎么集成微服務的,怎么讓請求路由到對應的服務,這也是大多數初學的微服務相對比較難啃的一道。那么我也是通過Kong、Consul、Fabio三個中間件結合來講述了他們的調用關系與使用。

        如果有文章有任何問題與更新的思路與方案,可在下方評論反饋給我

        

      posted @ 2021-01-11 13:10  陳珙  閱讀(5668)  評論(26)    收藏  舉報
      主站蜘蛛池模板: 最新国产精品亚洲| 国产麻豆md传媒视频| 国产乱子影视频上线免费观看| 亚洲中文字字幕精品乱码| 色噜噜狠狠色综合成人网| 日本一区不卡高清更新二区| 日韩在线一区二区每天更新| 这里只有精品在线播放| 国产91丝袜在线播放动漫| 麻豆亚洲精品一区二区| 久久久久国精品产熟女久色| 色综合天天综合网中文伊| 久99久热免费视频播放| 18禁亚洲一区二区三区| 亚洲一区二区精品另类| 亚洲老熟女乱女一区二区| 久久国产乱子精品免费女| 强奷白丝美女在线观看| 亚洲国产成人综合自在线| 豆国产97在线 | 亚洲| 国产日女人视频在线观看| 国产不卡av一区二区| 极品美女扒开粉嫩小泬图片| 国产成人精品国内自产色| 视频二区中文字幕在线| 中牟县| 一区二区亚洲人妻av| 亚洲乱码中文字幕小综合| 国产日韩入口一区二区| 国产视色精品亚洲一区二区| 久久99精品久久久大学生| 久久国产乱子精品免费女| 国产亚洲中文字幕久久网| 永久免费无码av网站在线观看| 人妻少妇无码精品视频区| 欧美高清狂热视频60一70| 国精偷拍一区二区三区| 国产日韩精品视频无码| 亚洲电影天堂在线国语对白| 动漫AV纯肉无码AV电影网 | 久久综合综合久久高清免费|