前言
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上傳入滑動時間,或者固定時間,靈活控制快取時間
/// <summary>
/// 配合nginx快取
/// </summary>
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method,AllowMultiple = false)]
public class NginxCacheFilterAttribute : Attribute, IAsyncActionFilter
{
/// <summary>
/// 建構函式
/// </summary>
public NginxCacheFilterAttribute() { }
/// <summary>
/// 固定時間格式正則,例如:00:00 、10:30
/// <summary>
static Regex reg = new Regex(@"^(\d{1,2}):(\d{1,2})$",RegexOptions.IgnoreCase);
/// <summary>
/// 快取清除固定時間,new string[] { "00:00", "10:00", "14:00", "15:00" }
/// </summary>
public string[] MustCleanTimes { get; set; }
/// <summary>
/// 快取清除滑動時間,預設 300 (5分鐘)
/// </summary>
public int Period { get; set; } = 300;
/// <summary>
/// 請求頭變數
/// </summary>
const string X_Accel_Expires = "X-Accel-Expires";
const string ETag = "ETag";
const string Cache_Control = "Cache-Control";
/// <summary>
/// 過濾器執行
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public Task OnActionExecutionAsync(ActionExecutingContext context,ActionExecutionDelegate next)
{
//非GET請求,不設定nginx快取頭
if (context.HttpContext.Request.Method.ToUpper() != "GET") {
return next.Invoke();
}
var response = context.HttpContext.Response;
//判斷固定時間
if (MustCleanTimes != null && MustCleanTimes.Length > 0) {
var nowTime = DateTime.Now; //當前時間
var nowYmd = nowTime.ToString("yyyy-MM-dd"); //當前日期
List<DateTime> cleanTimes = new List<DateTime>();
foreach (var time in MustCleanTimes) {
if (reg.IsMatch(time) && DateTime.TryParse($"{nowYmd} {time}",out DateTime _date)) {
//已超時的推到第二天,例如設定的是00:00,重新整理時間就應該是第二天的00:00
if (_date < nowTime)
cleanTimes.Add(_date.AddDays(1));
else
cleanTimes.Add(_date);
}
}
if (cleanTimes.Count > 0) {
var nextTime = cleanTimes.OrderBy(o => o).FirstOrDefault(); //下次重新整理時間
var leftSeconds = nextTime.Subtract(nowTime).TotalSeconds; //下次重新整理剩餘秒數
if (leftSeconds >= 0 && leftSeconds < Period)
Period = (int)leftSeconds;
}
}
//新增X_Accel_Expires
if (response.Headers.ContainsKey(X_Accel_Expires)) {
response.Headers.Remove(X_Accel_Expires);
}
response.Headers.Add(X_Accel_Expires,Period.ToString());
//新增ETag
if (response.Headers.ContainsKey(ETag)) {
response.Headers.Remove(ETag);
}
response.Headers.Add(ETag,new System.Net.Http.Headers.EntityTagHeaderValue($"\"{DateTime.Now.Ticks.ToString()}\"",true).ToString());
//移除Cache-Control
response.Headers.Remove(Cache_Control);
return next.Invoke();
}
}
具體的使用方式如下:
1. 全域性用法,全域性Api都是設定的預設快取時間,不需要快取的Api在Controller或Action上單獨設定Period=0即可
//在Stratup中全域性新增過濾器
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(config => {
config.Filters.Add<NginxCacheFilterAttribute>();
});
}
/// <summary>
/// 設定滑動時間
/// Period=0為不生效
/// </summary>
/// <returns></returns>
[HttpGet]
[NginxCacheFilter(Period = 0)]
public HttpResponseMessage TestCache1()
{
return new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK };
}
2. 區域性用法
/// <summary>
/// 設定滑動時間
/// 30秒後自動過期
/// </summary>
/// <returns></returns>
[HttpGet]
[NginxCacheFilter(Period = 30)]
public HttpResponseMessage TestCache1()
{
return new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK };
}
/// <summary>
/// 設定固定時間
/// 例如:9點第一次請求,一直快取到10點失效,12點第一次請求,一直快取到15點失效
/// </summary>
/// <returns></returns>
[HttpGet]
[NginxCacheFilter(MustCleanTimes = new[] { "10:00","15:00","22:00" })]
public HttpResponseMessage TestCache2()
{
return new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK };
}
具體效果
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