今天,总结一下之前学习的Consul基础(后续如果有时间的话,再加上Ocelot部分):
新建一个WebApi项目:模拟服务
创建一个Web应用程序:模拟客户端
下载好Consul客户端
准备工作做好了,开始撸码
首先,在两个项目中分别引入Consul包
第二步:准备一个测试接口(UserController)和一个健康检查的接口(HealthController)
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Tnblog.MicroService.WebApiDemo.Controllers { /// <summary> /// 用户信息服务 /// </summary> [Route("api/[controller]/[action]")] [ApiController] public class UserController : ControllerBase { /// <summary> /// AllowAnonymous:允许匿名访问接口 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet] [AllowAnonymous] public Users Get() { Users user = new Users() {UserName="Asa",Email="12345@qq.com"}; // 允许跨域(这一句不需要) base.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*"); return user; } } public class Users { public int UserID { get; set; } public string UserName { get; set; } public string Email { get; set; } } }
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Asa.AspNetCore31WebApiDemo.Controllers { /// <summary> /// (服务)心跳检查/健康检查 /// </summary> [Route("api/[controller]")] [ApiController] public class HealthController : ControllerBase { private readonly ILogger<HealthController> _logger; private readonly IConfiguration _iConfiguration; public HealthController(ILogger<HealthController> logger, IConfiguration configuration) { _logger = logger; this._iConfiguration = configuration; } /// <summary> /// 用于做健康检查的服务 /// [Route("Index")]:拼接到控制器上的route /// </summary> /// <returns></returns> [HttpGet] [Route("Index")] public IActionResult Index() { this._logger.LogWarning($"This is HealthController {this._iConfiguration["Port"]}"); //HttpStatusCode--200:代表调用成功,服务正常运行 return Ok(); } } }
8.第三步:准备一个服务注册帮助类
using Consul; using Microsoft.Extensions.Configuration; using System; namespace Asa.AspNetCore31WebApiDemo.Utility { /// <summary> /// 自己封装的Consul服务注册类 /// </summary> public static class ConsulHelper { /// <summary> /// Consul服务注册 /// </summary> /// <param name="configuration"></param> public static void ConsulRegist(this IConfiguration configuration) { #region 获取Consul客户端 // 获取Consul客户端:用于将服务注册进Consul ConsulClient client = new ConsulClient(c => { // Consul客户端地址 c.Address = new Uri("http://localhost:8500/"); // 数据中心 c.Datacenter = "dc1"; }); #endregion #region 获取命令行参数 // Program-Main中配置了AddCommandLine就能支持命令行获取 // 服务实例IP string ip = configuration["ip"]; // 服务实例端口号:命令行参数必须传入 int port = int.Parse(configuration["port"]); // 命令行参数必须传入:权重模式(根据配置的权重来随机调用服务实例) int weight = string.IsNullOrWhiteSpace(configuration["weight"]) ? 1 : int.Parse(configuration["weight"]); #endregion #region 注册服务 // 注册服务 client.Agent.ServiceRegister(new AgentServiceRegistration() { // 在consul中服务的id:唯一的 ID = "service" + Guid.NewGuid(), // 一组服务的组名称 Name = "AsaUserService", // 其实应该写ip地址 Address = ip, // 不同实例:例如(8085/8086) Port = port, // 标签:权重模式使用 Tags = new string[] { weight.ToString() }, #region 健康检查 // 配置心跳检查的 Check = new AgentServiceCheck() { // 每隔12秒检查一次 Interval = TimeSpan.FromSeconds(12), // 心跳检查的IP地址 HTTP = $"http://{ip}:{port}/Api/Health/Index", // 超时时间:如果五秒内服务实例没有响应,那么就判定为服务实例已经挂掉 // 测试后发现这个设置延迟生效:实际时间不止五秒 Timeout = TimeSpan.FromSeconds(5), // 在多久之后去除注册 DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5) } #endregion }); #endregion Console.WriteLine($"http://{ip}:{port}完成注册"); } } }
9.第四步:配置服务注册,在服务启动时注册
10.第五步:在客户端添加一个用于测试的控制器(TestController)
using Asa.AspNetCore31.Demo.Utility.WebApiHelper; using Consul; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; namespace Tnblog.MicroService.ClientDemo.Controllers { public class TestController : Controller { // (随机标识)没考虑溢出问题,到达一定长度应该重置 private static int iSeed = 0; /// <summary> /// 模拟客户端调用用户服务 /// </summary> /// <returns></returns> public IActionResult Index() { Users user = new Users(); string resultUrl = null; #region 通过Consul去发现所有服务地址 { // 指定要调用的服务实例:用Consul服务组名称替换IP和端口号:AsaUserService:服务实例注册时指定的组名称 //string url = "http://localhost:5728/api/user/get"; string url = "http://AsaUserService/api/user/get"; Uri uri = new Uri(url); // 获取服务实例组名称 string groupName = uri.Host; // 获取Consul客户端 using (ConsulClient client = new ConsulClient(c => { // Consul地址:8500 端口基于 HTTP 协议,用于 API 接口或 WEB UI 访问 c.Address = new Uri("http://localhost:8500/"); // 数据中心:默认名称为dc1 c.Datacenter = "dc1"; })) { // 获取所有注册的服务实例 var dictionary = client.Agent.Services().Result.Response; // 根据组名称筛选服务实例:得到名称为AsaUserService的一组服务 var list = dictionary.Where(k => k.Value.Service.Equals(groupName, StringComparison.OrdinalIgnoreCase)); KeyValuePair<string, AgentService> keyValuePair = new KeyValuePair<string, AgentService>(); #region 负载策略 //{ // // 获取第一个服务实例 // keyValuePair = list.First(); // //url = url.Replace(groupName, $"{keyValuePair.Value.Address}:{keyValuePair.Value.Port}"); //} //// 随机策略/平均策略 //{ // var array = list.ToArray(); // // 随机策略/平均策略 // keyValuePair = array[new Random(iSeed++).Next(0, array.Length)]; //} //// 轮巡策略 / 平均策略 //{ // var array = list.ToArray(); // keyValuePair = array[iSeed++ % array.Length];// 取余数:就能按照0 1 2 的顺序调用 //} // 权重模式 { List<KeyValuePair<string, AgentService>> serviceList = new List<KeyValuePair<string, AgentService>>(); foreach (KeyValuePair<string, AgentService> agentService in list) { int count = int.Parse(agentService.Value.Tags[0]); for (int i = 0; i < count; i++) { serviceList.Add(agentService); } } keyValuePair = serviceList[new Random(iSeed++).Next(0, serviceList.Count())]; } #endregion resultUrl = $"{uri.Scheme}://{keyValuePair.Value.Address}:{keyValuePair.Value.Port}{uri.PathAndQuery}"; // 调用服务实例中的接口(WebApiHelperExtend:接口调用拓展类) string result = WebApiHelperExtend.InvokeApi(resultUrl); user = Newtonsoft.Json.JsonConvert.DeserializeObject<Users>(result); } } #endregion this.ViewBag.User = user; this.ViewBag.Url = resultUrl; return View(); } } /// <summary> /// 用于测试 /// </summary> public class Users { public int UserID { get; set; } public string UserName { get; set; } public string Email { get; set; } } }
11.第六步:准备一个接口调用帮助类
using System; using System.Net.Http; namespace Asa.AspNetCore31.Demo.Utility.WebApiHelper { /// <summary> /// 接口调用帮助类 /// </summary> public static class WebApiHelperExtend { /// <summary> /// 模拟调用接口 /// </summary> /// <param name="url"></param> /// <returns></returns> public static string InvokeApi(string url) { // 提供基类,用于从由URI标识的资源发送HTTP请求和接收HTTP响应。 using (HttpClient httpClient = new HttpClient()) { // Http请求消息 HttpRequestMessage message = new HttpRequestMessage(); // 请求方式 message.Method = HttpMethod.Get; // 请求地址 message.RequestUri = new Uri(url); // 以异步操作的形式发送HTTP请求。并接收响应信息 var result = httpClient.SendAsync(message).Result; // 将HTTP内容序列化为字符串 string content = result.Content.ReadAsStringAsync().Result; return content; } } } }
12.第七步:为TestController添加对应视图,用于观察负载的效果
@{ ViewData["Title"] = "Index"; } <h1>Index</h1> <h3>@this.ViewBag.User.UserName</h3> <h3>@this.ViewBag.Url</h3>
13.第八步:由于我是使用的命令提示符窗口运行,所以要配置为支持从命令行读取配置信息
14.找到Consul所在的文件夹,以cmd的方式打开:
使用命令启动Consul:consul_1.6.2.exe agent –dev
启动后在浏览器使用以下地址打开:
效果如下:这时只有一个默认的Service
15.打开服务实例项目所在的地址,同样以cmd方式打开,然后用命令启动:
启动命令:dotnet Asa.AspNetCore31WebApiDemo.dll --urls="http://*:5726" --ip="127.0.0.1" --port=5726 --weight=7
想要观察多个服务实例负载的效果的话,这里可以多用cmd启动几个窗口,指定不同的端口号,例如:
dotnet Asa.AspNetCore31WebApiDemo.dll --urls="http://*:5200" --ip="127.0.0.1" --port=5200 --weight=5
dotnet Asa.AspNetCore31WebApiDemo.dll --urls="http://*:5201" --ip="127.0.0.1" --port=5201 --weight=3
dotnet Asa.AspNetCore31WebApiDemo.dll --urls="http://*:5202" --ip="127.0.0.1" --port=5202 --weight=2
最后,运行客户端测试,效果如下: