5

我有一个我认为很重的 actionresult,所以我想知道如何优化它以获得更好的性能。此 Web 应用程序将同时被 +100, 000 个用户使用。

现在我的 Actionresult 做了以下事情:

  • 从 Internet url 检索 XML 文件
  • 将 xml 数据填充到我的数据库中
  • 数据库数据填充我的 Viewmodel
  • 将模型返回到视图

每次用户访问视图时都会触发这 4 个函数。这就是为什么我认为这个 Actionresult 是我做的非常糟糕的原因。

如何将以下内容添加到我的 Actionresults 中?

添加一个计时器来检索 XML 文件并将 xml 数据填充到 DB,例如每 10 分钟一次,因此它不会在每次用户访问视图时触发。每次用户访问站点时唯一需要触发的功能是 viewmodel 绑定和返回模型。我怎样才能做到这一点?

笔记:

  • xml 文件每 10 分钟左右更新一次新数据。
  • 我有大约 50 个操作结果,它们执行相同的获取 xml 数据并添加到数据库但 50 个不同的 xml 文件。
  • 如果 xml URL 处于脱机状态,它应该跳过整个 xml 检索和 DB 添加,只进行模型绑定

这是我的行动结果:

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);
            }
        }

任何类型的解决方案和信息都非常感谢!

4

7 回答 7

3

这里可以做很多事情:文件是否必须是 XML(与 JSON 相比非常冗长)?每次都必须保存到数据库吗?

但是,假设您必须执行每一步,您会遇到两个瓶颈:

  1. 等待 XML 文件下载/解析
  2. 将所有 XML 数据保存到数据库

有几种方法可以加快速度:

设置轮询间隔

如果您很高兴没有立即看到更新,那么您可以执行以下操作:

  • 检查数据库以获取上次更新。
  • 如果(且仅当)最后一次更新超过 10 分钟:
    • 从 Internet url 检索 XML 文件
    • 将 xml 数据填充到我的数据库中
  • 数据库数据填充我的 Viewmodel
  • 将模型返回到视图

这意味着您的数据最多可能会过期 10 分钟,但绝大多数请求只需填充模型即可。

根据您使用它的方式,您可以使其更加简单 - 只需添加一个OutputCache属性:

[OutputCache(Duration=600)]
public ActionResult Index() { ...

这将告诉浏览器每 10 分钟刷新一次。您还可以设置该Location属性以使其仅由浏览器缓存或在服务器上为所有人缓存。

使 XML 检索异步

在下载 XML 文件期间,您的代码基本上只是在等待 URL 被加载——使用asyncC# 中的 new 关键字,您无需在此处等待。

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 次 DB 调用

您当前的数据库保存操作非常不理想:

  • 您的代码遇到了通常称为N+1 问题的问题
  • 每次添加时,您首先检查标题并删除记录。这是一种非常缓慢的更新方式,并且如果非常难以使用任何索引来优化它,将会变得非常困难。
  • 您每次都在循环浏览所有新闻文章,并一一删除所有旧文章。这比单个delete from News where ...查询慢得多。

基于此,我将尝试以下更改(按照它们应该有多容易的粗略顺序):

  1. 更改您的AddNews方法 - 如果新数据不是较新的,则不要为该项目保存任何更改。

  2. 将删除循环更改为单个delete from News where Date <= @yesterday

  3. 查看新闻项目标题和日期的索引,这些似乎是您查询最多的字段。

  4. 看看用AddNewsupsert/的东西替换你的方法merge

  5. re.GetByCategoryID打到你的数据库了吗?如果是这样,请考虑将其拆分出来并将其构建到更新查询中或填充字典以更快地查找它。

基本上你应该(最多)每篇新闻文章有 1 个 DB 操作和删除旧文章的 1 个 DB 操作。您目前每篇文章有 3 个不到一天的时间 ( re.GetByCategoryID++ db.News.Any) ,db.News.Add|DeleteObject另外 1 个 ( re.GetAllWorldNewsByID),然后每篇文章还有 1 个要删除 ( re.DeleteNews)。

添加分析

您可以将分析添加到 MVC 项目中,这将准确地告诉您每个步骤需要多长时间,并帮助您找到如何使用MiniProfiler优化它们。它在 StackOverflow 上使用过,我自己也经常使用它——它会告诉你哪些步骤会减慢你的速度,哪些不值得微优化。

如果您不想使用 Visual Studio 中的优化工具以及RedGate ANTS等第三方工具。

于 2013-06-03T14:17:55.930 回答
0

将 XML 数据的检索和数据库的填充移至后端进程。这样,您的操作只会从数据库中检索数据并将其返回。

进一步来说,

  1. 创建一个将在后台运行的程序(如 Windows 服务)
  2. 在一个循环中,检索您的 XML 数据,更新您的数据库,然后等待您想要的延迟时间。

例如,要延迟,您可以使用以下命令延迟 1 分钟(60 秒):

System.Threading.Thread.Sleep(60*1000);

Sleep可能不是满足您的需求的最佳延迟方法,但它可以作为一个开始。

于 2013-05-25T14:22:20.197 回答
0

解决此问题的一种方法是使用 ASP.NET 输出缓存。

输出缓存允许您执行一次操作 - 然后缓存生成的页面,因此不会每次都执行该操作。您可以指定要缓存的操作(或类),以及保持项目缓存的持续时间。当缓存项过期时,将再次运行该操作。

ASP.NET 站点上有一个 C#/MVC 教程:使用输出缓存提高性能

我建议您阅读链接,但这里有一些相关部分:

例如,假设您的 ASP.NET MVC 应用程序在名为 Index 的视图中显示数据库记录列表。通常,每次用户调用返回索引视图的控制器操作时,都必须通过执行数据库查询从数据库中检索数据库记录集。

另一方面,如果您利用输出缓存,则可以避免每次任何用户调用相同的控制器操作时都执行数据库查询。可以从缓存中检索视图,而不是从控制器操作中重新生成视图。缓存使您能够避免在服务器上执行冗余工作。

您可以通过将 [OutputCache] 属性添加到单个控制器操作或整个控制器类来启用输出缓存。


编辑 - 例如,这会将您的 ActionResult 缓存 1 小时:

[OutputCache(Duration = 3600, VaryByParam = "none")]
public ActionResult Index()
{
...
于 2013-05-27T16:48:00.863 回答
0

现在我的 Actionresult 做了以下事情:

  • 从 Internet url 检索XML 文件
  • 将 xml 数据填充到我的数据库中
  • 数据库数据填充我的 Viewmodel
  • 将模型返回到视图

在所有这些步骤中,XML 文件是“弱/慢链接”之一。

在我的应用程序中,我使用(for net)更改了 XMLProtobuf序列化,并且速度发生了巨大变化!

XML 文件的读取、写入和传输使此步骤变慢,尤其是在每次调用时。

所以这是我要改变的第一点,用更快的 XML 序列化。

于 2013-05-31T12:59:34.320 回答
0

首先,您不应该在运行时删除新闻。您可以手动或通过 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);
}
于 2013-05-31T07:14:15.023 回答
0

您可以实现一个类,以在选定的时间间隔内管理 xml 处理和 db 操作的执行。我不会使用计时器,因为我认为没有必要:如果您有 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
    ....
}

这样,每次收到此页面的请求时,您都可以检查是否需要更新数据。您可以将其用于需要延迟的所有其他处理。

这可能是缓存的一个很好的补充。

于 2013-05-30T21:49:29.840 回答
0

100000+ 并发用户和写入 sql 数据库,我认为如果不重新考虑整个基础架构,这将很难实现。

我同意马特的观点,最好将写入数据库(具有索引器进程)与向用户生成结果分开。在这种情况下,您将拥有 1 个写入器和 100000 多个读取器,这对于单个服务器实例来说仍然太多,并且关系数据库扩展很困难。从这个角度来看,我会考虑非关系持久性解决方案,特别是因为您的数据看起来不是很关键。

看起来输出缓存对您有用,因为信息不是特定于用户的,所以问题是,如果简单的输出缓存足够好 - 可能您需要为此使用分布式缓存。要知道这一点,您需要计算 - 缓存多久会过期以及需要多少资源来为过期缓存生成响应。请记住,单个服务器将无法为超过 100000 个并发用户提供服务,因此您将拥有多个前端实例,每个实例都需要生成自己的结果来缓存它——这就是分布式缓存将发挥作用的地方——服务器可以共享生成的结果。

于 2013-05-30T12:07:11.460 回答