简介
微服务的系统应用中,网关系统使用的是ocelot,ocelot目前已经比较成熟了
ocelot就不做介绍了,等整体介绍完后再进行各类扩展介绍,ocelot源码地址:
https://github.com/ThreeMammals/Ocelot <https://github.com/ThreeMammals/Ocelot>
ocelot目前由很多功能组件组成,每个组件都可以根据自己的实际情况进行扩展(暂时不做过多介绍)
本文主要介绍ocelot网关使用中个人认为应该最先处理的东西
健康检查
在实际的应用中网关项目都会部署多台,然后通过nginx进行软负载,在更新部署网关项目的过程中服务肯定是无法使用,这个时候我们就需要利用nginx的健康检查机制进行控制
网关需要给nginx提供一个健康检查地址,ocelot使用的url
path地址进行路由匹配,当匹配不到时会返回404,所以我们需要单独处理一个健康检查地址
Ocelot提供了一个中间件配置替换的方法OcelotPipelineConfiguration,我们对OcelotPipelineConfiguration的PreErrorResponderMiddleware中间件方法进行扩展,代码如下:
1 var conf = new OcelotPipelineConfiguration() 2 { 3
PreErrorResponderMiddleware =async (ctx, next) => 4 { 5 if
(ctx.HttpContext.Request.Path.Equals(new PathString("/"))) 6 { 7 await
ctx.HttpContext.Response.WriteAsync("ok"); 8 } 9 else 10 { 11 await
next.Invoke();12 } 13 } 14 }; 15 app.UseOcelot(conf).Wait();
网关和路由配置
网关的配置包含四个部分,ReRoutes、DynamicReRoutes、Aggregates、GlobalConfiguration,
ocelot配置的获取默认是使用配置文件的方式,上面已经说了网关一般都会部署多台,使用文件配置还是存在一定弊端
ocelot的配置获取方法是IFileConfigurationRepository接口,所以如果我们实现了此接口就可以满足配置存储方式的扩展,目前已扩展mysql和redis,代码如下
redis:
1 public class RedisFileConfigurationRepository: IFileConfigurationRepository
2 { 3 private readonly RedisClient _redisClient; 4 private readonly string
_apiGatewayKey; 5 private readonly string _redisConnection; 6 7 public
RedisFileConfigurationRepository(RedisClient redisClient,string apiGatewayKey,
string redisConnection) 8 { 9 _redisClient = redisClient; 10 _apiGatewayKey =
apiGatewayKey;11 _redisConnection = redisConnection; 12 } 13 14 public async
Task<Response<FileConfiguration>> Get() 15 { 16 var redis =
_redisClient.GetDatabase(_redisConnection,11); 17 18 var json = await
redis.StringGetAsync($"ApiGatewayConfig:{_apiGatewayKey}"); 19 20 if
(json.IsNullOrEmpty)21 return new OkResponse<FileConfiguration>(new
FileConfiguration { });22 23 var fileConfig =
JsonConvert.DeserializeObject<FileConfiguration>(json); 24 25 return new
OkResponse<FileConfiguration>(fileConfig); 26 } 27 28 public async
Task<Response> Set(FileConfiguration fileConfiguration) 29 { 30 return await
Task.FromResult(new OkResponse()); 31 } 32 }
mysql:
1 public class MySqlFileConfigurationRepository : IFileConfigurationRepository
2 { 3 private readonly IDbRepository<ConfigurationInfo> _configDbRepository;
4 private readonly IDbRepository<ReRouteInfo> _routeDbRepository; 5 private
readonly string _apiGatewayKey; 6 7 public
MySqlFileConfigurationRepository(IDbRepository<ConfigurationInfo>
configDbRepository, IDbRepository<ReRouteInfo> routeDbRepository,string
apiGatewayKey) 8 { 9 _configDbRepository = configDbRepository; 10
_routeDbRepository = routeDbRepository; 11 _apiGatewayKey = apiGatewayKey; 12
} 13 14 public async Task<Response<FileConfiguration>> Get() 15 { 16 var
st = DateTime.Now; 17 var fileConfig = new FileConfiguration(); 18 var
configInfo =await _configDbRepository.GetFirstAsync(it => it.GatewayKey ==
_apiGatewayKey); 19 if (configInfo != null) 20 { 21 // config 22 var fgc =
new FileGlobalConfiguration 23 { 24 BaseUrl = configInfo.BaseUrl, 25
DownstreamScheme = configInfo.DownstreamScheme, 26 RequestIdKey =
configInfo.RequestIdKey, 27 }; 28 if (!string
.IsNullOrWhiteSpace(configInfo.HttpHandlerOptions)) 29 fgc.HttpHandlerOptions =
ToObject<FileHttpHandlerOptions>(configInfo.HttpHandlerOptions); 30 if (!string
.IsNullOrWhiteSpace(configInfo.LoadBalancerOptions)) 31 fgc.LoadBalancerOptions
= ToObject<FileLoadBalancerOptions>(configInfo.LoadBalancerOptions); 32 if (!
string.IsNullOrWhiteSpace(configInfo.QoSOptions)) 33 fgc.QoSOptions =
ToObject<FileQoSOptions>(configInfo.QoSOptions); 34 if (!string
.IsNullOrWhiteSpace(configInfo.RateLimitOptions)) 35 fgc.RateLimitOptions =
ToObject<FileRateLimitOptions>(configInfo.RateLimitOptions); 36 if (!string
.IsNullOrWhiteSpace(configInfo.ServiceDiscoveryProvider)) 37
fgc.ServiceDiscoveryProvider = ToObject<FileServiceDiscoveryProvider>
(configInfo.ServiceDiscoveryProvider); 38 fileConfig.GlobalConfiguration = fgc;
39 40 // reroutes 41 var reRouteResult = await
_routeDbRepository.GetListAsync(it => it.GatewayId == configInfo.GatewayId &&
it.State ==1); 42 if (reRouteResult.Count > 0) 43 { 44 var reroutelist = new
List<FileReRoute>(); 45 foreach (var model in reRouteResult) 46 { 47 var m
=new FileReRoute() 48 { 49 UpstreamHost = model.UpstreamHost, 50
UpstreamPathTemplate = model.UpstreamPathTemplate, 51 52
DownstreamPathTemplate = model.DownstreamPathTemplate, 53 DownstreamScheme =
model.DownstreamScheme, 54 55 ServiceName = model.ServiceName, 56 Priority =
model.Priority, 57 RequestIdKey = model.RequestIdKey, 58 Key = model.Key, 59
Timeout = model.Timeout, 60 }; 61 if (!string
.IsNullOrWhiteSpace(model.UpstreamHttpMethod)) 62 m.UpstreamHttpMethod =
ToObject<List<string>>(model.UpstreamHttpMethod); 63 if (!string
.IsNullOrWhiteSpace(model.DownstreamHostAndPorts)) 64 m.DownstreamHostAndPorts
= ToObject<List<FileHostAndPort>>(model.DownstreamHostAndPorts); 65 if (!string
.IsNullOrWhiteSpace(model.SecurityOptions)) 66 m.SecurityOptions =
ToObject<FileSecurityOptions>(model.SecurityOptions); 67 if (!string
.IsNullOrWhiteSpace(model.CacheOptions)) 68 m.FileCacheOptions =
ToObject<FileCacheOptions>(model.CacheOptions); 69 if (!string
.IsNullOrWhiteSpace(model.HttpHandlerOptions)) 70 m.HttpHandlerOptions =
ToObject<FileHttpHandlerOptions>(model.HttpHandlerOptions); 71 if (!string
.IsNullOrWhiteSpace(model.AuthenticationOptions)) 72 m.AuthenticationOptions =
ToObject<FileAuthenticationOptions>(model.AuthenticationOptions); 73 if (!
string.IsNullOrWhiteSpace(model.RateLimitOptions)) 74 m.RateLimitOptions =
ToObject<FileRateLimitRule>(model.RateLimitOptions); 75 if (!string
.IsNullOrWhiteSpace(model.LoadBalancerOptions)) 76 m.LoadBalancerOptions =
ToObject<FileLoadBalancerOptions>(model.LoadBalancerOptions); 77 if (!string
.IsNullOrWhiteSpace(model.QoSOptions)) 78 m.QoSOptions =
ToObject<FileQoSOptions>(model.QoSOptions); 79 if (!string
.IsNullOrWhiteSpace(model.DelegatingHandlers)) 80 m.DelegatingHandlers =
ToObject<List<string>>(model.DelegatingHandlers); 81 reroutelist.Add(m); 82
} 83 fileConfig.ReRoutes = reroutelist; 84 } 85 } 86
Console.WriteLine((DateTime.Now - st).TotalMilliseconds); 87 return new
OkResponse<FileConfiguration>(fileConfig); 88 } 89 90 public async
Task<Response> Set(FileConfiguration fileConfiguration) 91 { 92 return await
Task.FromResult(new OkResponse()); 93 } 94 95 /// <summary> 96 ///
将Json字符串转换为对象 97 /// </summary> 98 /// <param name="json">Json字符串</param> 99
private T ToObject<T>(string json) 100 { 101 if (string
.IsNullOrWhiteSpace(json))102 return default(T); 103 return
JsonConvert.DeserializeObject<T>(json); 104 } 105 }
可以看到四项配置里并不是全部都进行可配置化,如果有需求可以自行增加字段实现
redis的存储是大json方式,而mysql是一条一条的,因为配置的管理是以mysql为主,然后同步到其他存储介质中的
网关配置的更新
有加载就有更新,在ocelot中配置的更新是使用自己的实现来完成配置的热更新,方式如下
1、配置文件方式是通过配置文件的IOptionsMonitor的OnChange方式重新加载配置信息
2、第三方存储方式是通过默认实现的FileConfigurationPoller方法定时(默认1s)取获取配置信息的
所以我们扩展的获取配置形式,在注册的时候要把FileConfigurationPoller HostedService一同注入进去,代码如下
1 public static IOcelotBuilder AddConfigStoredInRedis(this IOcelotBuilder
builder,string apiGatewayKey, string redisConnectionString) 2 { 3
builder.Services.AddSingleton<RedisClient>(); 4
builder.Services.AddHostedService<FileConfigurationPoller>(); 5
builder.Services.AddSingleton<IFileConfigurationRepository>(sp => 6 { 7 return
new RedisFileConfigurationRepository(sp.GetRequiredService<RedisClient>(),
apiGatewayKey, redisConnectionString); 8 }); 9 return builder; 10 }
其中涉及到Bucket.DbContext和Bucket.Redis组件很简单,也可自行实现
配置的管理
其实最开始的时候,使用的是consul存储配置,然后通过网关自带的配置接口进行配置的管理,但是在ocelot的一次升级的时候出现了一个问题(配置信息丢失),虽然当时修改了ocelot的源码解决了,后来还是决定扩展存储方式,所以上面的获取配置接口的set方法都不实现了
上面已经说了是已mysql进行配置存储然后同步到其他介质上,所以我们只要维护好mysql数据库就可以了
具体代码就不贴了,后续会进行具体介绍,管理项目地址:github地址
<https://github.com/q315523275/FamilyBucket/tree/master/%E5%9F%BA%E7%A1%80%E6%9C%8D%E5%8A%A1%E9%A1%B9%E7%9B%AE/Pinzhi.Platform>
,截几张管理图
热门工具 换一换