zoukankan      html  css  js  c++  java
  • 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》

  • 相关阅读:
    Scanner类
    BufferedReader类
    打印类
    管道流
    内存操作流
    转换流——OutputStreamWriter类与InputStreamReader类
    Java字节流与字符流基本操作
    RandomAccessFile类
    File类
    Timer类和TimerTask类
  • 原文地址:https://www.cnblogs.com/zhengwei-cq/p/12894981.html
Copyright © 2011-2022 走看看