Ocelot+Naco搭建的.net core 微服务下封装通用的服务之间的调用中间件
在 Ocelot + Nacos 搭建的 .NET Core 微服务中,可以封装一个通用的服务调用中间件,该中间件可以使用 Ocelot 作为 API 网关路由微服务请求,并通过 Nacos 服务发现来实现微服务的动态调用。
构建中间件ServiceProxyMiddleware
以下是一个构建通用服务调用中间件的示例代码:
public class ServiceProxyMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; private readonly IServiceDiscoveryProvider _serviceDiscoveryProvider; public ServiceProxyMiddleware(RequestDelegate next, IConfiguration configuration, IServiceDiscoveryProvider serviceDiscoveryProvider) { _next = next; _configuration = configuration; _serviceDiscoveryProvider = serviceDiscoveryProvider; } public async Task InvokeAsync(HttpContext context) { // 获取请求服务名称 var serviceName = context.Request.Headers["ServiceName"]; if (string.IsNullOrEmpty(serviceName)) { // 如果请求中没有ServiceName头,则直接跳过 await _next(context); return; } // 获取服务实例地址 var serviceInstances = await _serviceDiscoveryProvider.GetInstancesAsync(serviceName); // 轮询方式选择一个服务实例 string serviceInstance = null; if (serviceInstances != null && serviceInstances.Count() > 0) { serviceInstance = serviceInstances.RandomOrDefault()?.ServiceUrl; } if (string.IsNullOrEmpty(serviceInstance)) { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; return; } // 转发请求到微服务 await ForwardRequestToServiceAsync(context, serviceInstance); await _next(context); } private async Task ForwardRequestToServiceAsync(HttpContext context, string serviceInstance) { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(serviceInstance); var requestMessage = new HttpRequestMessage(); requestMessage.Method = new HttpMethod(context.Request.Method); requestMessage.RequestUri = new Uri(httpClient.BaseAddress + context.Request.Path); requestMessage.Content = new StreamContent(context.Request.Body); if (context.Request.Headers != null) { foreach (var h in context.Request.Headers) { requestMessage.Headers.TryAddWithoutValidation(h.Key, h.Value.ToArray()); } } HttpResponseMessage response = await httpClient.SendAsync(requestMessage); // 将微服务的响应直接返回 context.Response.StatusCode = (int)response.StatusCode; foreach (var header in response.Headers) { context.Response.Headers[header.Key] = header.Value.ToArray(); } var stream = await response.Content.ReadAsStreamAsync(); await stream.CopyToAsync(context.Response.Body); } } }
在上述代码中,ServiceProxyMiddleware
是微服务调用中间件的主体。当请求到达 API 网关时,该中间件会从传入请求的头部信息中获取服务名称,并使用 IServiceDiscoveryProvider
接口封装的服务发现功能获取服务的实例地址,并使用 HttpClient
将请求转发到微服务的实例地址,最后将微服务的响应直接返回到 API 网关。
我们还可以通过创建一个包含 IServiceDiscoveryProvider
接口的默认实现的服务集合,将 ServiceProxyMiddleware
用于微服务调用中间件的引用注入到程序的服务容器中。这样,在需要发出调用的应用程序中,可以轻松地使用相同的控件调用通用的服务调用中间件。
注入服务
例如,在 ConfigureServices
方法中,我们可以注册该服务:
public void ConfigureServices(IServiceCollection services) { // 注入Nacos服务发现功能 services.AddSingleton<IServiceDiscoveryProvider, NacosServiceDiscoveryProvider>(sp => { var configuration = sp.GetService<IConfiguration>(); var options = new NacosDiscoveryOptions(); configuration.GetSection("Nacos").Bind(options); return new NacosServiceDiscoveryProvider(options); }); // 注入服务调用中间件 services.AddSingleton<ServiceProxyMiddleware>(); }
最后,我们将 ServiceProxyMiddleware
用于微服务调用中间件的引用注入到程序的服务容器中,并在 Configure
方法中添加以下代码:
使用中间件
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 省略代码... // 使用服务调用中间件 app.UseMiddleware<ServiceProxyMiddleware>(); }
这样,在需要连接到任何微服务的应用程序中。
调用中间件
在需要连接到任何微服务的应用程序中,可以通过以下代码来调用通用服务调用中间件:
public class MyService { private readonly HttpClient _httpClient; public MyService(HttpClient httpClient) { _httpClient = httpClient; } // 向指定微服务发起请求,其中 serviceName 是服务名称、apiPath 是 API 的地址,返回响应消息体 string 类型 public async Task<string> GetMicroserviceResponseAsync(string serviceName, string apiPath) { var request = new HttpRequestMessage(HttpMethod.Get, apiPath); request.Headers.Add("ServiceName", serviceName); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } }
IServiceDiscoveryProvider接口的实现
IServiceDiscoveryProvider
接口定义了从服务发现源中获取微服务实例的方法。在 Ocelot + Nacos 架构中,我们可以创建一个接口实现,用于从 Nacos 服务注册中心中获取服务实例。
以下是一个基于 Nacos 的 IServiceDiscoveryProvider
接口实现:
public class NacosServiceDiscoveryProvider : IServiceDiscoveryProvider { private readonly INacosNamingClient _namingClient; public NacosServiceDiscoveryProvider(NacosDiscoveryOptions options) { _namingClient = new NacosNamingClient(Options.Create(options)); } public async Task<List<ServiceInstance>> GetInstancesAsync(string serviceName) { // 从 Nacos 服务注册中心获取指定服务名称的所有实例 var instances = await _namingClient.ListInstances(serviceName); // 将 Nacos 服务注册中心返回的实例数据映射为 ServiceInstance 实体 var serviceInstances = new List<ServiceInstance>(); if (instances != null) { foreach (var instance in instances) { var serviceInstance = new ServiceInstance { ServiceName = serviceName, ServiceUrl = instance.ToUrlString() }; serviceInstances.Add(serviceInstance); } } return serviceInstances; } }
在上述代码中,NacosServiceDiscoveryProvider
是接口 IServiceDiscoveryProvider
的实现。在构造函数中,我们使用 NacosDiscoveryOptions
配置项初始化 NacosNamingClient,并将其保存为类成员变量。在 GetInstancesAsync
方法中,我们使用 _namingClient.ListInstances
方法从 Nacos 服务注册中心获取特定服务名称的所有实例,然后将实例数据转换为 ServiceInstance
实体,并使用实体列表封装的所有实例返回结果。
在上述代码中,ServiceInstance
是一个映射到服务实例的实体类,可以根据自己的需要自定义它的属性和逻辑。
以下是 ServiceInstance
类的一个示例定义:
public class ServiceInstance { public string ServiceName { get; set; } public string ServiceUrl { get; set; } }
以上是基于 Nacos 的 IServiceDiscoveryProvider
接口实现,它可以从 Nacos 服务注册中心中获取服务实例,并在需要的时候使用这些实例作为目标微服务的地址。