Есть ли способ использовать сессионное хранилище в ядре .NET без накладных расходов двоичных сериализаторов? - PullRequest
2 голосов
/ 21 марта 2019

Обработка сеанса в основных приложениях .net требует, чтобы вы сначала преобразовали его в байтовый массив перед его извлечением.По сравнению с хранилищем .net Framework накладные расходы на эти операции сериализации и десериализации влекут за собой значительные накладные расходы.Операции, которые занимают 5 мс в наших приложениях .Net Framework, занимают более 850 мс в ядре .Net.Мне нужна возможность хранить и извлекать довольно большие объемы данных из кэша сервера высокопроизводительным способом, аналогичным тому, как мы можем использовать сессионное хранилище в .Net Framework.

Наши приложения используют много довольно больших массивов данных ADO.NET.Они нередко содержат тысячи строк и десятки столбцов.В прошлом мы использовали .Net Framework с хранилищем сеансов для быстрого извлечения объектов 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, который управляет сессией немного по-другому.Вместо того, чтобы сохранять и извлекать любой объект в сеансе, я сначала должен преобразовать его в байтовый массив.Я использую двоичный форматер с небольшой логикой для обработки операций get и set.Приведенный ниже код не так эффективен, как мог бы быть, но, безусловно, самым большим узким местом является операция десериализации в методе 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");

Те же самые операции подкачки и фильтрации, которые мы выполняем для DataTables с использованием тех же классов, занимают от 850 мс до 950 мс по сравнению с нашими приложениями .Net Framework.которые занимают 5 мс.Профилируя производительность в визуальной студии, операция десериализации заняла львиную долю того времени, в 600 мс только для этой операции.

Мне известны другие библиотеки (например, protobuf), которые работают намного быстрее двоичного форматера, и, вероятно, я пойду дальше.Однако, если я уменьшу время десерилизации до 300 мс или даже 200 мс, я все равно потеряю большую производительность по сравнению с .Net Framework.

Мне кажется, что требуется другой тип стратегии кэширования.Есть ли способ хранить и извлекать данные в основных приложениях .Net, который не требует дополнительных затрат на сериализацию и десериализацию данных в первую очередь?

Ответы [ 2 ]

2 голосов
/ 21 марта 2019

Наши приложения потребляют много больших данных ADO.NET. Они нередко содержат тысячи строк и десятки столбцов.

Я думаю, что мы нашли проблему:)

Предложения:

  1. не пытайтесь сериализовать огромные наборы данных в таком состоянии; они просто слишком дороги
  2. включить dataSet.RemotingFormat = System.Data.SerializationFormat.Binary; перед сериализацией DataSet в BinaryFormatter
  3. прекратить использование DataSet и прекратить использование BinaryFormatter; Я не говорю об этом свободно - они довольно дорогие, поэтому , если данные на самом деле довольно фиксированные , рассмотрите возможность использования типа POCO с другим сериализатором - protobuf будет намного более эффективным в обоих процессорах и пропускной способности (объем данных)
0 голосов
/ 21 марта 2019

В итоге я использовал IMemoryCache в соответствии с рекомендациями JohanP.IMemoryCache - это системное хранилище, которое не требует сериализации ваших объектов для их хранения и извлечения.

Я оставил свое приложение настроенным для сеанса, чтобы .NET продолжал предоставлять cookie для идентификатора сеанса.Затем я добавляю идентификатор сеанса к предоставленному пользователем ключу перед сохранением или извлечением элемента, что более или менее соответствует управлению сеансом в .NET Framework.

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...