上篇文章我们介绍了如何扩展Ocelot网关,并实现数据库存储,然后测试了网关的路由功能,一切都是那么顺利,但是有一个问题未解决,就是如果网关配置信息发生变更时如何生效?以及我使用其他数据库存储如何快速实现?本篇就这两个问题展开讲解,用到的文档及源码将会在GitHub上开源,每篇的源代码我将用分支的方式管理,本篇使用的分支为course2。
附文档及源码下载地址:[https://github.com/jinyancao/CtrAuthPlatform/tree/course2]
上一篇我们实现了网关的配置信息从数据库中提取,项目发布时可以把我们已有的网关配置都设置好并启动,但是正式项目运行时,网关配置信息随时都有可能发生变更,那如何在不影响项目使用的基础上来更新配置信息呢?这篇我将介绍2种方式来实现网关的动态更新,一是后台服务定期提取最新的网关配置信息更新网关配置,二是网关对外提供安全接口,由我们需要更新时,调用此接口进行更新,下面就这两种方式,我们来看下如何实现。
1、定时服务方式
网关的灵活性是设计时必须考虑的,实现定时服务的方式我们需要配置是否开启和更新周期,所以我们需要扩展配置类AhphOcelotConfiguration,增加是否启用服务和更新周期2个字段。
namespace Ctr.AhphOcelot.Configuration { /// <summary> /// 金焰的世界 /// 2018-11-11 /// 自定义配置信息 /// </summary> public class AhphOcelotConfiguration { /// <summary> /// 数据库连接字符串,使用不同数据库时自行修改,默认实现了SQLSERVER /// </summary> public string DbConnectionStrings { get; set; } /// <summary> /// 金焰的世界 /// 2018-11-12 /// 是否启用定时器,默认不启动 /// </summary> public bool EnableTimer { get; set; } = false; /// <summary> /// 金焰的世界 /// 2018-11.12 /// 定时器周期,单位(毫秒),默认30分钟自动更新一次 /// </summary> public int TimerDelay { get; set; } = 30*60*1000; } }配置文件定义完成,那如何完成后台任务随着项目启动而一起启动呢?IHostedService接口了解一下,我们可以通过实现这个接口,来完成我们后台任务,然后通过Ioc容器注入即可。
新建DbConfigurationPoller类,实现IHostedService接口,详细代码如下。
using Microsoft.Extensions.Hosting; using Ocelot.Configuration.Creator; using Ocelot.Configuration.Repository; using Ocelot.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Ctr.AhphOcelot.Configuration { /// <summary> /// 金焰的世界 /// 2018-11-12 /// 数据库配置信息更新策略 /// </summary> public class DbConfigurationPoller : IHostedService, IDisposable { private readonly IOcelotLogger _logger; private readonly IFileConfigurationRepository _repo; private readonly AhphOcelotConfiguration _option; private Timer _timer; private bool _polling; private readonly IInternalConfigurationRepository _internalConfigRepo; private readonly IInternalConfigurationCreator _internalConfigCreator; public DbConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IInternalConfigurationRepository internalConfigRepo, IInternalConfigurationCreator internalConfigCreator, AhphOcelotConfiguration option) { _internalConfigRepo = internalConfigRepo; _internalConfigCreator = internalConfigCreator; _logger = factory.CreateLogger<DbConfigurationPoller>(); _repo = repo; _option = option; } public void Dispose() { _timer?.Dispose(); } public Task StartAsync(CancellationToken cancellationToken) { if (_option.EnableTimer) {//判断是否启用自动更新 _logger.LogInformation($"{nameof(DbConfigurationPoller)} is starting."); _timer = new Timer(async x => { if (_polling) { return; } _polling = true; await Poll(); _polling = false; }, null, _option.TimerDelay, _option.TimerDelay); } return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { if (_option.EnableTimer) {//判断是否启用自动更新 _logger.LogInformation($"{nameof(DbConfigurationPoller)} is stopping."); _timer?.Change(Timeout.Infinite, 0); } return Task.CompletedTask; } private async Task Poll() { _logger.LogInformation("Started polling"); var fileConfig = await _repo.Get(); if (fileConfig.IsError) { _logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); return; } else { var config = await _internalConfigCreator.Create(fileConfig.Data); if (!config.IsError) { _internalConfigRepo.AddOrReplace(config.Data); } } _logger.LogInformation("Finished polling"); } } }