有没有一种方法可以利用.NET核心中的会话化存储而无需二进制序列化程序的开销?

时间:2019-03-20 23:57:57

标签: c# asp.net-core

.p.net核心应用程序中的会话存储要求您先将其转换为字节数组,然后再检索它。与.net框架存储相比,这些序列化和反序列化操作的开销会导致大量开销。 .Net框架应用程序中耗时5ms的操作在.Net核心中耗时850ms以上。我需要能够以高性能的方式从服务器缓存中存储和检索大量数据的能力,类似于我们可以在.Net框架中利用会话化存储的方式。

我们的应用程序使用了很多较大的ADO.NET数据表。它们包含数千行和数十列的情况并不少见。过去,我们使用带有会话存储的.Net框架来快速检索与会话之间的ADO.NET数据表对象。

DataTable dt = new DataTable();
HttpContext.Current.Session["data"] = dt; // store in session
dt = (DataTable)HttpContext.Current.Session["data"]; // retrieve from session

我们也有自己的自定义类,这些类具有数据表作为成员。这些类以不同的方式处理数据。我们还从会话中存储和检索它们。

[Serializable]
 MyClass {
    public DataTable dt;
 }

在我们的.NET Framework应用程序上,典型的筛选和分页数据请求大约需要5毫秒的往返时间。会话访问非常快,并且每次获取和设置操作的性能损失都可以忽略不计。

我们一直在尝试过渡到.NET Core,后者对会话的管理略有不同。首先必须将其转换为字节数组,而不是能够在会话中存储和检索任何对象。我正在使用带有少量逻辑的二进制格式化程序来处理获取和设置操作。下面的代码效率不高,但是到目前为止,最大的瓶颈是retrieveData方法中的反序列化操作。

Public class SessionManager : ISessionManager
    {
        private readonly IHttpContextAccessor _contextAccessor;
        public SessionManager(IHttpContextAccessor contextAccessor) {
            _contextAccessor = contextAccessor;
         }
        public T get<T>(string key)
        {
            object data = retrieveData(key);
            return data == null ? default(T) : (T)data;
        }

        public void set(string key, object data)
        {
            serializeBinary(key, data); 
        }

        public void remove(string key) {
            _contextAccessor.HttpContext.Session.Remove(key);
        }

        private void serializeBinary(string key, object data) {
            BinaryFormatter bf = new BinaryFormatter();
            using (var ms = new MemoryStream())
            {
                bf.Serialize(ms, data);
                var bytes = ms.ToArray();
                _contextAccessor.HttpContext.Session.Set(key, bytes);
            }
        }

        private object retrieveData(string key) {
            byte[] data = null;
            _contextAccessor.HttpContext.Session.TryGetValue(key, out data);
            if (data == null) return null;
            using (MemoryStream ms = new MemoryStream(data))
            {
                IFormatter br = new BinaryFormatter();
                return br.Deserialize(ms);
            }
        }
    }
}

用法:

MyClass c;
c.dt = _myRepo.getLotsOfData();
_SessionManager.set("data", c);
c = _SessionManager.get<MyClass>("data");

与使用5ms的.Net Framework应用程序相比,使用相同的类在DataTables上执行的相同分页和筛选操作完成时间在850ms-950ms之间。在Visual Studio中对性能进行性能分析时,反序列化操作占用了当时的大部分时间,仅此操作就花费了600ms。

我知道比二进制格式化程序快得多的其他库(例如protobuf),这可能是我接下来要去的地方。但是,如果将反序列化时间减少到300ms甚至200ms,与.Net Framework相比,我仍然会损失很多性能。

在我看来,需要一种不同的缓存策略。是否有一种方法可以在.Net核心应用程序中存储和检索数据,而不需要先对数据进行序列化和反序列化的开销?

2 个答案:

答案 0 :(得分:2)

  

我们的应用程序使用了很多较大的ADO.NET数据表。它们包含数千行和数十列的情况并不少见。

我认为我们已经找到问题了:)

建议:

  1. 不要尝试以这种状态对大型数据集进行序列化;他们太贵了
  2. dataSet.RemotingFormat = System.Data.SerializationFormat.Binary;中的DataSet序列化之前启用BinaryFormatter
  3. 停止使用DataSet并停止使用BinaryFormatter;我并不是说这个很松散-它们非常昂贵,因此如果数据确实很固定,请考虑将POCO类型与其他序列化程序一起使用-protobuf可能会很多在CPU和带宽(数据量)方面都更加有效

答案 1 :(得分:0)

我最终按照JohanP的建议使用了IMemoryCache。 IMemoryCache是​​系统存储,不需要您序列化对象即可存储和检索它们。

我为会话配置了我的应用程序,因此.NET将继续为会话ID提供cookie。然后,在存储或检索项目之前,我将会话ID附加到用户提供的键上,这或多或少是在.NET框架上管理会话的方式。

public class SessionManager : ISessionManager
    {
        private readonly IHttpContextAccessor _contextAccessor;
        private readonly IMemoryCache _cache;
        public SessionManager(IHttpContextAccessor contextAccessor
                            , IMemoryCache cache
        ) {
            _contextAccessor = contextAccessor;
            _cache = cache;
         }
        public T get<T>(string key)
        {
           object data;
           _cache.TryGetValue(buildSessionKey(key), out data);
           return data == null ? default(T) : (T)data;
        }

        public void set(string key, object data, TimeSpan maxLifeTime = default(TimeSpan))
        {
            TimeSpan adjustLifeTime = maxLifeTime.TotalMinutes < 5 ? TimeSpan.FromMinutes(20) : maxLifeTime;
            if (adjustLifeTime.TotalMinutes > 90) adjustLifeTime = TimeSpan.FromMinutes(90);
            MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromMinutes(20))
            .SetAbsoluteExpiration(adjustLifeTime);
            _cache.Set(buildSessionKey(key), data);
        }

        public void remove(string key) {
            _cache.Remove(buildSessionKey(key));
        }

        private string buildSessionKey(string partialKey) {
            string sessionID = _contextAccessor.HttpContext.Session.Id;
            return sessionID + partialKey;
        } 
    }

Startup.cs

        services.AddMemoryCache(options => 
        {
            // options.SizeLimit = 4096;
            options.ExpirationScanFrequency = TimeSpan.FromMinutes(20);
        });
        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromMinutes(20);
            options.Cookie.HttpOnly = true;
            options.Cookie.IsEssential = true;
        });

使用这种方法,性能类似于.NET Framework应用程序。