时代在变,技术也在更新迭代。从传统的单体应用架构到现在的分布式集群架构,在技术的学习上真的是一点都不能松懈。
网上关于微服务与Consul的话题太多了,我在这里不做过多描述。
其实就是在微服务中我们可以利用Consul可以实现服务的发现、治理、健康检查等...
用它先下载它:
我此番在windows下操作,打开下载的Consul所在文件夹,输入 consul.exe agent -dev
Consul的默认启动端口为8500,如果能正常显示页面则启动成功。
新建解决方案,建立一个.Net Core MVC的项目和一个.Net Core WebApi的项目。 安装NuGet包Consul
首先Api端服务实例启动时需到Consul中进行服务注册,Web Client直接与Consul进行连接,从Consul中拿到服务实例并配置策略及发送http请求等。
如图所示:
Consul每隔一段时间就会调用一次注册的服务实例进行健康检查。
在Api项目中新建一个IConfiguration的扩展方法:
public static void ConsulExtend(this IConfiguration configuration) { ConsulClient client = new ConsulClient(m => { m.Address = new Uri("http://localhost:8500/"); m.Datacenter = "dc1"; }); //启动的时候在consul中注册实例服务 //在consul中注册的ip,port string ip = configuration["ip"]; int port = int.Parse(configuration["port"]); int weight = string.IsNullOrWhiteSpace(configuration["weight"]) ? 1 : int.Parse(configuration["weight"]); client.Agent.ServiceRegister(new AgentServiceRegistration() { ID = "service" + Guid.NewGuid(),//唯一的 Name = "MicroserviceAttempt",//组(服务)名称 Address = ip, Port = port,//不同的端口=>不同的实例 Tags = new string[] { weight.ToString() },//标签 Check = new AgentServiceCheck()//服务健康检查 { Interval = TimeSpan.FromSeconds(12),//间隔12s一次 检查 HTTP = $"http://{ip}:{port}/Api/Health/Index", Timeout = TimeSpan.FromSeconds(5),//检测等待时间 DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(20)//失败后多久移除 } }); Console.WriteLine($"{ip}:{port}--weight:{weight}"); }
心跳检查的接口:
[ApiController] [Route("api/[controller]/[action]")] public class HealthController : Controller { readonly IConfiguration _configuration; public HealthController(IConfiguration configuration) { _configuration = configuration; } [HttpGet] public IActionResult Index() { //心跳,consul会每隔几秒调一次 Console.WriteLine($"{ _configuration["port"]} Invoke"); return Ok(); } }
在Startup类中的Configure方法加入:
//启动时注册,且注册一次 this.Configuration.ConsulExtend();
将api项目启动(三个端口)
dotnet ServicesInstances.dll --urls="http://*:5726" --ip="127.0.0.1" --port=5726 dotnet ServicesInstances.dll --urls="http://*:5727" --ip="127.0.0.1" --port=5727 dotnet ServicesInstances.dll --urls="http://*:5728" --ip="127.0.0.1" --port=5728
接着是web端,新建控制器:
public class UserController : Controller {readonly HttpSender _httpSender; public UserController(HttpSender httpSender) { _httpSender = httpSender; } //暂不考虑线程安全 private static int index = 0; public async Task<IActionResult> Index() { #region nginx版 只知道nginx地址就行了 //var str = await _httpSender.InvokeApi("http://localhost:8088/api/User/GetCustomerUser"); #endregion #region consul //new一个consul实例 ConsulClient client = new ConsulClient(m => { new Uri("http://localhost:8500/"); m.Datacenter = "dc1"; }); //与consul进行通信(连接),得到consul中所有的服务实例 var response = client.Agent.Services().Result.Response; string url = "http://MicroserviceAttempt/api/User/GetCustomerUser"; Uri uri = new Uri(url); string groupName = uri.Host; AgentService agentService = null;//服务实例 var serviceDictionary = response.Where(m => m.Value.Service.Equals(groupName, StringComparison.OrdinalIgnoreCase)).ToArray();//找到的全部服务实例 //{ // agentService = serviceDictionary[0].Value; //} { //轮询策略=>达到负载均衡的目的 agentService = serviceDictionary[index++ % 3].Value; } { //平均策略(随机获取索引--相对平均)=>达到负载均衡的目的 agentService = serviceDictionary[new Random(index++).Next(0, serviceDictionary.Length)].Value; } { //权重策略,给不同的实例分配不同的压力,注册时提供权重 List<KeyValuePair<string, AgentService>> keyValuePairs = new List<KeyValuePair<string, AgentService>>(); foreach (var item in keyValuePairs) { int count = int.Parse(item.Value.Tags?[0]);//在服务注册的时候给定权重数量 for (int i = 0; i < count; i++) { keyValuePairs.Add(item); } } agentService = keyValuePairs.ToArray()[new Random(index++).Next(0, keyValuePairs.Count())].Value; } url = $"{uri.Scheme}://{agentService.Address}:{agentService.Port}{uri.PathAndQuery}"; string content = await _httpSender.InvokeApi(url); #endregion return View(JsonConvert.DeserializeObject<CustomerUser>(content)); } }
public class HttpSender { public async Task<string> InvokeApi(string url) { using (HttpClient client = new HttpClient()) { HttpRequestMessage message = new HttpRequestMessage(); message.Method = HttpMethod.Get; message.RequestUri = new Uri(url); var result = client.SendAsync(message).Result; string content = result.Content.ReadAsStringAsync().Result; return content; } } }
启动web项目,访问User-Index这个视图,会轮询不同的服务实例。
但是这样做不好,客户端都需要和Consul进行连接,拿到所有的服务实例,直接和服务实例进行交互,服务实例就暴露了--所以需要网关。
网关将服务实例与客户端进行隔离,是所有Api请求的入口。因此可以统一鉴权。当然微服务网关的作用有很多,大家可自行百度了解。
新建一个网关的项目,请求先到达网关,再由网关分发请求到不同的实例。如图:
引入NuGet包:Ocelot、Ocelot.Provider.Consul
修改Startup类:
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.AddOcelot().AddConsul(); //services.AddControllers(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //将默认的请求管道全部丢掉 app.UseOcelot(); //if (env.IsDevelopment()) //{ // app.UseDeveloperExceptionPage(); //} //app.UseHttpsRedirection(); //app.UseRouting(); //app.UseAuthorization(); //app.UseEndpoints(endpoints => //{ // endpoints.MapControllers(); //}); } }
新建一个json文件,用于配置策略等...
{//*************************单地址多实例负载均衡+Consul***************************** "Routes": [ { //GeteWay转发=>Downstream "DownstreamPathTemplate": "/api/{url}", //服务地址--url变量 "DownstreamScheme": "http", //http://localhost:6299/T5/User/GetCustomerUser "UpstreamPathTemplate": "/T5/{url}", //网关地址--url变量 冲突的还可以加权重Priority "UpstreamHttpMethod": [ "Get", "Post" ], "UseServiceDiscovery": true, //使用服务发现 "ServiceName": "MicroserviceAttempt", //Consul服务名称 "LoadBalancerOptions": { "Type": "RoundRobin" //轮询 //"LeastConnection":最少连接数服务器 "NoloadBalance":不负载均衡 "CookieStickySession":会话粘滞 } } ], "GlobalConfiguration": { "BaseUrl": "http://127.0.0.1:6299", "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul"//由Consul提供服务发现,每次请求去Consul } //"ServiceDiscoveryProvider": { // "Host": "localhost", // "Port": 8500, // "Type": "PollConsul", //由Consul提供服务发现,每次请求去Consul // "PollingInterval": 1000//轮询Consul,评率毫秒--down是不知道的 //} } //*************************单地址多实例负载均衡+Consul***************************** }
启动网关(项目)服务:
dotnet GateWay-Ocelot.dll --urls="http://*:6299" --ip="127.0.0.1" --port=6299
调用服务接口:
每请求一次就轮询不同的服务实例,达到负载均衡。
到此就结尾了。如有不当的地方,请谅解,希望能帮到大家。
代码已上传至我的github: