【.NET Core项目实战-统一认证平台】开篇及目录索引

一、背景

首先说声抱歉,可能是因为假期综合症(其实就是因为懒哈)的原因,已经很长时间没更新博客了,现在也调整的差不多了,准备还是以每周1-2篇的进度来更新博客,并完成本项目所有功能。

言归正传,本重构项目是在我根据实际需求重构,由于还未完全写完,所以也没进行压测,在2月份时,张善友老师给我留言说经过压测发现我重构的Ocelot网关功能性能较差,其中根本原因就是缓存模块,由于重构项目的缓存强依赖Redis缓存,造成性能瓶颈,发现问题后,我也第一时间进行测试,性能影响很大,经过跟张老师请教,可以使用二级缓存来解决性能问题,首先感谢张老师关注并指点迷津,于是就有了这篇文章,如何把现有缓存改成二级缓存并使用。

二、改造思路

为了解决redis的强依赖性,首先需要把缓存数据存储到本地,所有请求都优先从本地提取,如果提取不到再从redis提取,如果redis无数据,在从数据库中提取。提取流程如下:

MemoryCache > Redis > db

此种方式减少提取缓存的网络开销,也合理利用了分布式缓存,并最终减少数据库的访问开销。但是使用此种方案也面临了一个问题是如何保证集群环境时每个机器本地缓存数据的一致性,这时我们会想到redis的发布、订阅特性,在数据发生变动时更新redis数据并发布缓存更新通知,由每个集群机器订阅变更事件,然后处理本地缓存记录,最终达到集群缓存的缓存一致性。

但是此方式对于缓存变更非常频繁的业务不适用,比如限流策略(准备还是使用分布式redis缓存实现),但是可以扩展配置单机限流时使用本地缓存实现,如果谁有更好的实现方式,也麻烦告知下集群环境下限流的实现,不胜感激。

三、改造代码

首先需要分析下目前改造后的Ocelot网关在哪些业务中使用的缓存,然后把使用本地缓存的的业务重构,增加提取数据流程,最后提供网关外部缓存初始化接口,便于与业务系统进行集成。

1.重写缓存方法

找到问题的原因后,就可以重写缓存方法,增加二级缓存支持,默认使用本地的缓存,新建CzarMemoryCache类,来实现IOcelotCache<T>方法,实现代码如下。

  1. using Czar.Gateway.Configuration;
  2. using Czar.Gateway.RateLimit;
  3. using Microsoft.Extensions.Caching.Memory;
  4. using Ocelot.Cache;
  5. using System;
  6. namespace Czar.Gateway.Cache
  7. {
  8. /// <summary>
  9. /// 金焰的世界
  10. /// 2019-03-03
  11. /// 使用二级缓存解决集群环境问题
  12. /// </summary>
  13. public class CzarMemoryCache<T> : IOcelotCache<T>
  14. {
  15. private readonly CzarOcelotConfiguration _options;
  16. private readonly IMemoryCache _cache;
  17. public CzarMemoryCache(CzarOcelotConfiguration options,IMemoryCache cache)
  18. {
  19. _options = options;
  20. _cache = cache;
  21. }
  22. public void Add(string key, T value, TimeSpan ttl, string region)
  23. {
  24. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix,region, key);
  25. if (_options.ClusterEnvironment)
  26. {
  27. var msg = value.ToJson();
  28. if (typeof(T) == typeof(CachedResponse))
  29. {//带过期时间的缓存
  30. _cache.Set(key, value, ttl); //添加本地缓存
  31. RedisHelper.Set(key, msg); //加入redis缓存
  32. RedisHelper.Publish(key, msg); //发布
  33. }
  34. else if (typeof(T) == typeof(CzarClientRateLimitCounter?))
  35. {//限流缓存,直接使用redis
  36. RedisHelper.Set(key, value, (int)ttl.TotalSeconds);
  37. }
  38. else
  39. {//正常缓存,发布
  40. _cache.Set(key, value, ttl); //添加本地缓存
  41. RedisHelper.Set(key, msg); //加入redis缓存
  42. RedisHelper.Publish(key, msg); //发布
  43. }
  44. }
  45. else
  46. {
  47. _cache.Set(key, value, ttl); //添加本地缓存
  48. }
  49. }
  50. public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
  51. {
  52. Add(key, value, ttl, region);
  53. }
  54. public void ClearRegion(string region)
  55. {
  56. if (_options.ClusterEnvironment)
  57. {
  58. var keys = RedisHelper.Keys(region + "*");
  59. RedisHelper.Del(keys);
  60. foreach (var key in keys)
  61. {
  62. RedisHelper.Publish(key, ""); //发布key值为空,处理时删除即可。
  63. }
  64. }
  65. else
  66. {
  67. _cache.Remove(region);
  68. }
  69. }
  70. public T Get(string key, string region)
  71. {
  72. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, region, key);
  73. if(region== CzarCacheRegion.CzarClientRateLimitCounterRegion&& _options.ClusterEnvironment)
  74. {//限流且开启了集群支持,默认从redis取
  75. return RedisHelper.Get<T>(key);
  76. }
  77. var result = _cache.Get<T>(key);
  78. if (result == null&& _options.ClusterEnvironment)
  79. {
  80. result= RedisHelper.Get<T>(key);
  81. if (result != null)
  82. {
  83. if (typeof(T) == typeof(CachedResponse))
  84. {//查看redis过期时间
  85. var second = RedisHelper.Ttl(key);
  86. if (second > 0)
  87. {
  88. _cache.Set(key, result, TimeSpan.FromSeconds(second));
  89. }
  90. }
  91. else
  92. {
  93. _cache.Set(key, result, TimeSpan.FromSeconds(_options.CzarCacheTime));
  94. }
  95. }
  96. }
  97. return result;
  98. }
  99. }
  100. }

上面就段代码实现了本地缓存和Redis缓存的支持,优先从本地提取,如果在集群环境使用,增加redis缓存支持,但是此种方式不适用缓存变更非常频繁场景,比如客户端限流的实现,所以在代码中把客户端限流的缓存直接使用redis缓存实现。

2.注入实现和订阅

有了实现代码后,发现还缺少添加缓存注入和配置信息修改。首先需要修改配置文件来满足是否开启集群判断,然后需要实现redis的不同部署方式能够通过配置文件配置进行管理,避免硬编码导致的不可用问题。

配置文件CzarOcelotConfiguration.cs修改代码如下:

  1. namespace Czar.Gateway.Configuration
  2. {
  3. /// <summary>
  4. /// 金焰的世界
  5. /// 2018-11-11
  6. /// 自定义配置信息
  7. /// </summary>
  8. public class CzarOcelotConfiguration
  9. {
  10. /// <summary>
  11. /// 数据库连接字符串,使用不同数据库时自行修改,默认实现了SQLSERVER
  12. /// </summary>
  13. public string DbConnectionStrings { get; set; }
  14. /// <summary>
  15. /// 金焰的世界
  16. /// 2018-11-12
  17. /// 是否启用定时器,默认不启动
  18. /// </summary>
  19. public bool EnableTimer { get; set; } = false;
  20. /// <summary>
  21. /// 金焰的世界
  22. /// 2018-11.12
  23. /// 定时器周期,单位(毫秒),默认30分总自动更新一次
  24. /// </summary>
  25. public int TimerDelay { get; set; } = 30 * 60 * 1000;
  26. /// <summary>
  27. /// 金焰的世界
  28. /// 2018-11-14
  29. /// Redis连接字符串
  30. /// </summary>
  31. public string RedisConnectionString { get; set; }
  32. /// <summary>
  33. /// 金焰的世界
  34. /// 2019-03-03
  35. /// 配置哨兵或分区时使用
  36. /// </summary>
  37. public string[] RedisSentinelOrPartitionConStr { get; set; }
  38. /// <summary>
  39. /// 金焰的世界
  40. /// 2019-03-03
  41. /// Redis部署方式,默认使用普通方式
  42. /// </summary>
  43. public RedisStoreMode RedisStoreMode { get; set; } = RedisStoreMode.Normal;
  44. /// <summary>
  45. /// 金焰的计界
  46. /// 2019-03-03
  47. /// 做集群缓存同步时使用,会订阅所有正则匹配的事件
  48. /// </summary>
  49. public string RedisOcelotKeyPrefix { get; set; } = "CzarOcelot";
  50. /// <summary>
  51. /// 金焰的世界
  52. /// 2019-03-03
  53. /// 是否启用集群环境,如果非集群环境直接本地缓存+数据库即可
  54. /// </summary>
  55. public bool ClusterEnvironment { get; set; } = false;
  56. /// <summary>
  57. /// 金焰的世界
  58. /// 2018-11-15
  59. /// 是否启用客户端授权,默认不开启
  60. /// </summary>
  61. public bool ClientAuthorization { get; set; } = false;
  62. /// <summary>
  63. /// 金焰的世界
  64. /// 2018-11-15
  65. /// 服务器缓存时间,默认30分钟
  66. /// </summary>
  67. public int CzarCacheTime { get; set; } = 1800;
  68. /// <summary>
  69. /// 金焰的世界
  70. /// 2018-11-15
  71. /// 客户端标识,默认 client_id
  72. /// </summary>
  73. public string ClientKey { get; set; } = "client_id";
  74. /// <summary>
  75. /// 金焰的世界
  76. /// 2018-11-18
  77. /// 是否开启自定义限流,默认不开启
  78. /// </summary>
  79. public bool ClientRateLimit { get; set; } = false;
  80. }
  81. }

在配置文件中修改了redis相关配置,支持使用redis的普通模式、集群模式、哨兵模式、分区模式,配置方式可参考csrediscore开源项目。

然后修改ServiceCollectionExtensions.cs代码,注入相关实现和redis客户端。

  1. builder.Services.AddMemoryCache(); //添加本地缓存
  2. #region 启动Redis缓存,并支持普通模式 官方集群模式 哨兵模式 分区模式
  3. if (options.ClusterEnvironment)
  4. {
  5. //默认使用普通模式
  6. var csredis = new CSRedis.CSRedisClient(options.RedisConnectionString);
  7. switch (options.RedisStoreMode)
  8. {
  9. case RedisStoreMode.Partition:
  10. var NodesIndex = options.RedisSentinelOrPartitionConStr;
  11. Func<string, string> nodeRule = null;
  12. csredis = new CSRedis.CSRedisClient(nodeRule, options.RedisSentinelOrPartitionConStr);
  13. break;
  14. case RedisStoreMode.Sentinel:
  15. csredis = new CSRedis.CSRedisClient(options.RedisConnectionString, options.RedisSentinelOrPartitionConStr);
  16. break;
  17. }
  18. //初始化 RedisHelper
  19. RedisHelper.Initialization(csredis);
  20. }
  21. #endregion
  22. builder.Services.AddSingleton<IOcelotCache<FileConfiguration>, CzarMemoryCache<FileConfiguration>>();
  23. builder.Services.AddSingleton<IOcelotCache<InternalConfiguration>, CzarMemoryCache<InternalConfiguration>>();
  24. builder.Services.AddSingleton<IOcelotCache<CachedResponse>, CzarMemoryCache<CachedResponse>>();
  25. builder.Services.AddSingleton<IInternalConfigurationRepository, RedisInternalConfigurationRepository>();
  26. builder.Services.AddSingleton<IOcelotCache<ClientRoleModel>, CzarMemoryCache<ClientRoleModel>>();
  27. builder.Services.AddSingleton<IOcelotCache<RateLimitRuleModel>, CzarMemoryCache<RateLimitRuleModel>>();
  28. builder.Services.AddSingleton<IOcelotCache<RemoteInvokeMessage>, CzarMemoryCache<RemoteInvokeMessage>>();
  29. builder.Services.AddSingleton<IOcelotCache<CzarClientRateLimitCounter?>, CzarMemoryCache<CzarClientRateLimitCounter?>>();

现在需要实现redis订阅来更新本地的缓存信息,在项目启动时判断是否开启集群模式,如果开启就启动订阅,实现代码如下:

  1. public static async Task<IApplicationBuilder> UseCzarOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
  2. {
  3. //重写创建配置方法
  4. var configuration = await CreateConfiguration(builder);
  5. ConfigureDiagnosticListener(builder);
  6. CacheChangeListener(builder);
  7. return CreateOcelotPipeline(builder, pipelineConfiguration);
  8. }
  9. /// <summary>
  10. /// 金焰的世界
  11. /// 2019-03-03
  12. /// 添加缓存数据变更订阅
  13. /// </summary>
  14. /// <param name="builder"></param>
  15. /// <returns></returns>
  16. private static void CacheChangeListener(IApplicationBuilder builder)
  17. {
  18. var config= builder.ApplicationServices.GetService<CzarOcelotConfiguration>();
  19. var _cache= builder.ApplicationServices.GetService<IMemoryCache>();
  20. if (config.ClusterEnvironment)
  21. {
  22. //订阅满足条件的所有事件
  23. RedisHelper.PSubscribe(new[] { config.RedisOcelotKeyPrefix + "*" }, message =>
  24. {
  25. var key = message.Channel;
  26. _cache.Remove(key); //直接移除,如果有请求从redis里取
  27. //或者直接判断本地缓存是否存在,如果存在更新,可自行实现。
  28. });
  29. }
  30. }

使用的是从配置文件提取的正则匹配的所有KEY都进行订阅,由于本地缓存增加了定时过期策略,所以为了实现方便,当发现redis数据发生变化,所有订阅端直接移除本地缓存即可,如果有新的请求直接从redis取,然后再次缓存,防止集群客户端缓存信息不一致。

为了区分不同的缓存实体,便于在原始数据发送变更时进行更新,定义CzarCacheRegion类。

  1. namespace Czar.Gateway.Configuration
  2. {
  3. /// <summary>
  4. /// 缓存所属区域
  5. /// </summary>
  6. public class CzarCacheRegion
  7. {
  8. /// <summary>
  9. /// 授权
  10. /// </summary>
  11. public const string AuthenticationRegion = "CacheClientAuthentication";
  12. /// <summary>
  13. /// 路由配置
  14. /// </summary>
  15. public const string FileConfigurationRegion = "CacheFileConfiguration";
  16. /// <summary>
  17. /// 内部配置
  18. /// </summary>
  19. public const string InternalConfigurationRegion = "CacheInternalConfiguration";
  20. /// <summary>
  21. /// 客户端权限
  22. /// </summary>
  23. public const string ClientRoleModelRegion = "CacheClientRoleModel";
  24. /// <summary>
  25. /// 限流规则
  26. /// </summary>
  27. public const string RateLimitRuleModelRegion = "CacheRateLimitRuleModel";
  28. /// <summary>
  29. /// Rpc远程调用
  30. /// </summary>
  31. public const string RemoteInvokeMessageRegion = "CacheRemoteInvokeMessage";
  32. /// <summary>
  33. /// 客户端限流
  34. /// </summary>
  35. public const string CzarClientRateLimitCounterRegion = "CacheCzarClientRateLimitCounter";
  36. }
  37. }

现在只需要修改缓存的region为定义的值即可,唯一需要改动的代码就是把之前写死的代码改成如下代码即可。

var enablePrefix = CzarCacheRegion.AuthenticationRegion;

3.开发缓存变更接口

现在整个二级缓存基本完成,但是还遇到一个问题就是外部如何根据数据库变更数据时来修改缓存数据,这时就需要提供外部修改api来实现。

添加CzarCacheController.cs对外部提供缓存更新相关接口,详细代码如下:

  1. using Czar.Gateway.Authentication;
  2. using Czar.Gateway.Configuration;
  3. using Czar.Gateway.RateLimit;
  4. using Czar.Gateway.Rpc;
  5. using Microsoft.AspNetCore.Authorization;
  6. using Microsoft.AspNetCore.Mvc;
  7. using Microsoft.Extensions.Caching.Memory;
  8. using Ocelot.Configuration;
  9. using Ocelot.Configuration.Creator;
  10. using Ocelot.Configuration.Repository;
  11. using System;
  12. using System.Threading.Tasks;
  13. namespace Czar.Gateway.Cache
  14. {
  15. /// <summary>
  16. /// 提供外部缓存处理接口
  17. /// </summary>
  18. [Authorize]
  19. [Route("CzarCache")]
  20. public class CzarCacheController : Controller
  21. {
  22. private readonly CzarOcelotConfiguration _options;
  23. private readonly IClientAuthenticationRepository _clientAuthenticationRepository;
  24. private IFileConfigurationRepository _fileConfigurationRepository;
  25. private IInternalConfigurationCreator _internalConfigurationCreator;
  26. private readonly IClientRateLimitRepository _clientRateLimitRepository;
  27. private readonly IRpcRepository _rpcRepository;
  28. private readonly IMemoryCache _cache;
  29. public CzarCacheController(IClientAuthenticationRepository clientAuthenticationRepository, CzarOcelotConfiguration options,
  30. IFileConfigurationRepository fileConfigurationRepository,
  31. IInternalConfigurationCreator internalConfigurationCreator,
  32. IClientRateLimitRepository clientRateLimitRepository,
  33. IRpcRepository rpcRepository,
  34. IMemoryCache cache)
  35. {
  36. _clientAuthenticationRepository = clientAuthenticationRepository;
  37. _options = options;
  38. _fileConfigurationRepository = fileConfigurationRepository;
  39. _internalConfigurationCreator = internalConfigurationCreator;
  40. _clientRateLimitRepository = clientRateLimitRepository;
  41. _rpcRepository = rpcRepository;
  42. _cache = cache;
  43. }
  44. /// <summary>
  45. /// 更新客户端地址访问授权接口
  46. /// </summary>
  47. /// <param name="clientid">客户端ID</param>
  48. /// <param name="path">请求模板</param>
  49. /// <returns></returns>
  50. [HttpPost]
  51. [Route("ClientRule")]
  52. public async Task UpdateClientRuleCache(string clientid, string path)
  53. {
  54. var region = CzarCacheRegion.AuthenticationRegion;
  55. var key = CzarOcelotHelper.ComputeCounterKey(region, clientid, "", path);
  56. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, region, key);
  57. var result = await _clientAuthenticationRepository.ClientAuthenticationAsync(clientid, path);
  58. var data = new ClientRoleModel() { CacheTime = DateTime.Now, Role = result };
  59. if (_options.ClusterEnvironment)
  60. {
  61. RedisHelper.Set(key, data); //加入redis缓存
  62. RedisHelper.Publish(key, data.ToJson()); //发布事件
  63. }
  64. else
  65. {
  66. _cache.Remove(key);
  67. }
  68. }
  69. /// <summary>
  70. /// 更新网关配置路由信息
  71. /// </summary>
  72. /// <returns></returns>
  73. [HttpPost]
  74. [Route("InternalConfiguration")]
  75. public async Task UpdateInternalConfigurationCache()
  76. {
  77. var key = CzarCacheRegion.InternalConfigurationRegion;
  78. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, "", key);
  79. var fileconfig = await _fileConfigurationRepository.Get();
  80. var internalConfig = await _internalConfigurationCreator.Create(fileconfig.Data);
  81. var config = (InternalConfiguration)internalConfig.Data;
  82. if (_options.ClusterEnvironment)
  83. {
  84. RedisHelper.Set(key, config); //加入redis缓存
  85. RedisHelper.Publish(key, config.ToJson()); //发布事件
  86. }
  87. else
  88. {
  89. _cache.Remove(key);
  90. }
  91. }
  92. /// <summary>
  93. /// 删除路由配合的缓存信息
  94. /// </summary>
  95. /// <param name="region">区域</param>
  96. /// <param name="downurl">下端路由</param>
  97. /// <returns></returns>
  98. [HttpPost]
  99. [Route("Response")]
  100. public async Task DeleteResponseCache(string region,string downurl)
  101. {
  102. var key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, region, downurl);
  103. if (_options.ClusterEnvironment)
  104. {
  105. await RedisHelper.DelAsync(key);
  106. RedisHelper.Publish(key, "");//发布时间
  107. }
  108. else
  109. {
  110. _cache.Remove(key);
  111. }
  112. }
  113. /// <summary>
  114. /// 更新客户端限流规则缓存
  115. /// </summary>
  116. /// <param name="clientid">客户端ID</param>
  117. /// <param name="path">路由模板</param>
  118. /// <returns></returns>
  119. [HttpPost]
  120. [Route("RateLimitRule")]
  121. public async Task UpdateRateLimitRuleCache(string clientid, string path)
  122. {
  123. var region = CzarCacheRegion.RateLimitRuleModelRegion;
  124. var key = clientid + path;
  125. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, region, key);
  126. var result = await _clientRateLimitRepository.CheckClientRateLimitAsync(clientid, path);
  127. var data = new RateLimitRuleModel() { RateLimit = result.RateLimit, rateLimitOptions = result.rateLimitOptions };
  128. if (_options.ClusterEnvironment)
  129. {
  130. RedisHelper.Set(key, data); //加入redis缓存
  131. RedisHelper.Publish(key, data.ToJson()); //发布事件
  132. }
  133. else
  134. {
  135. _cache.Remove(key);
  136. }
  137. }
  138. /// <summary>
  139. /// 更新客户端是否开启限流缓存
  140. /// </summary>
  141. /// <param name="path"></param>
  142. /// <returns></returns>
  143. [HttpPost]
  144. [Route("ClientRole")]
  145. public async Task UpdateClientRoleCache(string path)
  146. {
  147. var region = CzarCacheRegion.ClientRoleModelRegion;
  148. var key = path;
  149. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, region, key);
  150. var result = await _clientRateLimitRepository.CheckReRouteRuleAsync(path);
  151. var data = new ClientRoleModel() { CacheTime = DateTime.Now, Role = result };
  152. if (_options.ClusterEnvironment)
  153. {
  154. RedisHelper.Set(key, data); //加入redis缓存
  155. RedisHelper.Publish(key, data.ToJson()); //发布事件
  156. }
  157. else
  158. {
  159. _cache.Remove(key);
  160. }
  161. }
  162. /// <summary>
  163. /// 更新呢客户端路由白名单缓存
  164. /// </summary>
  165. /// <param name="clientid"></param>
  166. /// <param name="path"></param>
  167. /// <returns></returns>
  168. [HttpPost]
  169. [Route("ClientReRouteWhiteList")]
  170. public async Task UpdateClientReRouteWhiteListCache(string clientid, string path)
  171. {
  172. var region = CzarCacheRegion.ClientReRouteWhiteListRegion;
  173. var key = clientid + path;
  174. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, region, key);
  175. var result = await _clientRateLimitRepository.CheckClientReRouteWhiteListAsync(clientid, path);
  176. var data = new ClientRoleModel() { CacheTime = DateTime.Now, Role = result };
  177. if (_options.ClusterEnvironment)
  178. {
  179. RedisHelper.Set(key, data); //加入redis缓存
  180. RedisHelper.Publish(key, data.ToJson()); //发布事件
  181. }
  182. else
  183. {
  184. _cache.Remove(key);
  185. }
  186. }
  187. [HttpPost]
  188. [Route("Rpc")]
  189. public async Task UpdateRpcCache(string UpUrl)
  190. {
  191. var region = CzarCacheRegion.RemoteInvokeMessageRegion;
  192. var key = UpUrl;
  193. key = CzarOcelotHelper.GetKey(_options.RedisOcelotKeyPrefix, region, key);
  194. var result = await _rpcRepository.GetRemoteMethodAsync(UpUrl);
  195. if (_options.ClusterEnvironment)
  196. {
  197. RedisHelper.Set(key, result); //加入redis缓存
  198. RedisHelper.Publish(key, result.ToJson()); //发布事件
  199. }
  200. else
  201. {
  202. _cache.Remove(key);
  203. }
  204. }
  205. }
  206. }

现在基本实现整个缓存的更新策略,只要配合后台管理界面,在相关缓存原始数据发送变更时,调用对应接口即可完成redis缓存的更新,并自动通知集群的所有本机清理缓存等待重新获取。

接口的调用方式参考之前我写的配置信息接口变更那篇即可。

四、性能测试

完成了改造后,我们拿改造前网关、改造后网关、原始Ocelot、直接调用API四个环境分别测试性能指标,由于测试环境有效,我直接使用本机环境,然后是Apache ab测试工具测试下相关性能(本测试不一定准确,只作为参考指标),测试的方式是使用100个并发请求10000次,测试结果分别如下。

1、改造前网关性能

2、改造后网关性能

3、Ocelot默认网关性能

4、直接调用API性能

本测试仅供参考,因为由于网关和服务端都在本机环境部署,所以使用网关和不使用网关性能差别非常小,如果分开部署可能性别差别会明显写,这不是本篇讨论的重点。

从测试中可以看到,重构的网关改造前和改造后性能有2倍多的提升,且与原生的Ocelot性能非常接近。

五、总结

本篇主要讲解了如何使用redis的发布订阅来实现二级缓存功能,并提供了缓存的更新相关接口供外部程序调用,避免出现集群环境下无法更新缓存数据导致提取数据不一致情况,但是针对每个客户端独立限流这块集群环境目前还是采用的redis的方式未使用本地缓存,如果有写的不对或有更好方式的,也希望多提宝贵意见。

本篇相关源码地址:https://github.com/jinyancao/czar.gateway

【.NET Core项目实战-统一认证平台】第十五章 网关篇-使用二级缓存提升性能的更多相关文章

  1. 【.NET Core项目实战-统一认证平台】第五章 网关篇-自定义缓存Redis

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我们介绍了2种网关配置信息更新的方法和扩展Mysql存储,本篇我们将介绍如何使用Redis来实现网关的所有缓存功能,用到的文档及源码 ...

  2. 【.NET Core项目实战-统一认证平台】第四章 网关篇-数据库存储配置(2)

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我们介绍了如何扩展Ocelot网关,并实现数据库存储,然后测试了网关的路由功能,一切都是那么顺利,但是有一个问题未解决,就是如果网关 ...

  3. 【.NET Core项目实战-统一认证平台】第七章 网关篇-自定义客户端限流

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何在网关上增加自定义客户端授权功能,从设计到编码实现,一步一步详细讲解,相信大家也掌握了自定义中间件的开发技巧了,本篇我们 ...

  4. 【.NET Core项目实战-统一认证平台】第三章 网关篇-数据库存储配置(1)

    [.NET Core项目实战-统一认证平台]开篇及目录索引 本篇将介绍如何扩展Ocelot中间件实现自定义网关,并使用2种不同数据库来演示Ocelot配置信息存储和动态更新功能,内容也是从实际设计出发 ...

  5. 【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我们介绍了网关使用Redis进行缓存,并介绍了如何进行缓存实现,缓存信息清理接口的使用.本篇我们将介绍如何实现网关自定义客户端授权, ...

  6. 【.NET Core项目实战-统一认证平台】第十三章 授权篇-如何强制有效令牌过期

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上一篇我介绍了JWT的生成验证及流程内容,相信大家也对JWT非常熟悉了,今天将从一个小众的需求出发,介绍如何强制令牌过期的思路和实现过程. ...

  7. 【.NET Core项目实战-统一认证平台】第十一章 授权篇-密码授权模式

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了基于Ids4客户端授权的原理及如何实现自定义的客户端授权,并配合网关实现了统一的授权异常返回值和权限配置等相关功能,本篇将介绍 ...

  8. 【.NET Core项目实战-统一认证平台】第十章 授权篇-客户端授权

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了如何使用Dapper持久化IdentityServer4(以下简称ids4)的信息,并实现了sqlserver和mysql两种 ...

  9. 【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.什么是RPC RPC是"远程调用(Remote Procedure Call)"的一个名称的缩写,并不是任何规范化的 ...

随机推荐

  1. 浏览器url地址殊字符转义编码

    网址URL中特殊字符转义编码字符    -    URL编码值 空格    -    %20"          -    %22#         -    %23%        -   ...

  2. Java_接口与抽象类

    接口: 接口,英文interface,在java中,泛指供别人调用的方法或函数.接口是对行为的一种抽象. 语法: [public] interface InterfaceName{} 注意: 1)接口 ...

  3. vue组件之间的传值方式

    一.父组件向子组件传值方式 1.1父组件向子组件传数据方式 <!DOCTYPE html> <html lang="en"> <head> &l ...

  4. pickle 模块

    序列化和反序列化的定义 序列化:就是把不可传输的对象转换为可存储或可传输的过程 反序列化:就是把在磁盘,等介质中的数据转换为对象 import pickle #dic={'name':'alex',' ...

  5. 大数相加 Big Num

    代码: #include<stdio.h>#include<algorithm>#include<iostream>#include<string.h> ...

  6. gc笔记2

    空间分配担保:在发生MinorGC之前,虚拟机会检查老年代最大连续可用是否大于新生代所有对象的空间,如果这个条件成立,则minorgc时安全的

  7. proxy_pass根据path路径转发时的"/"问题记录

    在nginx中配置proxy_pass时,如果是按照^~匹配路径时,要注意proxy_pass后的url最后的/.当加上了/,相当于是绝对根路径,则nginx不会把location中匹配的路径部分代理 ...

  8. [Swift]LeetCode123. 买卖股票的最佳时机 III | Best Time to Buy and Sell Stock III

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  9. [Swift]LeetCode241. 为运算表达式设计优先级 | Different Ways to Add Parentheses

    Given a string of numbers and operators, return all possible results from computing all the differen ...

  10. [Swift]LeetCode806. 写字符串需要的行数 | Number of Lines To Write String

    We are to write the letters of a given string S, from left to right into lines. Each line has maximu ...