有這個議題是既有系統的 Cache 邏輯在 Cache 沒有命中的時候,會啟動 lock 機制,然後去執行一個由呼叫端傳進來的 delegate function 去後端資料庫重新取得資料,可是我們都知道每家公司多多少少都有遺留一些「初學者程式碼」,這些初學者程式碼不一定是初學者寫的,但它有時候執行的效能並不是很好,在這種情況再搭配 lock 機制之下,後面進來的 Request 就塞住了,進而影響客戶端的響應速度。
較為適當的做法是去 Review 效能不彰的 delegate function,然後改善它,但是這時候「XX 不允許」(XX 可以帶入時間、成本、老闆……等)就會跳出來阻止我們,永遠都有相對更重要的事情要去做,所以別再相信什麼以後再…、晚一點再…、等…的時候再… 的說法,依據 LeBlanc's Law(勒布朗克法則):Later equals never
,有些事現在不做以後就不會做了。
我們針對這一類的 delegate function 只好先插管,這邊的做法是將目標 delegate function 與 Web Application 解耦,不要放在 Web Application 上呼叫,在 Cache 快要到期的時候,丟給另一個應用程式去執行並同時更新 Cache。
建立 DelegateWrapper<T> 類別
為了能夠完全掌握 Client 端傳進來的 delegate function 資訊,所以需要調整一下 Client 端的呼叫方式,原本的設計是讓 Clinet 傳遞型別為 Func<TResult>
的 delegate function,現在將它改為傳遞 DelegateWrapper<T>
這個型別的物件,將 Method 及 Parameters 拆開。
[Serializable]
public class DelegateWrapper<TResult>
{
private readonly Delegate func;
private readonly string[] jsonParameters;
public DelegateWrapper(Delegate func, params object[] parameters)
{
this.func = func;
this.jsonParameters = parameters.Select(x => JsonConvert.SerializeObject(x)).ToArray();
}
private object[] Arguments
{
get
{
return this.func.Method.GetParameters()
.Select((p, i) => JsonConvert.DeserializeObject(this.jsonParameters[i], p.ParameterType))
.ToArray();
}
}
public TResult Invoke()
{
return (TResult)this.func.GetType().GetMethod("Invoke").Invoke(this.func, this.Arguments);
}
}
有一個 Attribute - [Serializable]
很重要,我們需要把目標 Method 的聲明類別(DeclaringType
)也標上 [Serializable] Attribute,而原本 Method 的參數也需要標上 [Serializable] Attribute,但是這邊我借助 Newtonsoft.Json 的力量將參數一個一個序列化為 Json,在 Invoke 的時候根據 Method 本身提供的 ParameterType 再將參數一個一個反序列化回來,原因是參數的型別眾多,我懶得去把參數的型別也一個一個標上 [Serializable] Attribute。
怎麼用?
我假設有一個 Client - FuncSerialization
,透過 CacheLibrary
來取得 Cache,當 Cache 快要到期的時候將 delegate function 序列化為二進位資料。
由 FuncSerialization 呼叫 CacheProvider.GetCache() 方法,給入 CacheKey
及 DelegateWrapper<T>
參數,而 DelegateWrapper 則包裝了一個 CalculatorService.Add
方法及其參數值。
Cache 即將在 1 分鐘之內到期時,將 delegate function 序列化為二進位資料,我這邊暫時以 CacheKey 為檔名,將二進位資料以檔案的方式傳遞。
切換到 FuncDeserialization
將二進位資料檔案讀出來呼叫 Invoke()
方法取得執行結果,記得 FuncDeserialization 需要參考 CacheLibrary 及 ServiceLibrary 組件。
以上都是使用具名型別、方法,我在網路上有看到有人實作了 ISerializable 介面,對匿名的方法做序列化,挺威的,但套用到我場景在 FunDeserialize 反序列化之後卻遺失了參數,對這方面有興趣的朋友可以參考這篇文章 Anonymous Method Serialization,歡迎交流。
< Source Code >