zoukankan      html  css  js  c++  java
  • .NET跨平台之旅:基于.NET Core改写EnyimMemcached,实现Linux上访问memcached缓存团队

    注:支持 .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跨平台之旅迈上了一个新台阶。

  • 相关阅读:
    Begin Example with Override Encoded SOAP XML Serialization
    State Machine Terminology
    How to: Specify an Alternate Element Name for an XML Stream
    How to: Publish Metadata for a WCF Service.(What is the Metadata Exchange Endpoint purpose.)
    Beginning Guide With Controlling XML Serialization Using Attributes(XmlSerializaiton of Array)
    Workflow 4.0 Hosting Extensions
    What can we do in the CacheMetaData Method of Activity
    How and Why to use the System.servicemodel.MessageParameterAttribute in WCF
    How to: Begin Sample with Serialization and Deserialization an Object
    A Test WCF Service without anything of config.
  • 原文地址:https://www.cnblogs.com/cmt/p/5024508.html
Copyright © 2011-2022 走看看