.NET Core Consul服务注册一文中讲述了安装Consul,注册服务。
本文讲述在.NET Core项目中的Consul服务发现。和注册一样,为了复用还是新建一个类库。
首先我们还是启动3个AspNetCore服务,注册到Consul,如何注册可以看.NET Core Consul服务注册一文中的介绍。
启动好以后在Consul的监视界面localhost:8500中可以看到3个服务了,如图所示
接下来开始在ConsulServiceDiscovery类库项目中编写代码。
服务提供IServiceProvider接口和ConsulServiceProvider类代码
namespace ConsulServiceDiscovery
{
public interface IServiceProvider
{
Task<IList<string>> GetServicesAsync(string serviceName);
}
}
namespace ConsulServiceDiscovery
{
public class ConsulServiceProvider : IServiceProvider
{
private readonly ConsulClient _consulClient;
public ConsulServiceProvider(Uri uri) {
_consulClient = new ConsulClient(consulConfig =>
{
consulConfig.Address = uri;
});
}
public async Task<IList<string>> GetServicesAsync(string serviceName)
{
// Health 当前consul里已注册的服务,健康检查的信息也拿过来
var queryResult = await _consulClient.Health.Service(serviceName, string.Empty, true);
var result = new List<string>();
foreach (var item in queryResult.Response) {
result.Add($"{item.Service.Address}:{item.Service.Port}");
}
return result;
}
}
}
服务创建IServiceBuilder接口和ServiceBuilder类代码
namespace ConsulServiceDiscovery.Builder
{
public interface IServiceBuilder
{
/// <summary>
/// 服务提供者
/// </summary>
IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// 服务名称
/// </summary>
string ServiceName { get; set; }
/// <summary>
/// Uri方案
/// </summary>
string UriScheme { get; set; }
/// <summary>
/// 使用哪种策略
/// </summary>
ILoadBalancer LoadBalancer { get; set; }
Task<Uri> BuildAsync(string path);
}
}
namespace ConsulServiceDiscovery.Builder
{
public class ServiceBuilder : IServiceBuilder
{
public IServiceProvider ServiceProvider { get; set; }
public string ServiceName { get; set; }
public string UriScheme { get; set; }
public ILoadBalancer LoadBalancer { get; set; }
public ServiceBuilder(IServiceProvider serviceProvider) {
ServiceProvider = serviceProvider;
}
public async Task<Uri> BuildAsync(string path)
{
var serviceList = await ServiceProvider.GetServicesAsync(ServiceName);
var service = LoadBalancer.Resolve(serviceList);
var baseUri = new Uri($"{UriScheme}://{service}");
var uri = new Uri(baseUri, path);
return uri;
}
}
}
负载方式,这里列举随机和轮询两种负载方式,ILoadBalancer接口,RandomLoadBalancer实现类,RoundRobinLoadBalancer实现类,TypeLoadBalancer静态类代码
namespace ConsulServiceDiscovery.LoadBalancer
{
public interface ILoadBalancer
{
string Resolve(IList<string> services);
}
}
namespace ConsulServiceDiscovery.LoadBalancer
{
public class RandomLoadBalancer : ILoadBalancer
{
private readonly Random _random = new Random();
public string Resolve(IList<string> services)
{
var index = _random.Next(services.Count);
return services[index];
}
}
}
namespace ConsulServiceDiscovery.LoadBalancer
{
public class RoundRobinLoadBalancer : ILoadBalancer
{
private readonly object _lock = new object();
private int _index = 0;
public string Resolve(IList<string> services)
{
lock (_lock) {
if (_index >= services.Count) {
_index = 0;
}
return services[_index++];
}
}
}
}
namespace ConsulServiceDiscovery.LoadBalancer
{
public static class TypeLoadBalancer
{
// 单例
public static ILoadBalancer RandomLoad = new RandomLoadBalancer();
public static ILoadBalancer RoundRobinLoad = new RoundRobinLoadBalancer();
}
}
扩展方法创建ServiceBuilder
namespace ConsulServiceDiscovery
{
public static class ServiceProviderExtension
{
public static IServiceBuilder CreateServiceBuilder(this IServiceProvider serviceProvider, Action<IServiceBuilder> config) {
var builder = new ServiceBuilder(serviceProvider);
config(builder);
return builder;
}
}
}
这样,服务发现的代码就完成了。
下面是调用测试代码,创建一个控制台项目,在Program.cs里使用轮询的方式代码如下
namespace ConsulTest
{
class Program
{
static void Main(string[] args)
{
var serviceProvider = new ConsulServiceProvider(new Uri("http://localhost:8500"));
var aspNetCoreService = serviceProvider.CreateServiceBuilder(builder => {
builder.ServiceName = "AspNetCore";
builder.LoadBalancer = TypeLoadBalancer.RoundRobinLoad;//可选RandomLoad
builder.UriScheme = Uri.UriSchemeHttp;
});
var httpClient = new HttpClient();
for (int i = 0; i < 100; i++) {
Console.WriteLine($"----------第{i}次请求-----------");
try
{
var uri = aspNetCoreService.BuildAsync("/Health").Result;
Console.WriteLine($"{DateTime.Now} - 正在调用:{uri}");
var content = httpClient.GetStringAsync(uri).Result;
Console.WriteLine($"返回结果:{content}");
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
Task.Delay(500).Wait();
}
Console.ReadLine();
}
}
}
执行效果
可以看到,轮询方式会轮流请求1234,1235,1236端口的三个服务地址。