#c# #asp.net-core
#c# #asp.net-core
Вопрос:
Порядок работы сеанса в приложениях .net core требует, чтобы вы сначала преобразовали его в массив байтов, прежде чем извлекать его. По сравнению с хранилищем .net Framework, накладные расходы на эти операции сериализации и десериализации сопряжены со значительными накладными расходами. Операции, которые занимают 5 мс в нашем .Приложения Net Framework занимают более 850 мс.Сетевое ядро. Мне нужна возможность хранить и извлекать довольно большие объемы данных из кэша сервера высокопроизводительным способом, аналогично тому, как мы можем использовать сессионное хранилище в .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");
Те же операции подкачки и фильтрации, которые мы выполняем над таблицами данных, используя те же классы, занимают от 850 до 950 мс по сравнению с нашими приложениями .Net Framework, которые занимают 5 мс. При профилировании производительности в Visual Studio операция десериализации заняла львиную долю этого времени, 600 мс только для этой операции.
Я знаю о других библиотеках (таких как protobuf), которые намного быстрее, чем двоичный форматировщик, и, вероятно, к этому я перейду следующим. Однако, если я уменьшу время десерилизации до 300 мс или даже 200 мс, я все равно значительно потеряю производительность по сравнению to.Net Фреймворк.
Мне кажется, что требуется стратегия кэширования другого типа. Есть ли способ хранить и извлекать данные в .Приложения Net Core, которые не требуют дополнительных затрат на сериализацию и десериализацию данных в первую очередь?
Комментарии:
1. Вместо
Session
того, что вы можете попробоватьIMemoryCache
, вы можете вставить в это любой объект.2. Спасибо, Йохан, я попробую. Похоже, мне нужно создать собственное управление сеансами, построенное на кеше памяти, поскольку это системный кеш, а не кеш сеанса. Это рекомендуемый подход?
3. Да, вам придется заставить ключи вашего кэша использовать некоторый уникальный идентификатор пользователя, userid или что-то еще, что идентифицирует пользователя в данный момент. В противном случае используйте подход Марка Гравелла
Ответ №1:
Наши приложения потребляют много несколько больших ADO.NET таблицы данных. Нередко они содержат тысячи строк и десятки столбцов.
Я думаю, мы нашли проблему 🙂
Предложения:
- не пытайтесь сериализовать огромные наборы данных в таком состоянии; они просто слишком дороги
- включите
dataSet.RemotingFormat = System.Data.SerializationFormat.Binary;
перед сериализациейDataSet
вBinaryFormatter
- прекратите использовать
DataSet
и прекратите использоватьBinaryFormatter
; Я не говорю это свободно — они довольно дороги, поэтому, если данные действительно довольно фиксированы, рассмотрите возможность использования типа POCO с другим сериализатором — protobuf был бы намного эффективнее как с точки зрения процессора, так и с точки зрения пропускной способности (объем данных)
Комментарии:
1. Я полностью разделяю мнение о наборах данных, и мы рассматривали возможность отказа от них в прошлом. Данные, которые используют наши клиенты, довольно специфичны и требуют множества очень дорогих аналитических операций. Мы пытались использовать статически типизированные наборы данных раньше, но мы сталкиваемся с нагрузками на обслуживание и проблемами с нашими наиболее динамичными данными, из-за чего компромиссы не стоят того. Я рассмотрю ваше второе предложение и посмотрю, поможет ли это, спасибо.
Ответ №2:
В итоге я использовал 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.