NetCore專案實戰篇06---服務註冊與發現之consul

zhengwei_cq發表於2020-05-15

至此,我們的解決方案中新建了三個專案,閘道器(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》

相關文章