微服务中有关键的几项技术,其中网关和服务服务发现,服务注册相辅相成。
首先解释几个本次教程中需要的术语
网关 Gateway(API GW / API 网关),顾名思义,是企业 IT 在系统边界上提供给外部访问内部接口服务的统一入口,简化了外部由于多服务协同完成任务时的繁琐配置。网关组件有Kong,ocelot,
服务发现:通过网关访问内部各个微服务,网关要找到所需服务的过程称为服务发现
服务注册:既然有服务发现,前提是要把所需服务提前“录入”,这个录入的过程称为服务注册。服务注册可配置文件(人肉方式不推荐),也可用服务注册组件如Consul或者Eureka等等(推荐)
官网下载Consul程序,https://www.consul.io/downloads.html
下载下来就是一个可执行文件Consul.exe
Consul有两种代理模式,一种server,一种client,官方建议Server端达到3台以上才可高可用,但不要太多,太多会给集群间数据同步造成压力,client数量不限。
多个server端之间会选择出一个leader,当一个server的leader宕机则会从其他server端”投票“选择新的leader
这里server我们用2台实验
192.168.74.55
192.168.74.54
1台Client
192.168.74.161
consul启动有两种方式一种是命令行,一种是配置文件的方式。
命令行方式启动一个consul的server端
consul agent -server -ui -bootstrap-expect 2 -data-dir opt/consul/data -node ServerMaster -bind 192.168.74.55 -client 192.168.74.55
关键参数说明
-server:server模式启动
-ui :开启ui界面(consul.exe内部带了GUI图形界面操作)
-bootstrap-expect 2:server端到2个时集群生效
-data-dir:consul产生的文件路径(consul自己会产生一下数据存储的位置)
-node:此节点名称
-bind:集群内部通信地址,默认0.0.0.0
-client:此节点绑定的通讯地址
以上只是关键参数,以下是完整参数说明:
但是命令启动很繁琐,所以推荐下面的配置文件的方式启动
在consul同文件夹下建立一个server.json的配置文件
{ "datacenter": "dc1", "data_dir": "opt/consul/data", "node_name": "consul-server01", "server": true, "bootstrap_expect": 2, "bind_addr": "192.168.74.55", "client_addr": "192.168.74.55", "ui":true }
为了快速启动,再建立一个bat批处理文件runconsul.bat
consul agent -config-dir server.json pause
双击runconsul.bat启动consul
在192.168.74.54服务器开启一个server端继续以上操作。
命令方式启动
consul agent -server -ui -data-dir opt/consul/data -node Server01 -bind 192.168.74.54 -client 192.168.74.54 -join=192.168.74.55
-join将192.168.74.54加入到192.168.74.55服务器
配置文件方式:
{ "datacenter": "dc1", "data_dir": "opt/consul/data", "node_name": "consul-server2", "server": true, "bind_addr": "192.168.74.54", "client_addr": "192.168.74.54", "ui":true, "retry_join": ["192.168.74.55"], "retry_interval": "30s", "rejoin_after_leave": true, "start_join":["192.168.74.55"] }
在192.168.74.161服务器开启一个consul的client端
命令方式:
consul agent -ui -data-dir opt/consul/data -node ServerSlave -bind 192.168.74.161 -client 192.168.74.161 -join 192.168.74.55
配置文件方式:
{ "datacenter": "dc1", "data_dir": "opt/consul/data", "node_name": "consul-client01", "server": false, "bind_addr": "192.168.74.161", "client_addr": "192.168.74.161", "ui":true, "retry_join": ["192.168.74.55"], "retry_interval": "30s", "rejoin_after_leave": true, "start_join":["192.168.74.55"] }
简单Consul集群到这里就搭建成功,只要访问三台服务器任意一个都可数据同步,演示:
首先新建一个ConsulClient的类库
ConsulRegister.csproj所需组件如下:
netstandard2.0
服务发现自动注册,无需手动绑定本机地址,会自动扫描本地ipv4地址和localhost地址,项目中无需再手动创建健康检查接口
ServiceDiscoveryOptions.cs using System; using System.Collections.Generic; using System.Text; namespace ConsulRegister { ////// 服务治理第三方组件Consul相关配置参数 /// public class ServiceDiscoveryOptions { public string ServiceName { get; set; } public ConsulOptions Consul { get; set; } } public class ConsulOptions { public string HttpEndPoint { get; set; } } }
RegisterToConsulExtension.cs using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; namespace ConsulRegister { public static class RegisterToConsulExtension { ////// Add Consul /// 添加consul /// /// /// ///public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration configuration) { // configuration Consul register address //配置consul注册地址 services.Configure (configuration.GetSection("ServiceDiscovery")); //configuration Consul client //配置consul客户端 services.AddSingleton (sp => new Consul.ConsulClient(config => { var consulOptions = sp.GetRequiredService >().Value; if (!string.IsNullOrWhiteSpace(consulOptions.Consul.HttpEndPoint)) { config.Address = new Uri(consulOptions.Consul.HttpEndPoint); } })); return services; } /// /// use Consul /// 使用consul /// The default health check interface format is http://host:port/HealthCheck /// 默认的健康检查接口格式是 http://host:port/HealthCheck /// /// ///public static IApplicationBuilder UseConsul(this IApplicationBuilder app) { IConsulClient consul = app.ApplicationServices.GetRequiredService (); IApplicationLifetime appLife = app.ApplicationServices.GetRequiredService (); IOptions serviceOptions = app.ApplicationServices.GetRequiredService >(); var features = app.Properties["server.Features"] as FeatureCollection; var port = new Uri(features.Get () .Addresses .FirstOrDefault()).Port; Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine($"application port is :{port}"); var addressIpv4Hosts = NetworkInterface.GetAllNetworkInterfaces() .OrderByDescending(c => c.Speed) .Where(c => c.NetworkInterfaceType != NetworkInterfaceType.Loopback && c.OperationalStatus == OperationalStatus.Up); foreach (var item in addressIpv4Hosts) { var props = item.GetIPProperties(); //this is ip for ipv4 //这是ipv4的ip地址 var firstIpV4Address = props.UnicastAddresses .Where(c => c.Address.AddressFamily == AddressFamily.InterNetwork) .Select(c => c.Address) .FirstOrDefault().ToString(); var serviceId = $"{serviceOptions.Value.ServiceName}_{firstIpV4Address}:{port}"; var httpCheck = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1), Interval = TimeSpan.FromSeconds(30), //this is default health check interface //这个是默认健康检查接口 HTTP = $"{Uri.UriSchemeHttp}://{firstIpV4Address}:{port}/HealthCheck", }; var registration = new AgentServiceRegistration() { Checks = new[] { httpCheck }, Address = firstIpV4Address.ToString(), ID = serviceId, Name = serviceOptions.Value.ServiceName, Port = port }; consul.Agent.ServiceRegister(registration).GetAwaiter().GetResult(); //send consul request after service stop //当服务停止后想consul发送的请求 appLife.ApplicationStopping.Register(() => { consul.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult(); }); Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine($"health check service:{httpCheck.HTTP}"); } //register localhost address //注册本地地址 var localhostregistration = new AgentServiceRegistration() { Checks = new[] { new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1), Interval = TimeSpan.FromSeconds(30), HTTP = $"{Uri.UriSchemeHttp}://localhost:{port}/HealthCheck", } }, Address = "localhost", ID = $"{serviceOptions.Value.ServiceName}_localhost:{port}", Name = serviceOptions.Value.ServiceName, Port = port }; consul.Agent.ServiceRegister(localhostregistration).GetAwaiter().GetResult(); //send consul request after service stop //当服务停止后想consul发送的请求 appLife.ApplicationStopping.Register(() => { consul.Agent.ServiceDeregister(localhostregistration.ID).GetAwaiter().GetResult(); }); app.Map("/HealthCheck", s => { s.Run(async context => { await context.Response.WriteAsync("ok"); }); }); return app; } } }
再新建一个.netcore的webapi项目WebA,并且引用ConsulRegister项目
在WebA项目中的Startup.cs文件中加入Consul服务
public void ConfigureServices(IServiceCollection services) { services.AddConsul(Configuration); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseConsul(); app.UseMvc(); }
在WebA项目的appsettings.json配置文件中加入以下Consul服务端配置
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "ServiceDiscovery": { "ServiceName": "A", "Consul": { "HttpEndpoint": "http://192.168.74.161:8500" } } }
这里服务注册就算完成
接下来继续Ocelot借助于Consul实现服务发现
新建项目Ocelot.Gateway
将以下依赖加入Ocelot.Gateway.csproj中:
netcoreapp2.1 PreserveNewest
新建ocelot.json文件
{ "ReRoutes": [ { "UseServiceDiscovery": true, "DownstreamPathTemplate": "/{url}", "DownstreamScheme": "http", "ServiceName": "A", "LoadBalancerOptions": { "Type": "RoundRobin" }, "UpstreamPathTemplate": "/a/{url}", "UpstreamHttpMethod": [ "Get", "Post" ], "ReRoutesCaseSensitive": false } ], "GlobalConfiguration": { // 使用Consul服务治理 "ServiceDiscoveryProvider": { "Host": "192.168.74.161", "Port": 8500, "ConfigurationKey": "Oceolot_A" //存储在Consul上的Key } } }
修改Startup.cs文件如下:
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddOcelot( new ConfigurationBuilder() .AddJsonFile("ocelot.json", optional: false, reloadOnChange: true).Build()) .AddConsul() .AddConfigStoredInConsul(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseOcelot().Wait(); } }
发布WebA后复制两份分别启动
dotnet WebA.dll --urls="http://0.0.0.0:2001"
dotnet WebA.dll --urls="http://0.0.0.0:2002"
到这里相当于2001和2002程序简单集群了一下
可以发现日志中有 http://192.168.74.161:2002/HealthCheck调用信息: