我正在寻找一些设计思路
我有一个网站使用的ASP.Net Web服务。其中一项调用大约需要13秒才能检索到大约70000行。由于每条记录上都有一个处理,因此在数据库上需要4秒钟,而在Web服务器上需要9秒钟来处理。据我所知,此功能已进行了优化,而最初的42秒已将其降低。
数据不会经常更改,因此我的想法是在Web服务上创建缓存,并按计时器轮询以每30秒左右更新该缓存。然后,Webservice调用从缓存中检索已处理的记录
我正在寻找设计思想,以寻求最佳方法。我知道ASP.Net具有输入缓存字典,但是那不能解决轮询问题,因此我也不需要单例,那么就有可能出现线程问题。
感到非常困惑,不确定Im是否在正确的行上,或者我是否应该计算数据并将其存储在DB表中,因此不胜感激
更新
作为对某些评论的反馈。该网站旨在与客户站点上的ERP Dynamics AX进行交互,因此,即使我对数据库层有一定控制权,但它还是有限的(我可以添加一些Select SP和一些索引,但是更改的触发器和通知者可能不是否)
Dynamics AX的最新升级是在Azure中进行的,无法访问数据库层,因此我可能也必须将Azure服务器托管在Azure中。如果是这种情况,并且由于我需要支持所有版本,则似乎只能使用Redis或另一个NoSQL DB,或者将结果写入我自己的DB表并从那里调用。肯定是Azure的情况吗?
答案 0 :(得分:0)
您可以使用Redis缓存数据,如果数据发生更改,则可以使用sql依赖项更新缓存。我认为您只需要redis和sql依赖项即可。
答案 1 :(得分:0)
如果要实现您的方案,我将不希望使用轮询,因为重复调用服务和保持服务/网络繁忙的意义不大。此外,如果要实现新的客户端,则还必须再次实现轮询。
请在静态类中使用基于字典的缓存。您使用现有的库,例如CacheManager。通常的想法是使用用于进行服务调用的参数来创建密钥。然后,将处理后获得的结果存储在ConcurrentDictionary
中,该{@ 1}负责多个线程本身的访问。
仅当基础数据库表(?)已更新或过于复杂时,才每30秒清除一次存储结果。
此外,您还可以在数据访问层上实现类似的缓存机制,以减少当前的4秒钟。在基础数据更改(添加,更新,删除,插入操作)之后刷新缓存的数据!
答案 2 :(得分:0)
我们在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;
}
}
答案 3 :(得分:0)
在这种情况下,除了说“我想添加缓存”以外,还有其他一些事情需要考虑。
如果您在Azure或Web场中运行,则需要集中式缓存(REDIS等),因为内存缓存将被破坏并与您的站点一起重新创建,并且位于本地一台服务器上农场,因此您不一定会看到性能提升。
如果您确实设置了REDIS缓存,请确保在配置它时要格外小心。编码必须正确,以解决连接问题,如果操作不正确,最终将导致连接池超载。
这更多地取决于您的情况,但是即使要花4秒才能返回7万条记录,似乎也很高。您是否已执行了执行计划,以查看是否存在缺少可应用的CTE的索引或优化?
答案 4 :(得分:0)
您可以设置缓存的过期策略并按需加载。您还可以具有一个以上的缓存级别,一个为分布式缓存,一个为本地缓存,因为本地版本总是最快的。我更喜欢按需加载而不是轮询,因为轮询总是刷新数据,即使在没有人收听的时候也是如此。如果您采用多层存储,则可以轮询分布式缓存,并按需加载本地缓存。那将与您获得的效率差不多。