如何优化此操作结果以获得更好的性能?我需要放一个计时器,何时从 url 等“获取”XML 数据
本文关键字:何时 计时器 一个 XML 数据 获取 url 操作 结果 优化 何优化 | 更新日期: 2023-09-27 18:15:13
我有一个我认为很重的操作结果,所以我想知道如何优化它以获得更好的性能。此 Web 应用程序将同时由 +100, 000 个用户使用。
现在,我的操作结果执行以下操作:
- 从互联网网址检索 XML 文件 将
- xml 数据填充到我的数据库
- 数据库数据填充我的视图模型
- 将模型返回到视图
这 4 个函数在用户每次访问视图时触发。这就是为什么我认为这个行动结果是我做的非常糟糕。
如何将以下内容添加到我的操作结果中?
添加一个计时器来检索 XML 文件并将 XML 数据填充到数据库,例如每 10 分钟一次,这样它就不会在用户每次访问视图时触发。每次用户访问站点时需要触发的唯一函数是视图模型绑定和返回模型。我怎样才能做到这一点?
注意:
- XML 文件每 10 分钟左右更新一次新数据。
- 我有大约 50 个操作结果,它们执行相同的操作,以获取 xml 数据并添加到数据库中,但有 50 个不同的 xml 文件。
- 如果 xml URL 处于脱机状态,则应跳过整个 xml 检索和数据库添加,而只执行模型绑定
这是我的操作结果:
public ActionResult Index()
{
//Get data from xml url (This is the code that shuld not run everytime a user visits the view)
var url = "http://www.interneturl.com/file.xml";
XNamespace dcM = "http://search.yahoo.com/mrss/";
var xdoc = XDocument.Load(url);
var items = xdoc.Descendants("item")
.Select(item => new
{
Title = item.Element("title").Value,
Description = item.Element("description").Value,
Link = item.Element("link").Value,
PubDate = item.Element("pubDate").Value,
MyImage = (string)item.Elements(dcM + "thumbnail")
.Where(i => i.Attribute("width").Value == "144" && i.Attribute("height").Value == "81")
.Select(i => i.Attribute("url").Value)
.SingleOrDefault()
})
.ToList();
//Fill my db entities with the xml data(This is the code that shuld not run everytime a user visits the view)
foreach (var item in items)
{
var date = DateTime.Parse(item.PubDate);
if (!item.Title.Contains(":") && !(date <= DateTime.Now.AddDays(-1)))
{
News NewsItem = new News();
Category Category = new Category();
var CategoryID = 2;
var WorldCategoryID = re.GetByCategoryID(CategoryID);
NewsItem.Category = WorldCategoryID;
NewsItem.Description = item.Description;
NewsItem.Title = item.Title.Replace("'", "");
NewsItem.Image = item.MyImage;
NewsItem.Link = item.Link;
NewsItem.Date = DateTime.Parse(item.PubDate);
re.AddNews(NewsItem);
re.save();
}
}
//All code below this commenting needs to run everytime a user visits the view
var GetAllItems = re.GetAllWorldNewsByID();
foreach (var newsitemz in GetAllItems)
{
if (newsitemz.Date <= DateTime.Now.AddDays(-1))
{
re.DeleteNews(newsitemz);
re.save();
}
}
var model = new ItemViewModel()
{
NewsList = new List<NewsViewModel>()
};
foreach (var NewsItems in GetAllItems)
{
FillProductToModel(model, NewsItems);
}
return View(model);
}
现在,每次用户访问索引视图时,它都会获取XML数据并将其添加到数据库中,因此在我的存储库中完成的错误修复是在addNews上完成的:
public void AddNews(News news)
{
var exists = db.News.Any(x => x.Title == news.Title);
if (exists == false)
{
db.News.AddObject(news);
}
else
{
db.News.DeleteObject(news);
}
}
非常感谢任何类型的解决方案和信息!
这里可以做很多事情:文件必须是XML(与JSON相比非常详细(吗?是否每次都必须将其保存到数据库中?
但是,假设您必须执行每一步,则有两个瓶颈:
- 等待 XML 文件下载/解析
- 将所有 XML 数据保存到数据库
有几种方法可以加快速度:
设置轮询间隔
如果您很高兴没有立即看到更新,那么您可以执行以下操作:
- 检查数据库以获取上次更新。
- 如果(且仅当(上次更新超过 10 分钟:
- 从互联网网址检索 XML 文件 将
- xml 数据填充到我的数据库
- 数据库数据填充我的视图模型
- 将模型返回到视图
这意味着您的数据可能最多过期 10 分钟,但绝大多数请求只需填充模型。
根据你如何使用它,你可以使它更简单 - 只需添加一个OutputCache
属性:
[OutputCache(Duration=600)]
public ActionResult Index() { ...
这将告诉浏览器每 10 分钟刷新一次。您还可以设置 Location
属性,使其仅由浏览器或服务器上缓存给每个人。
使 XML 检索异步
在下载 XML 文件期间,您的代码基本上只是等待 URL 加载 - 使用 C# 中的 new async
关键字,您无需在此处等待。
public async Task<ActionResult> Index()
{
// Get data from xml url
string url = "http://www.interneturl.com/file.xml";
XNamespace dcM = "http://search.yahoo.com/mrss/";
// The await keyword tells the C# code to continue until the slow action completes
var xdoc = await LoadRemoteXmlAsync(url, dcM);
// This won't fire until LoadRemoteXmlAsync has finished
var items = xdoc.Descendants("item")
使用async
还有很多内容,而不是我在这里实际涵盖的内容,但是如果您使用的是最新的C#和MVC,那么开始使用它可能相当简单。
仅进行 1 个数据库调用
您当前的数据库保存操作非常欠佳:
- 您的代码存在通常称为 N+1 问题的问题。
- 每次添加时,您首先要检查标题并删除记录。这是一种非常缓慢的更新方式,并且很难使用任何索引来优化它。
- 您每次都会遍历所有新闻文章,并逐个删除所有旧文章。这比单个
delete from News where ...
查询慢得多。
基于此,我将尝试以下更改(按它们应该有多容易的粗略顺序(:
更改
AddNews
方法 - 如果新数据不是较新的,则不要保存该项目的任何更改。将删除循环更改为单个
delete from News where Date <= @yesterday
查看新闻项标题和日期上的索引,这些似乎是您查询最多的字段。
考虑用执行
upsert
/merge
的方法替换您的AddNews
方法re.GetByCategoryID
会击中您的数据库吗?如果是这样,请考虑将其拆分出来,并将其构建到更新查询中,或者填充字典以更快地查找它。
基本上,每篇新新闻文章应该(最多(有 1 个数据库操作,删除旧新闻文章应该有 1 个数据库操作。您目前每篇文章有 3 个不到一天的时间 ( re.GetByCategoryID
+ db.News.Any
+ db.News.Add|DeleteObject
( 另外 1 个 ( re.GetAllWorldNewsByID
(,然后每篇文章还有 1 个要删除 ( re.DeleteNews
(。
添加性能分析
您可以向 MVC 项目添加性能分析,这些项目将准确告诉您每个步骤需要多长时间,并帮助找到如何使用 MiniProfiler 优化它们的方法。它用在StackOverflow上,我自己也用过很多次 - 它会告诉你哪些步骤减慢了你的速度,哪些步骤不值得进行微优化。
如果你不想使用它,Visual Studio中有优化工具,还有RedGate ANTS等第三方工具。
将 XML 数据和数据库填充的检索移动到后端进程。这样,您的操作将只从数据库中检索数据并返回它。
更具体地说,
- 创建一个将在后台运行的程序(如 Windows 服务(
- 在循环中,检索 XML 数据,更新数据库,然后等待所需的延迟期。
例如,要延迟,您可以使用以下内容延迟 1 分钟(60 秒(:
System.Threading.Thread.Sleep(60*1000);
Sleep
可能不是延迟满足您需求的最佳方法,但它可以作为一个开始。
的一种方法是使用 ASP.NET 输出缓存。
输出缓存允许您执行一次操作 - 然后缓存生成的页面,以便不会每次都执行该操作。您可以指定要缓存的操作(或类(以及保持项目缓存的持续时间。当缓存项过期时,将再次运行该操作。
ASP.NET 站点上有一个 C#/MVC 教程:使用输出缓存提高性能。
我建议您阅读链接,但这里有一些相关部分:
<小时 />例如,假设您的 ASP.NET MVC 应用程序在名为 Index 的视图中显示数据库记录列表。通常,每次用户调用返回 Index 视图的控制器操作时,都必须通过执行数据库查询从数据库中检索数据库记录集。
另一方面,如果利用输出缓存,则可以避免每次任何用户调用相同的控制器操作时执行数据库查询。可以从缓存中检索视图,而不是从控制器操作中重新生成视图。缓存使您能够避免在服务器上执行冗余工作。
通过将 [OutputCache] 属性添加到单个控制器操作或整个控制器类来启用输出缓存。
编辑 - 例如,这会将操作结果缓存 1 小时:
[OutputCache(Duration = 3600, VaryByParam = "none")]
public ActionResult Index()
{
...
100000+ 并发用户并写入 sql 数据库,我认为如果不重新考虑整个基础架构,这将很难实现。
我同意 Matt 的观点,即最好将写入数据库(具有索引器进程(与生成结果分开给用户。在这种情况下,您将有 1 个写入器和 100000+ 个读取器,这对于单个服务器实例来说仍然太多了,并且关系数据库扩展很困难。从这个角度来看,我会考虑非关系持久性解决方案,特别是因为您的数据看起来不是很关键。
看起来输出缓存对您有用,因为信息不是特定于用户的,所以问题是,如果简单的输出缓存就足够了 - 可能需要分布式缓存。要知道这一点,您需要计算 - 缓存过期的频率以及生成过期缓存响应所需的资源量。请记住,单个服务器将无法为 100000+ 并发用户提供服务,因此您将有多个前端实例,每个实例都需要生成自己的结果来缓存它 - 这就是分布式缓存将发挥作用的地方 - 服务器可以共享生成的结果。
首先,您不应该在运行时删除新闻。您可以手动或通过 shedule 执行此操作,而不是
var GetAllItems = re.GetAllWorldNewsByID();
foreach (var newsitemz in GetAllItems)
{
if (newsitemz.Date <= DateTime.Now.AddDays(-1))
{
re.DeleteNews(newsitemz);
re.save();
}
}
使用代码:
var GetAllItems = re.GetAllWorldNewsByID().Where(x=>x.Date > DateTime.Now.AddDays(-1)).ToList();
GetAllWorldNewsByID(( 必须返回 IQuaryable,因为如果返回 List,则会失去延迟执行的好处(在 LINQ 中延迟执行有什么好处?(。当您在运行时不删除时,服务操作不会有太大的延迟(因为该操作不是针对用户,而是用于清理数据库(
其次,您可以使用缓存
//Get data from xml url (This is the code that shuld not run everytime a user visits the view)
var url = "http://www.interneturl.com/file.xml";
// Get data from cache (if available)
List<TypeOfItems> GetAllItems = (List<TypeOfItems>)HttpContext.Current.Cache.Get(/*unique identity of you xml, such as url*/ url);
if (GetAllItems == null)
{
var xdoc = XDocument.Load(url);
items = xdoc.Descendants("item").Select(item => new
{
Title = item.Element("title").Value,
Description = item.Element("description").Value,
Link = item.Element("link").Value,
PubDate = item.Element("pubDate").Value,
MyImage = (string)item.Elements(dcM + "thumbnail")
.Where(i => i.Attribute("width").Value == "144" && i.Attribute("height").Value == "81")
.Select(i => i.Attribute("url").Value)
.SingleOrDefault()
})
.ToList();
// Fill db
GetAllItems = re.GetAllWorldNewsByID().Where(x=>x.Date > DateTime.Now.AddDays(-1)).ToList()
// put data into the cache
HttpContext.Current.Cache.Add(/*unique identity of you xml, such as url*/url, /*data*/ GetAllItems, null,
DateTime.Now.AddMinutes(1) /*time of cache actual*/,
System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Default, null);
}
现在,我的操作结果执行以下操作:
- 从互联网网址检索 XML 文件
将- xml 数据填充到我的数据库
- 数据库数据填充我的视图模型
- 将模型返回到视图
在所有这些步骤中,XML文件是"弱/慢链接"之一。
在我的应用程序中,我用Protobuf
(用于网络(更改了XML的序列化,并且速度发生了巨大变化!
XML 文件的读取、写入和传输会使此步骤变慢,尤其是在每次调用时执行此操作时。
因此,这是我将要更改的第一点,即使用更快的XML序列化。
您可以实现一个类,用于在选定的时间间隔管理 xml 处理和数据库操作的执行。我不会使用计时器,因为我认为没有必要:如果你有 100 000 个发出请求,你可以检查是否需要在每个请求中执行你的函数。
下面是一个您可以使用的类:
public static class DelayedAction
{
private static Dictionary<Action, Tuple<DateTime, TimeSpan>> _actions;
static DelayedAction()
{
_actions = new Dictionary<Action, Tuple<DateTime, TimeSpan>>();
}
public static void Add(Action a, TimeSpan executionInterval)
{
lock (_actions)
{
_actions.Add(a, new Tuple<DateTime, TimeSpan>(DateTime.MinValue, executionInterval));
}
}
public static void ExecuteIfNeeded(Action a)
{
lock (_actions)
{
Tuple<DateTime, TimeSpan> t = _actions[a];
if (DateTime.Now - t.Item1 > t.Item2)
{
_actions[a] = new Tuple<DateTime, TimeSpan>(DateTime.Now, t.Item2);
a();
}
}
}
}
它是线程安全的,您可以根据需要添加任意数量的延迟操作。
要使用它,只需移动您的 xml 检索并将代码保存到一个函数中,我们称之为 updateNews:
private void updateNews()
{
//Get data from xml url (This is the code that shuld not run everytime a user visits the view)
var url = "http://www.interneturl.com/file.xml";
XNamespace dcM = "http://search.yahoo.com/mrss/";
var xdoc = XDocument.Load(url);
var items = xdoc.Descendants("item")
.Select(item => new
{
Title = item.Element("title").Value,
Description = item.Element("description").Value,
Link = item.Element("link").Value,
PubDate = item.Element("pubDate").Value,
MyImage = (string)item.Elements(dcM + "thumbnail")
.Where(i => i.Attribute("width").Value == "144" && i.Attribute("height").Value == "81")
.Select(i => i.Attribute("url").Value)
.SingleOrDefault()
})
.ToList();
//Fill my db entities with the xml data(This is the code that shuld not run everytime a user visits the view)
foreach (var item in items)
{
var date = DateTime.Parse(item.PubDate);
if (!item.Title.Contains(":") && !(date <= DateTime.Now.AddDays(-1)))
{
News NewsItem = new News();
Category Category = new Category();
var CategoryID = 2;
var WorldCategoryID = re.GetByCategoryID(CategoryID);
NewsItem.Category = WorldCategoryID;
NewsItem.Description = item.Description;
NewsItem.Title = item.Title.Replace("'", "");
NewsItem.Image = item.MyImage;
NewsItem.Link = item.Link;
NewsItem.Date = DateTime.Parse(item.PubDate);
re.AddNews(NewsItem);
re.save();
}
}
}
然后将静态构造函数添加到控制器:
static MyController()
{
DelayedAction.Add(updateNews, new TimeSpan(0, 10, 0)); // set interval to 10mn
}
然后在您的Index
方法中:
public ActionResult Index()
{
DelayedAction.ExecuteIfNeeded(updateNews);
//All code below this commenting needs to run everytime a user visits the view
....
}
这样,每次收到此页面的请求时,您都可以检查是否需要更新数据。您可以将其用于需要延迟的所有其他处理。
这可能是缓存的一个很好的补充。