NetCore项目实战篇06---服务注册与发现之consul
至此,我们的解决方案中新建了三个项目,网关(Zhengwei.Gateway)、认证中心(Zhengwei.Identity)和用户资源API(Zhengwei.Use.Api)。当要访问用户API的某个资源先要访问网关,网关要对请求进行认证,然后要访问认证中心,认证通过后才能访问对应的资源。今天我们要讲的是在认证的时候我们需要较验用户的信息,这时就要访问用户服务(因该项目采用微服务,所有的模块都叫服务),这就涉及到服务之间的访问与发现了。所以这节重点在于使用consul注册服务与发现服务。多说也不益,直接上项目吧。
1、 下载consul 下载地址:https://www.consul.io/downloads.html
下载下来的文件是一个.exe文件,如下图:
2、 cmd打开我们的命令窗口,切换到对应的下载目录,输入命令consul agent –dev,看到如下的信息说明你的consul服务正常启动。
3、 consul提供了一个UI的访问界面,界面上可以看到注册的服务,地址:http://localhost:8500,界面如下,现在还没有服务注册:
4、 回到我们项目中开始注册服务吧。现在要求zhengwei.user.api这个项目在启动时就向consul中注册服务。引用consul包到zhengwei.user.api项目中。
5、增加配置,配置consul服务器的地址和要注册的服务名,配置的意思是向http://127.0.0.1:8500所在的服务器(也就是consul所在的服务器)上注册名为userapi的服务,如下图:
6、在项目启动时注册服务时自定义RegisterService()方法,在项目停止时取消服务自定义DeRegisterService()方法。下面贴出Startup.cs类完整代码。但是这里有一个小问题:我用的127.0.0.1无法访问,用localhost就可以,我的IP和别名映射没有问题的。但是就是访问报错,可能是跨域的问题吗,这里暂不讨论,有解决了的园友可以给我留言。
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.AddDbContext<UserContext>(options => { options.UseMySQL(Configuration.GetConnectionString("MysqlUser")); }); services.Configure<ServiceDisvoveryOptions>(Configuration.GetSection("ServiceDiscovery")); services.AddSingleton<IConsulClient>(p => new ConsulClient(cfg => { var s = p.GetRequiredService<IOptions<ServiceDisvoveryOptions>>().Value; if(!string.IsNullOrEmpty(s.Consul.HttpEndpoint)) { cfg.Address = new Uri(s.Consul.HttpEndpoint); } })); services.AddMvc(p=>p.Filters.Add(typeof(GlobalExceptionFilter))); //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, ILoggerFactory loggerFactory, IApplicationLifetime lifetime, IOptions<ServiceDisvoveryOptions> serviceOptions, IConsulClient consul) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // app.UseHsts(); } //启动时注册服务 安装consul后,通过localhost:8500可以查看服务 lifetime.ApplicationStarted.Register(()=> { RegisterService(app, serviceOptions,consul,lifetime); }); //停止时注销服务 lifetime.ApplicationStopped.Register(() => { DeRegisterService(app, serviceOptions, consul, lifetime); }); app.UseMvc(); //UserContextSeed.SeedAsync(app, loggerFactory).Wait(); } private void DeRegisterService(IApplicationBuilder app, IOptions<ServiceDisvoveryOptions> serviceOptions, IConsulClient consul, IApplicationLifetime lifetime) { var features = app.Properties["server.Features"] as FeatureCollection; var addresses = features.Get<IServerAddressesFeature>() .Addresses .Select(p => new Uri(p)); foreach (var address in addresses) { var serviceId = $"{serviceOptions.Value.ServiceName}_{address.Host}:{address.Port}"; consul.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult(); } } private void RegisterService(IApplicationBuilder app, IOptions<ServiceDisvoveryOptions> serviceOptions, IConsulClient consul, IApplicationLifetime lifetime) { var httpCheck = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1), Interval = TimeSpan.FromSeconds(30), HTTP = $"http://127.0.0.1:33545/HealthCheck" }; var anentReg = new AgentServiceRegistration() { ID = "userapi:33545", Check = httpCheck, Address = "127.0.0.1", Name = "userapi", Port = 33545 }; var serviceId = "userapi:127.0.0.1:33545"; consul.Agent.ServiceRegister(anentReg).GetAwaiter().GetResult(); lifetime.ApplicationStopping.Register(()=> { consul.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult(); }); } }
7、从注册的代码中可以看到在注册服务前会进行健康检查也就是调用Zhengwei.Use.Api项目中HealthCheckController控制器的Ping()方法。那么我们现在将这个方法加上。代码简单,直接上截图吧。
8、我们在UserController还要加一个方法CheckOrCreate()供认证时调用
9、启动项目,再次进入localhost:8500页面,会看到一个名叫userapi的服务已注册到我们的consul中。
10、服务注册到我们的consul中后,接下来就要在认证时(Zhengwei.Identity项目中)找到这个服务,并调用对应的CheckOrCreate()方法。
11、在这个系统的第四篇文章中(NetCore项目实战篇04---集成IdentityService4)已经写好了调用use.api的接口和实现类,并实现了方法CheckOrCreate()只不过当时我们这个方法是写死的,直接return 1;现在我们就要真正开始调用Zhengwei.Use.Api项目中的这个方法了。见代码:
public class UserService : IUserService { //private string _userServiceUrl = "http://localhost:33545"; private string _userServiceUrl; private HttpClient _httpClient; public UserService(HttpClient httpClient,IOptions<Dtos.ServiceDisvoveryOptions> serOp,IDnsQuery dnsQuery) { _httpClient = httpClient; var address = dnsQuery.ResolveService("service.consul",serOp.Value.ServiceName); var addressList = address.First().AddressList; var host = addressList.Any() ? addressList.First().ToString() : address.First().HostName; var port = address.First().Port; _userServiceUrl = $"http://{host}:{port}"; } public async Task<int> CheckOrCreate(string phone) { var from = new Dictionary<string, string> { { "phone", phone } }; var content = new FormUrlEncodedContent(from); var response = await _httpClient.PostAsync(_userServiceUrl + "/api/users/check-or-create", content); if(response.StatusCode == System.Net.HttpStatusCode.OK) { var userId =await response.Content.ReadAsStringAsync(); int.TryParse(userId, out int intuserId); return intuserId; } return 0; } }
在这个类中,我们先是发现了use.api服务的ip地址,并给_userServiceUrl字段赋值,在CheckOrCreate方法中就直接调用这个地址对应的方法了。
12、 所有编码完成生成没有问题后,同时启动这三个项目,再次打开postman,通过:http://localhost:4157/connect/token获取token,再用token值去访问:http://localhost:4157/users,如果一切正常,那就会返回对应的用户信息。
到此,我们网关、认证、服务注册与发现这条线就算是走通了。
但是,这里还会有一个问题,就是在UserService.cs类中CheckOrCreate方法是直接调用的另一个服务,对的,这里就是直接调用的,很粗暴,对调用后的错误没有作处理,如服务是否响应,调用时是否报错等,都没有处理。这里是否会有更好的处理方法呢,请看下篇《NetCore项目实战篇07---服务保护之polly》