文章出處

注:支持 .NET Core 的 memcached 客戶端 EnyimMemcachedCore 的 NuGet 包下載地址:https://www.nuget.org/packages/EnyimMemcachedCore

經過一周的努力,我們的“.NET跨平臺之旅”取得了一個重要的進展——基于.NET Core改寫了開源的memcached .NET客戶端EnyimMemcached,實現了Linux上訪問memcached緩存,解決了跨平臺.NET的緩存問題。

針對我們的應用場景,將實際應用遷移到部署在Linux服務器上的跨平臺.NET(.NET Core)有兩大障礙:一個障礙是Linux上訪問SQL Server數據庫,一個障礙是Linux上訪問memcached緩存。第一個問題在苦等之后,終于被微軟解決了,詳見 .NET跨平臺之旅:升級至ASP.NET 5 RC1,Linux上訪問SQL Server數據庫;而第二個問題,微軟還沒開始解決,目前的ASP.NET 5緩存組件只支持進程內的內存緩存與redis,不支持memcached。但我們不想苦等了,選擇了自己動手、豐衣足食,嘗試自己解決這個問題。

我們用的memcached緩存客戶端是EnyimMemcached,之前對它進行過異步化改造,對源代碼有些了解。用dnx基于.NET Core編譯EnyimMemcached的源代碼,出現了300多個編譯錯誤,當時有點望而卻步,但后來還是下定決心解決這些編譯錯誤。

一類編譯錯誤是對System.Configuration程序集的依賴,EnyimMemcached的配置是放在web.config中的,有不少代碼依賴System.Configuration。而ASP.NET 5中根本沒有web.config這個東東,corefx中自然也就沒有System.Configuration的實現。為了解決這個問題,我們暫時放棄使用配置文件,通過硬編碼進行配置,添加的主要代碼如下:

IMemcachedClientConfiguration configuration = new MemcachedClientConfiguration(_loggger); 
configuration.SocketPool.MinPoolSize = 20;
configuration.SocketPool.MaxPoolSize = 1000;
configuration.SocketPool.ConnectionTimeout = new TimeSpan(0, 0, 3);
configuration.SocketPool.ReceiveTimeout = new TimeSpan(0, 0, 3);
configuration.SocketPool.DeadTimeout = new TimeSpan(0, 0, 3);

一類編譯錯誤是corefx(.NET Core Framework)中程序集的變化,比如:

  • IPEndPoint跑到了System.Net.Primitives程序集中
  • System.Security.Cryptography.HashAlgorithm跑到了System.Security.Cryptography.Algorithms程序集中
  • System.Threading.Timer成為了一個獨立的程序集

一類編譯錯誤是corefx中類庫的變化,比如沒有了System.Net.Dns.GetHostEntry(),需要改用System.Net.NameResolution程序集中的System.Net.Dns.GetHostAddressesAsync()。

還有一類最頭疼的編譯錯誤是corefx中沒有二進制序列化(BinaryFormatter)的實現,而對于EnyimMemcached來說這是關鍵部分,對象的緩存讀寫全靠二進制序列化與反序列化。針對這個問題,我們改用Json.NET進行bson序列化與反序列化。

序列化實現代碼如下:

protected virtual ArraySegment<byte> SerializeObject(object value)
{
    using (var ms = new MemoryStream())
    {
        using (BsonWriter writer = new BsonWriter(ms))
        {
            JsonSerializer serializer = new JsonSerializer();
            serializer.Serialize(writer, value);
            return new ArraySegment<byte>(ms.ToArray(), 0, (int)ms.Length);
        }                
    }
}

反序列化實現代碼如下:

T ITranscoder.Deserialize<T>(CacheItem item)
{
    if (item.Data == null || item.Data.Count == 0) return default(T);

    using (var ms = new MemoryStream(item.Data.ToArray()))
    {
        using (BsonReader reader = new BsonReader(ms))
        {
            if(typeof(T).GetTypeInfo().ImplementedInterfaces.Contains(typeof(IEnumerable)))
            {
                reader.ReadRootValueAsArray = true;
            }
            JsonSerializer serializer = new JsonSerializer();
            return serializer.Deserialize<T>(reader);
        }
    }
}

Json.NET的bson反序列有個麻煩的地方,對于集合類型需要專門設置ReadRootValueAsArray的值為true。當時在這個地方折騰了不少時間,沒找到好的解決方法,只能用反射實現,就是上面代碼中的 typeof(T).GetTypeInfo().ImplementedInterfaces.Contains(typeof(IEnumerable))

在解決了這些編譯錯誤并在開發環境中測試通過之后,我們就將改造后的EnyimMemcached應用到“.NET的跨平臺之旅”的示例站點(http://about.cnblogs.com/)上,調用代碼如下:

public class TabNavService : ITabNavService
{
    private ITabNavRepository _tabNavRepository;
    private IMemcachedClient _memcachedClient;

    public TabNavService(
        ITabNavRepository tabNavRepository,
        IMemcachedClient memcachedClient)
    {
        _tabNavRepository = tabNavRepository;
        _memcachedClient = memcachedClient;
    }

    public async Task<IEnumerable<TabNav>> GetAll()
    {
        var result = await _memcachedClient.GetAsync<IEnumerable<TabNav>>(cacheKey);
        if(!result.Success)
        {
            var tabNavs = await _tabNavRepository.GetAll();
            await _memcachedClient.StoreAsync(StoreMode.Add, cacheKey, tabNavs, new TimeSpan(0, 0, 300));
            return tabNavs;
        }
        return result.Value;
    }
}

_memcachedClient是通過“依賴注入”注入的,但是在注入時,我們自己給自己挖了一個坑:

services.AddTransient<IMemcachedClient, MemcachedClient>();

加了memcached緩存功能之后,示例站點在一臺測試服務器(只有當前一個請求,沒有其它請求)上運行正常,緩存讀寫正常。

但是一發布到about.cnblogs.com的正式服務器上(有多個請求),請求發出后就一直處于等待狀態,服務器無任何響應。在原以為大功告成的時刻卻卡在了這個奇怪的問題上,這個滋味你懂的。

折騰了半天,實在找不到原因,找了個替罪羊——可能是corefox中System.Net.Sockets在Linux上的實現對并發請求的處理有問題,準備暫時放棄。

就在這一刻,突然想到,MemcachedClient的構造函數中有初始化socket pool的操作,每創建一個MemcachedClient的實例時都要創建好多到memcached服務器的socket連接。難道是在注入時忘了使用單例,造成每個請求都要創建MemcachedClient的實例,太多的socket連接讓kestrel服務器不堪重負。想到這一點,立馬跑到電腦前打開代碼一看,立馬發現自己的坑坑自己不淺。改為單例后,問題立馬解決。

services.AddSingleton<IMemcachedClient, MemcachedClient>();

于是,運行在Linux服務器上的示例站點(http://about.cnblogs.com/)用上了memached緩存;于是,寫了這篇博文分享自己坑自己的過程;于是,我們的.NET跨平臺之旅邁上了一個新臺階。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()