Webservice Cache для длительных процессов Идеи дизайна - PullRequest
0 голосов
/ 16 января 2019

Я ищу несколько дизайнерских идей

У меня есть веб-служба ASP.Net, которую использует веб-сайт. Один из вызовов занимает около 13 секунд, чтобы получить около 70000 строк. 4 секунды на БД и 9 секунд на обработку на веб-сервере, поскольку обработка каждой записи выполняется. Это было оптимизировано, насколько я могу, и было сбито с оригинальных 42 секунд.

Данные не меняются так часто, поэтому моя идея - создать кеш на веб-сервисе и опросить по таймеру, чтобы обновлять этот кеш каждые 30 секунд или около того. Затем вызов веб-службы извлекает обработанные записи из кэша

Я ищу дизайнерские идеи для лучшего подхода к этому. Я понимаю, что в ASP.Net есть словарь кэша ввода, но это не решило бы проблему опроса, поэтому мне бы не понадобился синглтон, тогда у меня возникнут возможные проблемы с многопоточностью.

В замешательстве и неуверенности в том, что я нахожусь в правильных строках или мне следует рассчитывать данные и сохранять их в таблице БД, любые рекомендации будут с благодарностью приняты

UPDATE

В качестве отзыва к некоторым комментариям. Веб-сайт предназначен для взаимодействия с ERP Dynamics AX на сайте клиента, поэтому, хотя у меня есть некоторый контроль над уровнем БД, он ограничен (я могу добавить некоторые Select SP и некоторые индексы, но триггеры и уведомители об изменениях, вероятно, нет Нет)

Последнее обновление для Dynamics AX в Azure, и нет доступа к слою БД, поэтому мне, вероятно, придется также размещать веб-сервер в Azure. Если это так и мне нужно поддерживать все версии, похоже, что я ограничен eith Redis или другой NoSQL DB - единственный вариант, или я записываю результат в свою собственную таблицу DB и вызываю оттуда. Это определенно относится к Azure?

Ответы [ 5 ]

0 голосов
/ 25 января 2019

Вы можете установить политики истечения срока действия для кэша и загрузки по требованию. Вы также можете иметь более одного уровня кэширования, один в качестве распределенного и один локальный в качестве локальной версии всегда будет самым быстрым. Я предпочитаю загрузку по запросу, чем опрос, так как при опросе вы всегда обновляете данные, даже когда никто не слушает. Если вы используете многоуровневую систему, вы можете опросить распределенный кеш и загрузить локальный кеш по требованию. Это примерно так же эффективно, как вы получите.

0 голосов
/ 22 января 2019

В этом сценарии есть несколько других вещей, о которых следует подумать, кроме просто сказать «Я хочу добавить кеширование».

  1. Если вы работаете в Azure или в веб-ферме, вам нужен централизованный кеш (REDIS или тому подобное), так как кэш памяти будет уничтожен и воссоздан вместе с вашим сайтом, и локально для одного сервера в ферма, поэтому вы не обязательно увидите прирост производительности.

  2. Если вы настраиваете REDIS Cache, убедитесь, что вы позаботились о его настройке. Кодирование должно быть таким же, чтобы учесть соединения, и если вы сделаете это неправильно, вы в конечном итоге переполните свой пул соединений.

  3. Это больше относится к вашей ситуации, но даже 4 секунды для возврата 70 тыс. Записей кажутся высокими. Проходили ли вы план выполнения, чтобы посмотреть, есть ли отсутствующие индексы или оптимизации с CTE, которые можно применить?

0 голосов
/ 22 января 2019

Если бы я реализовал ваш сценарий, я бы не предпочел опрос, так как нет смысла делать повторные вызовы в службу и сохранять занятость службы / сети. Кроме того, если вам нужно внедрить новый клиент, вам придется снова выполнить опрос.

Вместо этого используйте кэширование на основе словаря в статическом классе. Вы используете существующие библиотеки, такие как CacheManager. Общая идея заключается в создании ключа с использованием параметров, используемых для вызова службы. Затем сохраните результаты, полученные после обработки, в ConcurrentDictionary, который сам заботится о доступе для нескольких потоков.

Очистить сохраненный результат только после обновления базовой таблицы базы данных (?) Или, если это слишком сложно, через каждые 30 секунд.

Кроме того, вы можете также внедрить аналогичный механизм кэширования на уровне доступа к данным, чтобы сократить 4 секунды, которые у вас есть в настоящее время. Сброс кэшированных данных после изменений базовых данных (операции добавления, обновления, удаления, вставки)!

0 голосов
/ 22 января 2019

Мы реализуем шаблон опроса в ASP.NET, который может применяться в вашем случае использования.

В нашем Global.ashx у нас есть:

protected void Application_Start(object sender, EventArgs e)
{
  ConfigurationMonitor.Start();
}

, где ConfiguraitonMonitor выглядит примерно так:

public static class ConfigurationMonitor
{
    private static readonly Timer timer = new Timer(PollingInterval);
    public static bool MonitoringEnabled
    {
        get
        {
            return ((timer.Enabled || Working) ? true : false);
        }
    }
    private static int _PollingInterval;
    public static int PollingInterval
    {
        get
        {
            if (_PollingInterval == 0)
            {
                _PollingInterval = (Properties.Settings.Default.ConfigurationPollingIntervalMS > 0) ? Properties.Settings.Default.ConfigurationPollingIntervalMS : 5000;
            }
            return (_PollingInterval);

        }
        set { _PollingInterval = value; }
    }


    private static bool _Working = false;
    public static bool Working
    {
        get { return (_Working); }
    }
    public static void Start()
    {
        Start(PollingInterval);
    }

    /// <summary>
    /// Scans each DLL in a folder, building a list of the ConfigurationMonitor methods to call.
    /// </summary>
    private static List<ConfigurationMonitorAttribute> _MonitorMethods;
    private static List<ConfigurationMonitorAttribute> MonitorMethods
    {
        get
        {
            if (_MonitorMethods == null)
            {
                _MonitorMethods = new List<ConfigurationMonitorAttribute>();
                MonitorMethodsMessage = string.Empty;
                foreach (var assemblyFile in Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"), Properties.Settings.Default.ConfigurtionMonitorDLLPath))
                {
                    var assembly = Assembly.LoadFrom(assemblyFile);
                    foreach (ConfigurationMonitorAttribute monitor in assembly.GetCustomAttributes(typeof(ConfigurationMonitorAttribute), inherit: false))
                    {
                        _MonitorMethods.Add(monitor);
                    }
                }
            }
            return (_MonitorMethods);
        }
    }

    /// <summary>
    /// Resets and instanciates MonitorMethods property to refresh dlls being monitored
    /// </summary>
    public static void LoadMonitoringMethods()
    {
        _MonitorMethods = null;
        List<ConfigurationMonitorAttribute> monitorMethods = MonitorMethods;
    }

    /// <summary>
    /// Initiates a timer to monitor for configuration changes.
    /// This method is invoke on web application startup.
    /// </summary>
    /// <param name="pollingIntervalMS"></param>
    public static void Start(int pollingIntervalMS)
    {
        if (Properties.Settings.Default.ConfigurationMonitoring)
        {
            if (!timer.Enabled)
            {
                LoadMonitoringMethods();
                timer.Interval = pollingIntervalMS;
                timer.Enabled = true;
                timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
                timer.Start();
            }
            else
            {
                timer.Interval = pollingIntervalMS;
            }
        }
    }
    public static void Stop()
    {
        if (Properties.Settings.Default.ConfigurationMonitoring)
        {
            if (timer.Enabled)
            {
                timer.Stop();
            }
        }
    }

    /// <summary>
    /// Monitors CE table for changes
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private static void OnTimerElapsed(object sender, ElapsedEventArgs e)
    {
        timer.Enabled = false;
        PollForChanges();
        timer.Enabled = true;
    }
    public static DateTime PollForChanges()
    {
        LastPoll = PollForChanges(LastPoll);
        return (LastPoll);
    }
    public static DateTime PollForChanges(DateTime lastPollDate)
    {
        try
        {
            _Working = true;
            foreach (ConfigurationMonitorAttribute monitor in MonitorMethods)
            {
                try
                {
                    lastPollDate = monitor.InvokeMethod(lastPollDate);
                    if (lastPollDate > LastRefreshDate)
                        LastRefreshDate = lastPollDate;
                }
                catch (System.Exception ex)
                {
                    // log the exception; my code omitted for brevity
                }
            }
        }
        catch (System.Exception ex)
        {
            // log the exception; my code omitted for brevity

        }
        finally
        {
            _Working = false;
        }
        return (lastPollDate);
    }

    #region Events
    /// <summary>
    /// Event raised when an AppDomain reset should occur
    /// </summary>
    public static event AppDomainChangeEvent AppDomainChanged;
    public static void OnAppDomainChanged(string configFile, IDictionary<string, object> properties)
    {
        if (AppDomainChanged != null) AppDomainChanged(null, new AppDomainArgs(configFile, properties));
    }
    #endregion
}

Когда у нас есть сценарий использования, который хочетчтобы «участвовать» в этом механизме опроса, мы помечаем некоторый метод с атрибутом:

[assembly: ConfigurationMonitorAttribute(typeof(bar), "Monitor")]
namespace foo
{
  public class bar 
  {
    public static DateTime Monitor(DateTime lastPoll)
    {
      // do your expensive work here, setting values in your cache
    }
  }
}

Наш паттерн с методом, вызванным ConfigurationMonitor, возвращающим DateTime, был довольно странным краемдело.Вы, конечно, можете использовать метод void.

, где ConfigurationMonitorAttribute выглядит примерно так:

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class ConfigurationMonitorAttribute : Attribute
{
    private Type _type;
    private string _methodName;

    public ConfigurationMonitorAttribute(Type type, string methodName)
    {
        _type = type;
        _methodName = methodName;
    }

    public Type Type
    {
        get
        {
            return _type;
        }
    }

    public string MethodName
    {
        get
        {
            return _methodName;
        }
    }

    private MethodInfo _Method;
    protected MethodInfo Method
    {
        get
        {
            if (_Method == null)
            {
                _Method = Type.GetMethod(MethodName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
                if (_Method == null)
                    throw new ArgumentException(string.Format("The type {0} doesn't have a static method named {1}.", Type, MethodName));

            }
            return _Method;
        }
    }
    public DateTime InvokeMethod(DateTime lastPoll)
    {
        try
        {
            return (DateTime)Method.Invoke(null, new object[] { lastPoll });
        }
        catch (System.Exception err)
        {
            new qbo.Exception.ThirdPartyException(string.Format("Attempting to monitor {0}/{1} raised an error.", _type, _methodName), err);
        }
        return lastPoll;
    }
}
0 голосов
/ 19 января 2019

Вы можете кешировать свои данные с помощью Redis, а если данные изменяются, вы можете обновить кеш с помощью зависимости sql. Я думаю, вам просто нужны Redis и sql зависимости.

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