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
- 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)
- 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;
}