.netcore框架下的服务发现

   日期:2020-08-29     浏览:87    评论:0    
核心提示:项目所用到的NuGet包版本: netcore 3.1,consul 1.6.1.1什么是服务发现在传统的系统部署中,服务运行在一个固定的已知的 IP 和端口上,如果一个服务需要调用另外一个服务,可以通过地址直接调用,但是,在虚拟化或容器话的环境中,服务实例的启动和销毁是很频繁的,服务地址在动态的变化,如果需要将请求发送到动态变化的服务实例上,至少需要两个步骤:服务注册 — 存储服务的主机和端口信息服务发现 — 允许其他用户发现服务注册阶段存储的信息服务发现的主要优点是可以无需了解架..

目录

什么是服务发现 

服务发现的两种方式

1.客户端发现

2.服务端发现

代码演示

手动实现服务发现

使用Consul进行服务发现

使用Consul进行服务发现 -BlockingQueries

网关Ocelot

网关Ocelot+服务发现Consul

什么是服务发现 

在传统的系统部署中,服务运行在一个固定的已知的 IP 和端口上,如果一个服务需要调用另外一个服务,可以通过地址直接调用,但是,在虚拟化或容器话的环境中,服务实例的启动和销毁是很频繁的,服务地址在动态的变化,如果需要将请求发送到动态变化的服务实例上,至少需要两个步骤:

服务注册 — 存储服务的主机和端口信息

服务发现 — 允许其他用户发现服务注册阶段存储的信息

服务发现的主要优点是可以无需了解架构的部署拓扑环境,只通过服务的名字就能够使用服务,提供了一种服务发布与查找的协调机制。服务发现除了提供服务注册、目录和查找三大关键特性,还需要能够提供健康监控、多种查询、实时更新和高可用性等。

服务发现的两种方式

1.客户端发现

在使用客户端发现方式时,客户端通过查询服务注册中心,获取可用的服务的实际网络地址(IP 和端口)。然后通过负载均衡算法来选择一个可用的服务实例,并将请求发送至该服务。

优点:架构简单,扩展灵活,方便实现负载均衡功能。

缺点:强耦合,有一定开发成本。

 

2.服务端发现

客户端向load balancer 发送请求。load balancer 查询服务注册中心找到可用的服务,然后转发请求到该服务上。和客户端发现一样,服务都要到注册中心进行服务注册和注销。 优点:服务的发现逻辑对客户端是透明的。 缺点:需要额外部署和维护高可用的负载均衡器。

 

以上都是网络上可以搜索到的一些概念具体可以看一下,下面的链接

https://segmentfault.com/a/1190000004960668

下面我们假设一个业务环境,我们开发了一个库存的模块作为服务,然后有个盘点的界面会调用库存的服务,还有其他的模块,比如下单会间接的调用库存服务里的一些方法(我们假设调用模式是以api的形式调用),那么我们得到如下的一张库存服务的调用关系图:

大家注意一下:网关可以看做是一个负载均衡器,服务注册中心会返回服务的所有健康的真实的机器地址,然后网关就根据算法从对应服务下的n个地址中拿到一个真实地址进行链路的转发(客户端访问的是网关,他并不知道具体是访问了哪个api站点,网关会链路转发并且将http response返回给客户端):

举个例子:客户端访问一个请求 http://192.168.0.1:8090/StoreAPI/api/Store/Add (网关转发的配置 /StoreAPI/{url} 指向 StoreService下所有的服务地址)

我们的库存API服务下有个新增库存的接口地址为: http://192.168.10:8091/api/Store/Add , http://192.168.11:8091/api/Store/Add , http://192.168.12:8091/api/Store/Add  三个服务都是健康可用的 那么网关就挑里面的一个访问,并且返回结果给客户端。

如果 此时第三台服务器宕机了,那么服务注册中心获取不到心跳,就会把 192.168.0.12这台机器从可用服务列表中移除,网关就从前两个地址中挑一个访问。

下面我们通过代码更直观的循序渐进的理解一下:

代码演示

手动实现服务发现

我们先看一下项目目录结构:

ServiceAPI项目中建一个ServiceController.cs 里面做一个最简单的方法输出配置文件的 Msg,代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace ServiceAPI.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class ServiceController : ControllerBase
    {
        public IConfiguration Configuration { get; }
        public ServiceController(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        [HttpGet]
        public ActionResult<string> Show()
        {
            return Configuration["Msg"];
        }
    }
}

然后我们在IIS下部署两个相同的站点,修改配置文件中的Msg值分别为api1和api2

服务端部署好了 我们开发一下客户端我们用asp.net core mvc项目代替,WebClient项目中我们修改 首页中的OnGet方法,让他输出 当前时间+ 服务端api/Service/Show 中返回的Msg值

using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace WebClient.Pages
{
    public class IndexModel : PageModel
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IConfiguration _configuration;
         
        public IndexModel(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            _httpClientFactory = httpClientFactory;
            _configuration = configuration; 
        }

        public string Message { get; private set; } 
         

        public void OnGet()
        {
            Message = $"{DateTime.Now} {GetService()}"; 
        }

        /// <summary>
        /// 客户端 手动发现
        /// </summary>
        /// <returns></returns>
        public string GetService()
        {
            try
            {
                string[] serviceUrls = { "http://localhost:9050", "http://localhost:9051" };
                string serviceUrl = serviceUrls[new Random().Next(0, 2)];

                var client = _httpClientFactory.CreateClient();
                client.BaseAddress = new Uri(serviceUrl + "/api/Service/Show");
                return client.GetStringAsync(serviceUrl + "/api/Service/Show").Result;
            }
            catch
            {
                return "bad service";
            }
        }  

         
    }
}

我们新建了一个变量 string[] serviceUrls 保存了 "http://localhost:9050", "http://localhost:9051" 两个地址,并且用了随机访问的模式随机一个地址进行访问:

我们看一下运行效果:

启动后访问到  http://localhost:9050/api/Service/Show  输出 api1

再刷新一下,访问到  http://localhost:9051/api/Service/Show  输出 api2

此时我们把api2的服务停一下,然后多刷新几次你会发现有时候会出现以下情况:

随机访问到了 被我们关闭的api站点,所以返回bad service了,原因是我们手写的 变量 string[] serviceUrls 中没有移除关闭的站点地址。

那真实业务中当一个机器宕机的时候我们不应该再去访问这个服务,那么怎么办呢?就有了我们下面的改进

使用Consul进行服务发现

为了让各种原因导致的不可用的站点地址从 string[] serviceUrls 移除走,我们就需要实时去监控serviceUrls里面的所有站点是否健康。

首先我们要在api中添加一个 健康检查 api,注册中心会调用这个api检查站点是否可用,可能1s监测一次,也可能n秒去监测一次

然后当注册中心发现这个心跳 好几次都不通了,那么就要标记一下这个站点地址不健康了, 客户端通过注册中心查询服务地址列表时只会拿到健康的服务地址,那么自然就访问不到那个宕机的api了。

最后要注意了,当这个宕机的api修复后,服务注册中心又能监听到这个api的心跳了,又会再标记一下这个服务也是健康的并且返回给客户端。

这时候如果还是通过手撸代码的模式 实现服务注册中心的功能,已经有一些难度了,有兴趣的同学可以自己去实现一下,我们这里引用第三方工具 Consul 来实现。

我们先介绍一下 Consul 需要安装部署一些什么 并且consul做了些什么事情:

官方文档 https://www.consul.io/

我还找一篇博客 https://www.jianshu.com/p/32dc52f28a14

首先我们在服务器上安装一下 consul服务端,consul客户端的实现是写在netcore的客户端和 服务端里的

注册大致流程图如下:

当然我们也可以 不使用 Consul NuGet包 进行向Consul服务端的注册,只要服务器上启用了Consul服务端,我们可以用api去直接访问操作服务端,只不过第三方包已经帮我们封装好了直接调用就可以了。

接下来我们上代码,改造一下  API服务的代码,再startup中 向consul服务端注册,并且关闭程序的时候移除注册信息。

ServiceAPI 项目中  Startup代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ServiceAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env,IHostApplicationLifetime lifetime)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            #region 注册服务

            var consulClient = new ConsulClient(c =>
            {
                c.Address = new Uri(Configuration["ConsulSetting:ConsulAddress"]);
            });
            var registration = new AgentServiceRegistration()
            {
                ID = Guid.NewGuid().ToString(),//服务实例唯一标识
                Name = Configuration["ConsulSetting:ServiceName"],//服务名
                Address = Configuration["ConsulSetting:ServiceIP"], //服务IP
                Port = int.Parse(Configuration["ConsulSetting:ServicePort"]),//服务端口  
                Check = new AgentServiceCheck()
                {
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
                    Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔
                    HTTP = $"http://{Configuration["ConsulSetting:ServiceIP"]}:{Configuration["ConsulSetting:ServicePort"]}{Configuration["ConsulSetting:ServiceHealthCheck"]}",//健康检查地址
                    Timeout = TimeSpan.FromSeconds(5)//超时时间
                }
            };
            //服务注册
            consulClient.Agent.ServiceRegister(registration).Wait();
            //应用程序终止时,取消注册
            lifetime.ApplicationStopping.Register(() =>
            {
                consulClient.Agent.ServiceDeregister(registration.ID).Wait();
            });

            #endregion
        }
    }
}

配置文件 appsetting.json 如下:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Msg": "{msg}",
  "ConsulSetting": {
    "ServiceName": "APIService",
    "ServiceIP": "127.0.0.1",
    "ServicePort": "9050",
    "ServiceHealthCheck": "/api/ApiMonitor/HealthCheck",
    "ConsulAddress": "http://localhost:8500"
  }
}

我们重新发布一下两个api站点,然后启用看一下Consul的UI界面。

我推荐启用命令 使用:  consul.exe agent - dev  后面再跟上 -client 0.0.0.0 -ui  这样可以开启ip地址的访问 否则只能通过localhost:8500进行访问 

咦,怎么api没有注册进来,大家不要担心,代码没有问题只不过 .netcore 在IIS中发布的时候,走的是进程外挂载,只有第一个请求到达的时候 api站点程序才会运行起来,大家看一下这个博客跟着设置一下,设置完之后只要发布完api 注册中心就会有 注册进去的服务了

https://www.cnblogs.com/lxz-blog/p/12870950.html

正常的Consul UI界面如下:

 

WebClient的客户端我们也需要做一些修改,我们不在是在变量中随机获取服务的地址,而是从Consul服务端中找到 注册服务的地址列表:

WebClient项目的 Index.cshtml.cs 改动如下:

using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace WebClient.Pages
{
    public class IndexModel : PageModel
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IConfiguration _configuration;
        
        private readonly ConsulClient _consulClient;
         
        public IndexModel(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            _httpClientFactory = httpClientFactory;
            _configuration = configuration;
            #region
            _consulClient = new ConsulClient(c =>
            {
                //consul地址
                c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
            });
            #endregion
        }

        public string Message { get; private set; } 
         

        public void OnGet()
        {
             
            #region  通过consul发现服务

            Message = $"{DateTime.Now} {GetServiceByConsul()}";

            #endregion

            
        } 

        /// <summary>
        /// 使用Consul实现服务发现
        /// </summary>
        /// <returns></returns>
        public string GetServiceByConsul()
        { 
            var consulClient = new ConsulClient(c =>
            {
                //consul地址
                c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
            }); 
            var services = consulClient.Health.Service("APIService", null, true, null).Result.Response;//健康的服务

            string[] serviceUrls = services.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToArray();//服务地址列表

            if (!serviceUrls.Any())
            {
                return  "服务列表为空!" ;
            }

            //每次随机访问一个服务实例 
            string serviceUrl = serviceUrls[new Random().Next(0, serviceUrls.Length)];

            var client = _httpClientFactory.CreateClient();
            client.BaseAddress = new Uri(serviceUrl + "/api/Service/Show");
            return client.GetStringAsync(serviceUrl + "/api/Service/Show").Result;
        } 
    }
}

配置文件如下:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConsulSetting": {
    "ConsulAddress": "http://localhost:8500"
  }
}

这时候就完美的解决了 ,不访问不健康的api了。

大家有没有注意到一点,其实这样写法效率上还有点问题,当客户端需要调用 APIService这个服务名称下的服务地址的时候,每次都要去 Consul服务端获取一遍健康的api地址,那应该怎么去优化一下呢?我们看一下一章

使用Consul进行服务发现 -BlockingQueries

大家看一下consul官网对BlockingQueries的解释,其实就是用一个版本号去控制,我们获取Consul服务列表的同时获取一个版本号存于程序本地,当服务列表内的地址有所改变后会有个新的版本号,此时在调用服务发现查询列表时两个版本对不上才去Consul服务端在请求一次服务列表的查询(这个版本号的用法 大家有没有觉得很像 数据库里处理并发的乐观锁)

那么我们在 WebClient项目的 Startup中加入一端程序去维护服务列表并落地,当版本发生改变的时候维护这个落地数据,这样调用的时候 落地的服务列表永远就是最新的健康的服务列表:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace WebClient
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHttpClient();
            services.AddRazorPages();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
            });

            #region
            Task.Run(() =>
            {
                ConsulClient _consulClient = new ConsulClient(c =>
                {
                    //consul地址
                    c.Address = new Uri(Configuration["ConsulSetting:ConsulAddress"]);
                });

                //WaitTime默认为5分钟
                var queryOptions = new QueryOptions { WaitTime = TimeSpan.FromMinutes(10) };
                while (true)
                {
                    var res = _consulClient.Health.Service("APIService", null, true, queryOptions).Result;


                    //版本号不一致 说明服务列表发生了变化
                    if (queryOptions.WaitIndex != res.LastIndex)
                    {
                        //控制台打印一下获取服务列表的响应时间等信息
                        Console.WriteLine($"{DateTime.Now}获取 APIService :queryOptions.WaitIndex:{queryOptions.WaitIndex}  LastIndex:{res.LastIndex}");

                        queryOptions.WaitIndex = res.LastIndex;

                        //控制台打印一下获取服务列表的响应时间等信息
                        Console.WriteLine($"{DateTime.Now}更新 APIService :queryOptions.WaitIndex:{queryOptions.WaitIndex}  LastIndex:{res.LastIndex}");


                        //服务地址列表
                        var serviceUrls = res.Response.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToList();

                        ServicesList.Urls = serviceUrls;
                    }
                }
            });
            #endregion
        }
    }
}

WebClient项目的 Index.cshtml.cs 改动如下:

using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace WebClient.Pages
{
    public class IndexModel : PageModel
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IConfiguration _configuration; 

        public IndexModel(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            _httpClientFactory = httpClientFactory;
            _configuration = configuration; 
        }

        public string Message { get; private set; } 
         

        public void OnGet()
        { 
            Message = $"{DateTime.Now} {GetServiceByConsulBlockingQueries()}"; 
        } 

        /// <summary>
        /// 客户端 Consul发现 优化
        /// </summary>
        /// <returns></returns>
        public string GetServiceByConsulBlockingQueries()
        {
            var serviceUrls = ServicesList.Urls;

            if (!serviceUrls.Any())
            {
                return "服务列表为空!";
            }

            //每次随机访问一个服务实例 
            string serviceUrl = serviceUrls[new Random().Next(0, serviceUrls.Count)];

            var client = _httpClientFactory.CreateClient();
            client.BaseAddress = new Uri(serviceUrl + "/api/Service/Show");
            return client.GetStringAsync(serviceUrl + "/api/Service/Show").Result;
        } 
    }
}

现在看看最新的代码 越来越简单,并且执行效率也快了。但是有没有发现还有一个美中不足的地方就是

//每次随机访问一个服务实例 
string serviceUrl = serviceUrls[new Random().Next(0, serviceUrls.Count)];

我们先在用的时客户端自己去实现 负载均衡寻找一个服务地址,现在的客户端时asp.net core mvc 可能还有客户端是 ios程序,也可能是web程序,那开发的成本就有点高了,这时候我们引入网关,把负载均衡这块的功能移除到客户端外,客户端访问网关,网关再去和服务注册中心进行交互,就有了我们下面的改动。

网关Ocelot

大家再拉到文章的开始 看一下那张 库存服务的调用关系图。是不是就豁然开朗了,其实现在要写的代码才是最终解决方案,之前的代码只是告诉大家最后的方案是怎么演变过来的。

对于网关 我们选用netcore下的第三方开源包  Ocelot,功能其实和Nginx差不多,只是我觉得Ocelot是一款更适合编程的网关,Nginx如果需要加一些定制化的东西进去开发成本会比较高。

如果有同学还不知道网关,不知道Nginx的话,可以自行网上搜索一下。

这个时候 我们demo的目录结构发生了变化,新加了一个网关的项目:

我们先做一个网关手动指定的服务ip地址的例子:

Ocelot.json 内容如下:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 9050
        },
        {
          "Host": "localhost",
          "Port": 9051
        }
      ],
      "UpstreamPathTemplate": "/GateWay/{url}",
      "UpstreamHttpMethod": [
        "Get", "Post", "Put", "Delete"
      ],
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      }
    } 
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:9070"
  }
}

大致的意思是 上游访问网关的时候根据 /GateWay/{url} 进行分发 ,将请求分发到 localhost:9050/{url} 或者 localhost:9051/{url}下,请求的类型限定为 Get,Post,Put和 Delete,负载均衡的分发策略为:RoundRobin(轮询)

然后网关的 Startup.cs 注册一下 ocelot中间件 并且使用一下ocelot管道即可代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Consul;

namespace ApiGateWay
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddOcelot(new ConfigurationBuilder()
            .AddJsonFile("Ocelot.json")
            .Build());//注意!!

            //services.AddOcelot(new ConfigurationBuilder()
            //.AddJsonFile("Ocelot.json")
            //.Build()).AddConsul();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();

            app.UseOcelot().Wait();//注意!!

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "api/{controller}/{action}");
            });
        }
    }
}

我们发布一下网关项目:

再修改一下 WebClient项目,代码变得更为精简,只需要访问网关9070地址:

using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace WebClient.Pages
{
    public class IndexModel : PageModel
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IConfiguration _configuration; 

        public IndexModel(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            _httpClientFactory = httpClientFactory;
            _configuration = configuration; 
        }

        public string Message { get; private set; } 
         

        public void OnGet()
        { 
            Message = $"{DateTime.Now} {GetServiceByOcelot()}";
             
        } 
        /// <summary>
        /// 网关 
        /// </summary>
        /// <returns></returns>
        public string GetServiceByOcelot()
        {
            try
            {
                var gatewayUrl = "http://localhost:9070"; 
                var client = _httpClientFactory.CreateClient();
                client.BaseAddress = new Uri(gatewayUrl + "/GateWay/api/Service/Show");
                return client.GetStringAsync(gatewayUrl + "/GateWay/api/Service/Show").Result;
            }
            catch
            {
                return "bad service";
            }
        }

         
    }
}

马上就要大功告成了,我们只需要在网关中集成Consul服务发现,就能去除掉刚才网关配置文件中那些服务器地址的硬编码了。

网关Ocelot+服务发现Consul

这里已经没有什么代码需要改了,netcore这个框架 包括Ocelot这个网关,我是真心觉得很方便,我们改一下网关Ocelot的配置文件:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "APIService",
      "UpstreamPathTemplate": "/GateWay/{url}",
      "UpstreamHttpMethod": [
        "Get",
        "Post",
        "Put",
        "Delete"
      ],
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:9070",
    "ServiceDiscoveryProvider": {
      "Scheme": "http",
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    }
  }
}

已经没有了服务的硬编码 ip地址,剩下的只是 Consul中服务注册的名字 APIService。

网关项目中我们引入一下 Ocelot.Provider.Consul 包

Startup文件中我们 在注册网关的代码后再顺便 添加一下Consul这个服务发现。

services.AddOcelot(new ConfigurationBuilder()
            .AddJsonFile("Ocelot.json")
            .Build()).AddConsul();

今天分享的内容全部结束了

示例代码已上传github  https://github.com/Benjamin0626/ServiceDiscoveryDemo

但是如果要做好分布式的架构,我们需要考虑的因素其实还有很多,比如Consul的集群,服务的治理,服务的熔断等等,包括还有 网关的高可用,以及我们可以发现IIS其实已经不是一个很好的选择了,我们更倾向将Netcore的程序发布在Linux中,或者发布在Docker中,那么程序CI/CD 等等等等 又是新的话题。

如果您认为这篇文章还不错或者有所收获,您可以点击左下角的【点赞】【收藏】,或请我喝杯咖啡【赞赏】,这将是我继续写作,分享的最大动力!

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服