
今天,总结一下之前学习的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
最后,运行客户端测试,效果如下:

