翼度科技»论坛 编程开发 .net 查看内容

Asp.Net Core中利用过滤器控制Nginx的缓存时间

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
前言

Web项目中很多网页资源比如html、js、css通常会做服务器端的缓存,加快网页的加载速度
一些周期性变化的API数据也可以做缓存,例如广告资源位数据,菜单数据,商品类目数据,商品详情数据,商品列表数据,公共配置数据等,这样就可以省去很多在服务端手动实现缓存的操作
最早资源缓存大部分都用Expires、Cache-Control或Etag实现的,我们可以在WebServer中统一设置响应头,或者指定规则单独设置
以上都是基于Http协议的缓存,如今很多WebServer,例如Nginx和阿里二次开发的Tengine,都是自己的一套缓存实现,通过独有的响应头参数(X-Accel-Expires)来识别控制缓存,优先级是大于Http协议那些的
通常Nginx都是作为代理服务器,反向代理多台源服务器,如果开启了缓存,二次请求到了Nginx就会直接响应给客户端了,能减轻源服务器的压力
本文主要是基于 X-Accel-Expires 来实现缓存的,前提是在Nginx中已经配置了Proxy Cache规则
 
Nginx的缓存原理

1. 这是资源访问路径,通过Nginx反向代理多个源服务器,Nginx中配置了缓存,第二次访问到了Nginx就直接返回了,不会再到后面的源服务器

2. 常见的Http缓存响应头设置有以下几种,其中Etag和Last-Modified是组合使用的,X-Accel-Expires是Nginx独有的参数,优先级高于其他几个设置,值的单位是秒,0为不生效

Nginx缓存识别优先级如下

3. Nginx实现缓存的原理是把Url和相关参数,通过自定义组合作为Key,并使用MD5算法对Key进行哈希,把响应结果存到硬盘上的对应目录,支持通过命令清除缓存


具体可以参考以下文章,非常详细:
https://www.nginx.com/blog/nginx-high-performance-caching/
https://czerasz.com/2015/03/30/nginx-caching-tutorial/
 
代码实现

以下是通过过滤器实现控制该参数,支持在Controller或Action上传入滑动时间,或者固定时间,灵活控制缓存时间
  1.     /// <summary>
  2.     /// 配合nginx缓存
  3.     /// </summary>
  4.     [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method,AllowMultiple = false)]
  5.     public class NginxCacheFilterAttribute : Attribute, IAsyncActionFilter
  6.     {
  7.         /// <summary>
  8.         /// 构造函数
  9.         /// </summary>
  10.         public NginxCacheFilterAttribute() { }
  11.         
  12.         /// <summary>
  13.         /// 固定时间格式正则,例如:00:00 、10:30
  14.         /// <summary>
  15.         static Regex reg = new Regex(@"^(\d{1,2}):(\d{1,2})$",RegexOptions.IgnoreCase);
  16.         /// <summary>
  17.         /// 缓存清除固定时间,new string[] { "00:00", "10:00", "14:00", "15:00" }
  18.         /// </summary>
  19.         public string[] MustCleanTimes { get; set; }
  20.         /// <summary>
  21.         /// 缓存清除滑动时间,默认 300 (5分钟)
  22.         /// </summary>
  23.         public int Period { get; set; } = 300;
  24.         /// <summary>
  25.         /// 请求头变量
  26.         /// </summary>
  27.         const string X_Accel_Expires = "X-Accel-Expires";
  28.         const string ETag = "ETag";
  29.         const string Cache_Control = "Cache-Control";
  30.         /// <summary>
  31.         /// 过滤器执行
  32.         /// </summary>
  33.         /// <param name="context"></param>
  34.         /// <param name="next"></param>
  35.         /// <returns></returns>
  36.         public Task OnActionExecutionAsync(ActionExecutingContext context,ActionExecutionDelegate next)
  37.         {
  38.             //非GET请求,不设置nginx缓存头
  39.             if (context.HttpContext.Request.Method.ToUpper() != "GET") {
  40.                 return next.Invoke();
  41.             }
  42.             var response = context.HttpContext.Response;
  43.             //判断固定时间
  44.             if (MustCleanTimes != null && MustCleanTimes.Length > 0) {
  45.                 var nowTime = DateTime.Now;                  //当前时间
  46.                 var nowYmd = nowTime.ToString("yyyy-MM-dd"); //当前日期
  47.                 List<DateTime> cleanTimes = new List<DateTime>();
  48.                 foreach (var time in MustCleanTimes) {
  49.                     if (reg.IsMatch(time) && DateTime.TryParse($"{nowYmd} {time}",out DateTime _date)) {
  50.                         //已超时的推到第二天,例如设置的是00:00,刷新时间就应该是第二天的00:00
  51.                         if (_date < nowTime)
  52.                             cleanTimes.Add(_date.AddDays(1));
  53.                         else
  54.                             cleanTimes.Add(_date);
  55.                     }
  56.                 }
  57.                 if (cleanTimes.Count > 0) {
  58.                     var nextTime = cleanTimes.OrderBy(o => o).FirstOrDefault(); //下次刷新时间
  59.                     var leftSeconds = nextTime.Subtract(nowTime).TotalSeconds;  //下次刷新剩余秒数
  60.                     if (leftSeconds >= 0 && leftSeconds < Period)
  61.                         Period = (int)leftSeconds;
  62.                 }
  63.             }
  64.             //添加X_Accel_Expires
  65.             if (response.Headers.ContainsKey(X_Accel_Expires)) {
  66.                 response.Headers.Remove(X_Accel_Expires);
  67.             }
  68.             response.Headers.Add(X_Accel_Expires,Period.ToString());
  69.             //添加ETag
  70.             if (response.Headers.ContainsKey(ETag)) {
  71.                 response.Headers.Remove(ETag);
  72.             }
  73.             response.Headers.Add(ETag,new System.Net.Http.Headers.EntityTagHeaderValue($""{DateTime.Now.Ticks.ToString()}"",true).ToString());
  74.             //移除Cache-Control
  75.             response.Headers.Remove(Cache_Control);
  76.             return next.Invoke();
  77.         }
  78.     }
复制代码
 具体的使用方式如下:
1. 全局用法,全局Api都是设置的默认缓存时间,不需要缓存的Api在Controller或Action上单独设置Period=0即可
  1. //在Stratup中全局添加过滤器      
  2. public void ConfigureServices(IServiceCollection services)
  3. {
  4.     services.AddControllers(config => {
  5.         config.Filters.Add<NginxCacheFilterAttribute>();
  6.     });
  7. }
  8. /// <summary>
  9. /// 设置滑动时间
  10. /// Period=0为不生效
  11. /// </summary>
  12. /// <returns></returns>
  13. [HttpGet]
  14. [NginxCacheFilter(Period = 0)]
  15. public HttpResponseMessage TestCache1()
  16. {
  17.     return new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK };
  18. }
复制代码
 2. 局部用法
  1. /// <summary>
  2. /// 设置滑动时间
  3. /// 30秒后自动过期
  4. /// </summary>
  5. /// <returns></returns>
  6. [HttpGet]
  7. [NginxCacheFilter(Period = 30)]
  8. public HttpResponseMessage TestCache1()
  9. {
  10.     return new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK };
  11. }
  12. /// <summary>
  13. /// 设置固定时间
  14. /// 例如:9点第一次请求,一直缓存到10点失效,12点第一次请求,一直缓存到15点失效
  15. /// </summary>
  16. /// <returns></returns>
  17. [HttpGet]
  18. [NginxCacheFilter(MustCleanTimes = new[] { "10:00","15:00","22:00" })]
  19. public HttpResponseMessage TestCache2()
  20. {
  21.     return new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK };
  22. }
复制代码
 
具体效果

1. 我们第一次请求接口,返回200状态码,Nginx在响应头上会返回X-Cache:MISS,代表缓存未命中

2. 第二次请求,会返回304状态码,Nginx在响应头上会返回 X-Cache:HIT,代表已经命中缓存

3. 我们开启Chrome调试中的Disable Cache,这样所有请求的请求头中都会设置 Cache-Control: no-cache,再刷新下接口看下

发现接口返回200状态码,Nginx在响应头上会返回X-Cache:EXPIRED,说明缓存已过期,已从源服务器返回了数据,也说明通过请求头设置Cache-Control为no cache是可以跳过缓存的

更多含义:

 
高性能用法:

proxy_cache_lock:缓存锁
proxy_cache_lock_timeout:缓存锁过期时间
如果给缓存规则设置了proxy_cache_lock,那么该规则下同时进来多个同一个Key的请求,只会有一个请求被转发到后面的源服务器,其余请求会被等待,直到源服务器的内容被成功缓存
可以配合设置proxy_cache_lock_timeout,设置一个缓存锁的过期时间,这样其余请求如果等待超时了,也会被释放请求到后面的源服务器
通过这两个参数的组合使用,可以有效避免同一个请求大量打入时,瞬间压垮后面的源服务器

 
原创作者:Harry
原文出处:https://www.cnblogs.com/simendancer/articles/17109964.html

来源:https://www.cnblogs.com/simendancer/archive/2023/02/10/17109964.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具