dotnet 快取

蝸牛向海發表於2023-02-21

Net 內建記憶體快取

asp.net 中是有快取的實現:HttpContext.Cache,快取的資料是放到 Web 伺服器的程式 記憶體裡。

在控制檯、WinForm、子執行緒、SignalR 等不支援 HttpContext 的地方還可以使用 MemoryCache.Default(System.Runtime.Caching 這個程式集中) ,HttpContext.Cache 其實就是 對 MemoryCache 的封裝。

//寫入:
MemoryCache.Default.Add("age", 666, DateTimeOffset.Now.AddMinutes(1)); 
//讀取: 
if(MemoryCache.Default.Contains("name")) 
{  
    int age = (int)MemoryCache.Default["age"];                
} 

程式內快取最大的優點就是效率高。在可預期資料量不大的情況下推薦使用。 如果資料量比較大或者叢集伺服器比較多,就要用單獨的分散式快取了,也就是搞一臺 或者多臺專門伺服器儲存快取資料,所有伺服器都訪問分散式快取伺服器。

Memcached

簡介

Memcached 是一個專門用來做快取的伺服器,而且快取的資料都在記憶體中。Memcached 就相當於一個 Dictionary 鍵值對集合,儲存的是鍵值對,然後根據 key 取 value。 當然 web 伺服器和 Memcached 之間還是要網路間通訊,效率還是沒有程式內快取效率 高。Memcached 程式重啟之後資料就會消失。

安裝

memcached.exe -d install

解除安裝

memcached.exe -d uninstall

.Net 連線 memcached 安裝

Memcached 的.Net 開發包:Install-Package EnyimMemcached

  1. Memcache 存入的是鍵值對。Memcache 存入資料的 3 中模式 Set、Replace、Add,根據名 字就能猜出來:
  • Set:存在則覆蓋,不存在則新增
  • Replace:如果存在則覆蓋,並且返回 true;如果不存在則不處理,並且返回 false;
  • Add:如果不存在則新增,並且返回 true;如果存在則不處理,並且返回 false;

沒特殊要求一般用 Set 就可以了。

MemcachedClientConfiguration mcConfig = new MemcachedClientConfiguration(); mcConfig.AddServer("127.0.0.1:11211");
//必須指定埠 
using (MemcachedClient client = new MemcachedClient(mcConfig)) 
{  
    client.Store(Enyim.Caching.Memcached.StoreMode.Set, "name", "rsfy"); 
} 

如果儲存普通類物件,則物件必須可序列化(不同 Memcached 客戶端儲存物件的機制 都不盡相同)。

2)存入設定過期時間 設定最後一個 TimeSpan 型別的引數:

client.Store(Enyim.Caching.Memcached.StoreMode.Set, "name", "yzk",TimeSpan.FromSeconds(5)); 

如果之前對於同一個 Key 設定過一個過期時間,之後又設定過一個,以最後一次的為準。

3)讀取:如果找不到,則返回 null

client.Get("name");

當然也可以用

public bool TryGet(string key, out object value)

當然還支援泛型的

public T Get<T>(string key)
  1. Remove(string key)則是刪除一個 key 對應的內容。 Key 的長度最高是 250 個字元,Value 最長 1M。 與 Store、Get、Remove 配套的還有 ExecuteXXX 方法,唯一區別就是返回值資訊更詳細。

5)Key 的選擇: Memcaced就相當於一個大鍵值對,不同系統放到Memcached中的資料都是不隔離的, 因此設定 Key 的時候要選擇好 Key,這樣就不容易衝突。建議規則“系統名字_模組名字_業務_Key”,比如“Shop_Admin_FilterWords”

6) Increment、Decrement 是用來對計數器進行增減的,不過用得少。用 Redis 更合適。

Cas 操作:

用來解決併發問題:讀出一個值,做一些判斷或者處理,再寫回,有可能有併發的問題。 Cas 是 Memcached 1.2.5 之後引入的特性,類似於資料庫的“樂觀鎖”,查詢的時候查出一個 cas 值,在寫入的時候帶著這個 cas 值,如果發現 cas 值已經變了,則說明已經有別人改過了。 下面的程式:

var cas = client.GetWithCas("Name"); 
Console.WriteLine("按任意鍵繼續");
Console.ReadKey();
var res = client.Cas(Enyim.Caching.Memcached.StoreMode.Set, "Name", cas.Result + "1", cas.Cas); 
if(res.Result) {  
    Console.WriteLine("修改成功"); 
} else {  
    Console.WriteLine("被別人改了"); 
}  

啟動兩個例項,測試效果。 Memcached 一般就是做快取用,因此也不要用這個 Cas。

memcached 的叢集

memcached 重啟之後短時間內大量的請求會湧入資料庫,給資料庫造成壓力,解決這 個的方法就是使用叢集,有多臺 Memcached 伺服器提供服務。 當 memcached 伺服器壓力大了之後也有必要搞 memcached 叢集來分擔壓力。 Memcached 伺服器的“雪崩”問題:如果所有快取設定過期時間一樣,那麼每隔一段 時間就會造成一次資料庫訪問的高峰:

**解決的方法就是快取時間設定不一樣,比如加上一個隨機數。 **

Memcached 的叢集實現很簡單,叢集節點直接不進行通訊、同步,只要在多個伺服器 上啟動多個 Memcached 伺服器即可,客戶端決定把資料寫入不同的例項,不搞主從複製, 每個資料庫例項儲存一部分內容。 然後 mcConfig.AddServer("127.0.0.1:11211");新增多個伺服器 ip 地址,然後客戶端根據 自己的演演算法決定把資料寫入哪個 Memcached 例項,取資料庫的時候再根據同樣的定位演演算法 去哪臺伺服器上去取。 節點定位演演算法有很多種,最常用的有兩種 Ketama、VBucket。Ketama 是根據 Key 算出一 個 hash 值,根據 hash 值再算到伺服器;而 VBucket 也是根據 key 算出 hash 值,但是不是直 接根據 hash 值算出服務地址,而是維護一個 VBucket 表,在表中指定不同的 hash 值由不同 的伺服器處理,還可以臨時改變指向。建議用 Ketama 就可以了。節點定位演演算法會自動處理 故障伺服器。

mcConfig.NodeLocatorFactory = new KetamaNodeLocatorFactory()。

快取要求都 不高。

Memcached 封裝示例

/// <summary>
/// 獲取token
/// </summary>
/// <param name="args"></param>
/// <returns>token</returns>
/// <exception cref="ArgumentException"></exception>
public static string JwtEncoding(Dictionary<string, object> args)
{
    var payload = args.Count == 0 ? throw new ArgumentException($"{nameof(args)}長度為0") : args;
    var secret = ConfigurationManager.AppSettings["Jwt"];
    IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
    IJsonSerializer serializer = new JsonNetSerializer();
    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
    IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
    var token = encoder.Encode(payload, secret);
    return token;
}
/// <summary>
/// 解token
/// </summary>
/// <param name="token">token</param>
/// <returns></returns>
public static Dictionary<string, object> JwtDecoding(string token)
{
    Dictionary<string, object> data = null;
    try
    {
        var secret = ConfigurationManager.AppSettings["Jwt"];
        var serializer = new JsonNetSerializer();
        var algorithm = new HMACSHA256Algorithm();
        IDateTimeProvider provider = new UtcDateTimeProvider();
        IJwtValidator validator = new JwtValidator(serializer, provider);
        var urlEncoder = new JwtBase64UrlEncoder();
        IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
        var json = decoder.Decode(token, secret, verify: true);
        data = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
    }
    catch (TokenExpiredException)
    {
        Console.WriteLine("Token has expired");
        return null;
    }
    catch (SignatureVerificationException)
    {
        Console.WriteLine("Token has invalid signature");
        return null;
    }
    catch (Exception e)
    {
        return null;
    }
    return data;
}

相關文章