服务发现 Consul
一、安装和启动
下载 [Consul](https://www.consul.io/downloads.html)
下载完成后,解压,只有一个consul.exe,把目录添加到环境变量的PATH,注意添加到系统变量,仅仅加入用户变量不起作用。打开cmd,输入
consul agen -dev // 启动Consul服务
二、在aspnetcore中注册Consul
1. 定义配置项
/// <summary>
/// Consul配置
/// </summary>
public class ConsulOptions
{
/// <summary>
/// Consul服务器的地址
/// </summary>
public string HttpEndPoint { get; set; }
/// <summary>
/// 数据中心名称
/// </summary>
public string DataCenter { get; set; }
/// <summary>
/// Dns服务器端点
/// </summary>
public DnsEndPoint DnsEndPoint { get; set; }
}
/// <summary>
/// Dns节点
/// </summary>
public class DnsEndPoint
{
/// <summary>
/// 服务器地址
/// </summary>
public string Address { get; set; }
/// <summary>
/// 端口
/// </summary>
public int Port { get; set; }
/// <summary>
/// 转换为IpEndPoint
/// </summary>
/// <returns></returns>
public IPEndPoint ToIpEndPoint()
{
return new IPEndPoint(IPAddress.Parse(Address), Port);
}
}
public class ServiceDiscoveryOptions
{
/// <summary>
/// 服务名称
/// </summary>
public string ServiceName { get; set; }
/// <summary>
/// Consul配置
/// </summary>
public ConsulOptions Consul { get; set; }
}
2. 在appsettings.json中添加配置
"ServiceDiscovery": {
"ServiceName": "webapi1",
"Consul": {
"DataCenter": "dc1",
"HttpEndPoint": "http://127.0.0.1:8500",
"DnsEndPoint": {
"Address": "127.0.0.1",
"Port": 8600
}
}
}
3. 在startup中注册配置项
services.Configure<ServiceDiscoveryOptions>(
Configuration.GetSection("ServiceDiscovery"));
4. 注册IConsulClient服务
services.AddSingleton<IConsulClient>(new ConsulClient(config =>
{
config.Address = new Uri(Configuration["ServiceDiscovery:Consul:HttpEndPoint"]);
config.Datacenter = Configuration["ServiceDiscovery:Consul:DataCenter"];
}));
5. 在Configure中将自身注册到Consul服务
ILifeTime的ApplicationStarted,程序启动时注册到Consul,
使用IApplicationBuilder的Feature获取当前启动的ip和端口,作为服务的ID
public static class ApplicationBuilderExtensions
{
/// <summary>
/// 获取当前启动应用绑定的IP和端口
/// </summary>
public static IEnumerable<Uri> GetAddress(this IApplicationBuilder app)
{
var features = app.Properties["server.Features"] as FeatureCollection;
return features?.Get<IServerAddressesFeature>()
.Addresses.Select(p => new Uri(p)) ?? new List<Uri>();
}
}
public void Configure(
IApplicationBuilder app, IHostingEnvironment env,
IApplicationLifetime lifetime, IConsulClient consul,
IOptions<ServiceDiscoveryOptions> serviceDiscoveryOptions)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
lifetime.ApplicationStarted.Register(() =>
{
var addresses = app.GetAddress();
foreach (var address in addresses)
{
var serviceId =
$"{serviceDiscoveryOptions.Value.ServiceName}_{address.Host}:{address.Port}";
consul.Agent.ServiceRegister(new AgentServiceRegistration
{
ID = serviceId,
Name = serviceDiscoveryOptions.Value.ServiceName,
Address = address.Host,
Port = address.Port,
Check = new AgentServiceCheck
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),
Interval = TimeSpan.FromSeconds(5),
HTTP = new Uri(address, "api/Health/Check").OriginalString,
Timeout = TimeSpan.FromSeconds(5)
}
}).Wait();
lifetime.ApplicationStopped.Register(() =>
{
consul.Agent.ServiceDeregister(serviceId).Wait();
});
}
});
app.UseAuthentication();
app.UseMvc();
}
三、在项目中使用Consul
1、 定义一个接口
public interface IServiceDiscoveryManager
{
string GetApi(string serviceName);
}
2、 实现一个Consul的服务发现工具
public class ConsulServiceDiscoveryManager : IServiceDiscoveryManager
{
private readonly IConsulClient _client;
private readonly ILogger<ConsulServiceDiscoveryManager> _logger;
// 手动高亮,忽略大小写,这个很重要
private readonly ConcurrentDictionary<string, List<string>> _dict =
new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
private DateTime _syncTime = DateTime.Now.AddMinutes(-11);
private static object _lock = new object();
public ConsulServiceDiscoveryManager(IConsulClient client, ILogger<ConsulServiceDiscoveryManager> logger)
{
_client = client;
_logger = logger;
}
private ConcurrentDictionary<string, List<string>> GetAllServices()
{
if (_syncTime < DateTime.Now.AddSeconds(-10) || !_dict.Keys.Any())
{
_dict.Clear();
var services = _client.Agent.Services().Result.Response.Select(s => s.Value).GroupBy(s => s.Service);
foreach (var service in services)
{
_dict.TryAdd(service.Key, service.Select(s => $"{s.Address}:{s.Port}").ToList());
}
_syncTime = DateTime.Now;
}
return _dict;
}
public List<string> GetApis(string serviceName)
{
if(!GetAllServices().TryGetValue(serviceName, out var hosts))
{
return new List<string>();
}
return hosts;
}
public string GetApi(string serviceName)
{
var hosts = GetApis(serviceName);
var count = hosts.Count();
if(count == 0)
{
return string.Empty;
}
else if(count == 1)
{
return hosts.First();
}
var ran = new Random().Next(0, count);
return hosts.ElementAt(ran);
}
}
3、 在ConfigureServices中注册IServiceDiscoveryManager。
捎带把 IHttpConlientFactory 也注册掉,
注意在 appsettings.json 里把之前的 ServiceDiscoverty 的配置项添加进去,
也可以只添加Consul的部分
services.AddSingleton<IConsulClient>(new ConsulClient(config =>
{
config.Address = new Uri(Configuration["ServiceDiscovery:Consul:HttpEndPoint"]);
config.Datacenter = Configuration["ServiceDiscovery:Consul:DataCenter"];
}));
services.AddSingleton<IServiceDiscoveryManager, ConsulServiceDiscoveryManager>();
services.AddHttpClient();
4、 在Controller中使用,也可以封装一个Client,后面可以直接用。
public class TestController : Controller
{
private readonly ILogger<TestController> _logger;
private readonly IHttpClientFactory _clientFactory;
private readonly IServiceDiscoveryManager _serviceDiscoveryManager;
public TestController(ILogger<TestController> logger,
IConsulClient consul,
IHttpClientFactory clientFactory,
IServiceDiscoveryManager serviceDiscoveryManager)
{
_logger = logger;
_clientFactory = clientFactory;
_serviceDiscoveryManager = serviceDiscoveryManager;
}
public IActionResult Index()
{
var apiHost = _serviceDiscoveryManager.GetApi("WebApi1");
using(var client = _clientFactory.CreateClient())
{
var result = client.GetAsync($"http://{apiHost}/api/values").Result;
return Content(result.Content.ReadAsStringAsync().Result);
}
}
}
Ocelot 集成 Consul
一、 引入Nuget包
Ocelot
Ocelot.Provider.Consul
二、 在根目录下创建ocelog.json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"UseServiceDiscovery": true,
"ServiceName": "WebApi1",
"LoadBalancerOptions": {
"Type": "RoundRobin" //轮询
},
"UpstreamPathTemplate": "/WebApi1/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://127.0.0.1:5010",
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul"
}
}
}
三、 修改Program.cs,把刚刚创建好的ocelot.json添加到配置项
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(configuration =>
{
configuration.AddJsonFile("ocelot.json");
})
.UseUrls("http://localhost:5010")
.UseStartup<Startup>();
四、 修改Startup,注册服务,使用ocelot的中间件
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot().AddConsul();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseOcelot().Wait();
}
五、 一个比较全的配置项说明
{
"ReRoutes": [
{//官方文档ReRoutes全节点示例
//Upstream表示上游请求,即客户端请求到API Gateway的请求
"UpstreamPathTemplate": "/", //请求路径模板
"UpstreamHttpMethod": [ //请求方法数组
"Get"
],
//Downstreamb表示下游请求,即API Gateway转发的目标服务地址
"DownstreamScheme": "http", //请求协议,目前应该是支持http和https
"DownstreamHost": "localhost", //请求服务地址,应该是可以是IP及域名
"DownstreamPort": 51779, //端口号
"DownstreamPathTemplate": "/", //下游请求地址模板
"RouteClaimsRequirement": { //标记该路由是否需要认证
"UserType": "registered" //示例,K/V形式,授权声明,授权token中会包含一些claim,如填写则会判断是否和token中的一致,不一致则不准访问
},
//以下三个是将access claims转为用户的Header Claims,QueryString,该功能只有认证后可用
"AddHeadersToRequest": { //
"UserType": "Claims[sub] > value[0] > |", //示例
"UserId": "Claims[sub] > value[1] > |"//示例
},
"AddClaimsToRequest": {},
"AddQueriesToRequest": {},
"RequestIdKey": "", //设置客户端的请求标识key,此key在请求header中,会转发到下游请求中
"FileCacheOptions": { //缓存设置
"TtlSeconds": 15, //ttl秒被设置为15,这意味着缓存将在15秒后过期。
"Region": "" //缓存region ,可以使用administrator API清除
},
"ReRouteIsCaseSensitive": false, //路由是否匹配大小写
"ServiceName": "", //服务名称,服务发现时必填
"QoSOptions": { //断路器配置,目前Ocelot使用的Polly
"ExceptionsAllowedBeforeBreaking": 0, //打开断路器之前允许的例外数量。
"DurationOfBreak": 0, //断路器复位之前,打开的时间(毫秒)
"TimeoutValue": 0 //请求超时时间(毫秒)
},
"LoadBalancerOptions": {
"type": "RoundRobin" // 负载均衡 RoundRobin(轮询)/LeastConnection(最少连接数)
},
"RateLimitOptions": { //官方文档未说明
"ClientWhitelist": [], // 白名单
"EnableRateLimiting": false, // 是否限流
"Period": "1m", // 1s,4m,1h,1d
"PeriodTimespan": 0, // 多少秒之后客户端可以充实
"Limit": 0 // 一个时间周期最多可以请求的次数
},
"AuthenticationOptions": { //认证配置
"AuthenticationProviderKey": "", //这个key对应的是代码中.AddJWTBreark中的Key
"AllowedScopes": []//使用范围
},
"HttpHandlerOptions": {
"AllowAutoRedirect": true, //指示请求是否应该遵循重定向响应。 如果请求应该自动遵循来自Downstream资源的重定向响应,则将其设置为true; 否则为假。 默认值是true。
"UseCookieContainer": true //该值指示处理程序是否使用CookieContainer属性来存储服务器Cookie,并在发送请求时使用这些Cookie。 默认值是true。
},
"UseServiceDiscovery": false //使用服务发现,目前Ocelot只支持Consul的服务发现
}
],
"GlobalConfiguration": {}
}
六、 再来一个简单的不使用Consul的配置项
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
},
{
"Host": "localhost",
"Port": 5002
}
],
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"UpstreamPathTemplate": "/UserService/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ]
}
]
}
七、熔断
引入Nuget包
Ocelot.Provider.Polly