Consul 簡介
Consul是一個服務網格(微服務間的 TCP/IP,負責服務之間的網路呼叫、限流、熔斷和監控)解決方案,它是一個一個分散式的,高度可用的系統,而且開發使用都很簡便。它提供了一個功能齊全的控制平面,主要特點是:服務發現、健康檢查、鍵值儲存、安全服務通訊、多資料中心。除了 Consul 之外,還有 Eureka、Zookeeper 等類似軟體。
安裝Consul
我們這裡是直接在Windows上開發,所以對應下載Windows
版本的。下載地址:
下載完成後實際就是consul.exe
,我們在下載位置執行cmd命令
consul agent -dev
然後我們開啟瀏覽器,輸入http://localhost:8500/
可以看到Consul已經啟動了,但是除了他自己外還沒有其他服務註冊進來。
服務註冊
我們建立一個Api專案,比如訂單服務OrderService
。
安裝Consul
健康檢查
建立完成後就是預設的專案結構,我們新增一個健康檢查的Controller。健康檢查的意思是Consul會根據我們的配置定時的去請求健康檢查介面,判斷當前服務是不是可用。避擴音供掛掉的服務給消費者,當然間隔時間也會有,需要配合後面的熔斷、降級使用。
using System;
using Microsoft.AspNetCore.Mvc;
namespace Consul.OrderService.Controllers
{
[ApiController]
[Route("[controller]")]
public class HealthController : ControllerBase
{
public IActionResult Get()
{
Console.WriteLine("呼叫了健康檢查" + DateTime.Now);
return Ok("OK");
}
}
}
呼叫時我們輸出下當前時間,可以看到Consul有沒有呼叫健康檢查。
服務註冊及登出
因為這裡需要指定Api的地址,所以我們配置一下獲取配置。
修改Program.cs
public static IHostBuilder CreateHostBuilder(string[] args)
{
var config = new ConfigurationBuilder().AddCommandLine(args).Build();
string ip = config["ip"];
string port = config["port"];
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseUrls($"http://{ip}:{port}");
});
}
修改Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,IHostApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
string ip = Configuration["ip"];
int port = Convert.ToInt32(Configuration["port"]);
string serveiceName = "OrderService";
string serviceId = serveiceName + Guid.NewGuid();
using (var consulClient = new ConsulClient(ConsulConfig))
{
AgentServiceRegistration agentServiceRegistration = new AgentServiceRegistration();
//服務編號,不能重複,用 Guid 最簡單
agentServiceRegistration.ID = serviceId;
//服務的名字
agentServiceRegistration.Name = serveiceName;
//服務提供者的能被消費者訪問的 ip 地址(可以被其他應用訪問的 地址,本地測試可以用 127.0.0.1,機房環境中一定要寫自己的內網 ip 地址)
agentServiceRegistration.Address = ip;
// 服務提供者的能被消費者訪問的埠
agentServiceRegistration.Port = port;
agentServiceRegistration.Check = new AgentServiceCheck()
{
//服務停止多久 後反註冊(登出)
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),
//健康檢查時間間隔,或者稱為心跳 間隔
Interval = TimeSpan.FromSeconds(10),
//健康檢查地址
HTTP = $"http://{ip}:{port}/health",
Timeout = TimeSpan.FromSeconds(5)
};
//註冊服務到 Consul
consulClient.Agent.ServiceRegister(agentServiceRegistration).Wait();//Consult 客戶端的所有方法幾乎都是非同步方法,但是都沒按照規範加上 Async 字尾,所以容易誤導。記得呼叫後要 Wait()或者 await
}
//程式正常退出的時候從 Consul 登出服務
////要通過方法引數注入 IHostApplicationLifetime
applicationLifetime.ApplicationStopped.Register(() =>
{
using (var consulClient = new ConsulClient(ConsulConfig))
{
Console.WriteLine($"應用退出,開始從consul登出{serveiceName}");
consulClient.Agent.ServiceDeregister(serviceId).Wait();
}
});
}
private void ConsulConfig(ConsulClientConfiguration configuration)
{
configuration.Address = new Uri("http://127.0.0.1:8500/");
configuration.Datacenter = "dc1";
}
啟動
我們生成下Api專案,用命令啟動
dotnet Consul.OrderService.dll --ip localhost --port 5000
開啟Consul看一下,已經可以看到OrderService
已經註冊好了。而且是有健康檢查的。
服務消費
我們建立一個控制檯程式,並安裝Consul
。
修改下Program.cs
class Program
{
static void Main(string[] args)
{
using (var consulClient = new ConsulClient(c =>
{
c.Address = new Uri("http://127.0.0.1:8500");
}))
{
var services = consulClient.Agent.Services().Result.Response;
foreach (var agentService in services.Values)
{
Console.WriteLine($"id={agentService.ID},name={agentService.Service},ip={agentService.Address},port={agentService.Port}");
}
}
Console.ReadKey();
}
}
由於剛才我們只開了一個OrderService
,現在我們多開啟幾個。
dotnet Consul.OrderService.dll --ip localhost --port 5001
dotnet Consul.OrderService.dll --ip localhost --port 5002
dotnet Consul.OrderService.dll --ip localhost --port 5003
我們可以看到4個服務都有健康檢查,並且Consul也可以看到。
我們啟動下控制檯應用程式
可以看到我們註冊的4個服務都是可以獲取到的,那麼我們隨便請求一個試一下。
static void Main(string[] args)
{
using (var consulClient = new ConsulClient(c =>
{
c.Address = new Uri("http://127.0.0.1:8500");
}))
{
var services = consulClient.Agent.Services().Result.Response.Values.Where(s => s.Service.Equals("OrderService", StringComparison.OrdinalIgnoreCase));
if (!services.Any())
{
Console.WriteLine("找不到服務的例項");
}
else
{
//隨機獲取
var service = services.ElementAt(Environment.TickCount % services.Count());
using (HttpClient client=new HttpClient())
{
var result= client.GetAsync(new Uri($"http://{service.Address}:{service.Port}/WeatherForecast")).Result;
Console.WriteLine(result.Content.ReadAsStringAsync().Result);
}
}
}
Console.ReadKey();
}