查看原文
其他

.NET Core微服务一条龙应用:网关使用与配置

DotNet 2019-08-03

(给DotNet加星标,提升.Net技能


转自:天翔者

cnblogs.com/tianxiangzhe/p/10336923.html


简介


微服务的系统应用中,网关系统使用的是ocelot,ocelot目前已经比较成熟了


ocelot就不做介绍了,等整体介绍完后再进行各类扩展介绍,ocelot源码地址:https://github.com/ThreeMammals/Ocelot。


ocelot目前由很多功能组件组成,每个组件都可以根据自己的实际情况进行扩展(暂时不做过多介绍)。


本文主要介绍ocelot网关使用中个人认为应该最先处理的东西。


健康检查


在实际的应用中网关项目都会部署多台,然后通过nginx进行软负载,在更新部署网关项目的过程中服务肯定是无法使用,这个时候我们就需要利用nginx的健康检查机制进行控制。


网关需要给nginx提供一个健康检查地址,ocelot使用的url path地址进行路由匹配,当匹配不到时会返回404,所以我们需要单独处理一个健康检查地址


Ocelot提供了一个中间件配置替换的方法OcelotPipelineConfiguration,我们对OcelotPipelineConfiguration的PreErrorResponderMiddleware中间件方法进行扩展,代码如下:


var conf = new OcelotPipelineConfiguration(){ PreErrorResponderMiddleware = async (ctx, next) => { if (ctx.HttpContext.Request.Path.Equals(new PathString("/"))) { await ctx.HttpContext.Response.WriteAsync("ok"); } else { await next.Invoke(); } }};


网关和路由配置


网关的配置包含四个部分,ReRoutes、DynamicReRoutes、Aggregates、GlobalConfiguration,


ocelot配置的获取默认是使用配置文件的方式,上面已经说了网关一般都会部署多台,使用文件配置还是存在一定弊端


ocelot的配置获取方法是IFileConfigurationRepository接口,所以如果我们实现了此接口就可以满足配置存储方式的扩展,目前已扩展mysql和redis,代码如下


Redis:


public class RedisFileConfigurationRepository: IFileConfigurationRepository{ private readonly RedisClient _redisClient; private readonly string _apiGatewayKey;    private readonly string _redisConnection; public RedisFileConfigurationRepository(RedisClient redisClient, string apiGatewayKey, string redisConnection) { _redisClient = redisClient; _apiGatewayKey = apiGatewayKey; _redisConnection = redisConnection;    } public async Task<Response<FileConfiguration>> Get() {        var redis = _redisClient.GetDatabase(_redisConnection, 11);        var json = await redis.StringGetAsync($"ApiGatewayConfig:{_apiGatewayKey}"); if(json.IsNullOrEmpty)            return new OkResponse<FileConfiguration>(new FileConfiguration { });        var fileConfig = JsonConvert.DeserializeObject<FileConfiguration>(json); return new OkResponse<FileConfiguration>(fileConfig);    } public async Task<Response> Set(FileConfiguration fileConfiguration) { return await Task.FromResult(new OkResponse()); }}


MySQL:


public class MySqlFileConfigurationRepository : IFileConfigurationRepository{ private readonly IDbRepository<ConfigurationInfo> _configDbRepository; private readonly IDbRepository<ReRouteInfo> _routeDbRepository;    private readonly string _apiGatewayKey; public MySqlFileConfigurationRepository(IDbRepository<ConfigurationInfo> configDbRepository, IDbRepository<ReRouteInfo> routeDbRepository, string apiGatewayKey) { _configDbRepository = configDbRepository; _routeDbRepository = routeDbRepository; _apiGatewayKey = apiGatewayKey; }
public async Task<Response<FileConfiguration>> Get() { var st = DateTime.Now; var fileConfig = new FileConfiguration(); var configInfo = await _configDbRepository.GetFirstAsync(it => it.GatewayKey == _apiGatewayKey); if (configInfo != null) { // config var fgc = new FileGlobalConfiguration { BaseUrl = configInfo.BaseUrl, DownstreamScheme = configInfo.DownstreamScheme, RequestIdKey = configInfo.RequestIdKey, }; if (!string.IsNullOrWhiteSpace(configInfo.HttpHandlerOptions)) fgc.HttpHandlerOptions = ToObject<FileHttpHandlerOptions>(configInfo.HttpHandlerOptions); if (!string.IsNullOrWhiteSpace(configInfo.LoadBalancerOptions)) fgc.LoadBalancerOptions = ToObject<FileLoadBalancerOptions>(configInfo.LoadBalancerOptions); if (!string.IsNullOrWhiteSpace(configInfo.QoSOptions)) fgc.QoSOptions = ToObject<FileQoSOptions>(configInfo.QoSOptions); if (!string.IsNullOrWhiteSpace(configInfo.RateLimitOptions)) fgc.RateLimitOptions = ToObject<FileRateLimitOptions>(configInfo.RateLimitOptions); if (!string.IsNullOrWhiteSpace(configInfo.ServiceDiscoveryProvider)) fgc.ServiceDiscoveryProvider = ToObject<FileServiceDiscoveryProvider>(configInfo.ServiceDiscoveryProvider);            fileConfig.GlobalConfiguration = fgc; // reroutes var reRouteResult = await _routeDbRepository.GetListAsync(it => it.GatewayId == configInfo.GatewayId && it.State == 1); if (reRouteResult.Count > 0) { var reroutelist = new List<FileReRoute>(); foreach (var model in reRouteResult) { var m = new FileReRoute() { UpstreamHost = model.UpstreamHost,                        UpstreamPathTemplate = model.UpstreamPathTemplate, DownstreamPathTemplate = model.DownstreamPathTemplate, DownstreamScheme = model.DownstreamScheme,
ServiceName = model.ServiceName, Priority = model.Priority, RequestIdKey = model.RequestIdKey, Key = model.Key, Timeout = model.Timeout, }; if (!string.IsNullOrWhiteSpace(model.UpstreamHttpMethod)) m.UpstreamHttpMethod = ToObject<List<string>>(model.UpstreamHttpMethod); if (!string.IsNullOrWhiteSpace(model.DownstreamHostAndPorts)) m.DownstreamHostAndPorts = ToObject<List<FileHostAndPort>>(model.DownstreamHostAndPorts); if (!string.IsNullOrWhiteSpace(model.SecurityOptions)) m.SecurityOptions = ToObject<FileSecurityOptions>(model.SecurityOptions); if (!string.IsNullOrWhiteSpace(model.CacheOptions)) m.FileCacheOptions = ToObject<FileCacheOptions>(model.CacheOptions); if (!string.IsNullOrWhiteSpace(model.HttpHandlerOptions)) m.HttpHandlerOptions = ToObject<FileHttpHandlerOptions>(model.HttpHandlerOptions); if (!string.IsNullOrWhiteSpace(model.AuthenticationOptions)) m.AuthenticationOptions = ToObject<FileAuthenticationOptions>(model.AuthenticationOptions); if (!string.IsNullOrWhiteSpace(model.RateLimitOptions)) m.RateLimitOptions = ToObject<FileRateLimitRule>(model.RateLimitOptions); if (!string.IsNullOrWhiteSpace(model.LoadBalancerOptions)) m.LoadBalancerOptions = ToObject<FileLoadBalancerOptions>(model.LoadBalancerOptions); if (!string.IsNullOrWhiteSpace(model.QoSOptions)) m.QoSOptions = ToObject<FileQoSOptions>(model.QoSOptions); if (!string.IsNullOrWhiteSpace(model.DelegatingHandlers)) m.DelegatingHandlers = ToObject<List<string>>(model.DelegatingHandlers); reroutelist.Add(m); } fileConfig.ReRoutes = reroutelist; } } Console.WriteLine((DateTime.Now - st).TotalMilliseconds); return new OkResponse<FileConfiguration>(fileConfig);    } public async Task<Response> Set(FileConfiguration fileConfiguration) { return await Task.FromResult(new OkResponse()); }
/// <summary> /// 将Json字符串转换为对象 /// </summary> /// <param name="json">Json字符串</param> private T ToObject<T>(string json) { if (string.IsNullOrWhiteSpace(json)) return default(T); return JsonConvert.DeserializeObject<T>(json); }}


可以看到四项配置里并不是全部都进行可配置化,如果有需求可以自行增加字段实现


redis的存储是大json方式,而mysql是一条一条的,因为配置的管理是以mysql为主,然后同步到其他存储介质中的


网关配置的更新


有加载就有更新,在ocelot中配置的更新是使用自己的实现来完成配置的热更新,方式如下


 1、配置文件方式是通过配置文件的IOptionsMonitor的OnChange方式重新加载配置信息


 2、第三方存储方式是通过默认实现的FileConfigurationPoller方法定时(默认1s)取获取配置信息的


 所以我们扩展的获取配置形式,在注册的时候要把FileConfigurationPoller HostedService一同注入进去,代码如下


public static IOcelotBuilder AddConfigStoredInRedis(this IOcelotBuilder builder, string apiGatewayKey, string redisConnectionString){ builder.Services.AddSingleton<RedisClient>(); builder.Services.AddHostedService<FileConfigurationPoller>(); builder.Services.AddSingleton<IFileConfigurationRepository>(sp => { return new RedisFileConfigurationRepository(sp.GetRequiredService<RedisClient>(), apiGatewayKey, redisConnectionString); }); return builder;}


其中涉及到Bucket.DbContext和Bucket.Redis组件很简单,也可自行实现。


配置的管理


其实最开始的时候,使用的是consul存储配置,然后通过网关自带的配置接口进行配置的管理,但是在ocelot的一次升级的时候出现了一个问题(配置信息丢失),虽然当时修改了ocelot的源码解决了,后来还是决定扩展存储方式,所以上面的获取配置接口的set方法都不实现了。


上面已经说了是已mysql进行配置存储然后同步到其他介质上,所以我们只要维护好mysql数据库就可以了。


具体代码就不贴了,后续会进行具体介绍,管理项目地址:github地址,截几张管理图:




推荐阅读

(点击标题可跳转阅读)

基于.NET Core 微服务的另类实现

.NET Core微服务:分布式链路追踪系统分享

.NET Core 的微服务、容器、运维、自动化发布


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

喜欢就点一下「好看」呗~

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存