5

かなり重いと思われるアクション結果があるので、パフォーマンスを向上させるために最適化するにはどうすればよいでしょうか。この Web アプリケーションは、+100,000 人のユーザーが同時に使用します。

現在、私の Actionresult は次のことを行います。

  • インターネット URL から XML ファイルを取得する
  • xml データを DB に入力します
  • DB データがビューモデルを満たす
  • モデルをビューに返します

この 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 データが取得されて DB に追加されるため、私のリポジトリで行った悪い修正は 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 に比べて非常に冗長です)? 毎回DBに保存する必要がありますか?

ただし、すべてのステップを実行する必要があると仮定すると、次の 2 つのボトルネックがあります。

  1. XML ファイルのダウンロード/解析を待機しています
  2. すべての XML データを DB に保存する

これを高速化するには、いくつかの方法があります。

ポーリング間隔を設定する

更新がすぐに表示されないことに満足している場合は、次のようにすることができます。

  • DB の最終更新を確認します。
  • 最後の更新から 10 分以上経過している場合 (およびその場合のみ):
    • インターネット URL から XML ファイルを取得する
    • xml データを DB に入力します
  • DB データがビューモデルを満たす
  • モデルをビューに返します

これは、データが最大 10 分遅れている可能性があることを意味しますが、大部分のリクエストはモデルに入力するだけで済みます。

これをどのように使用しているかに応じて、これをさらに簡単にすることができます -OutputCache属性を追加するだけです:

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

これにより、ブラウザーは 10 分ごとにのみ更新するように指示されます。属性を設定しLocationて、ブラウザまたはサーバーにキャッシュするだけにすることもできます。

XML 取得を非同期にする

XML ファイルのダウンロード中、コードは基本的に URL が読み込まれるのを待っているだけasyncです。C# で 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 呼び出しのみを行う

現在の DB 保存アクションは非常に最適ではありません。

  • あなたのコードは、一般にN+1 問題と呼ばれるものに悩まされています。
  • 追加するたびに、最初にタイトルを確認してレコードを削除します。これは更新を行うのに非常に時間がかかる方法であり、インデックスを使用して最適化することが非常に困難になる場合があります。
  • 毎回すべてのニュース記事をループして、古い記事を 1 つずつ削除しています。delete from News where ...これは、単一のクエリよりもはるかに遅くなります。

これに基づいて、次の変更を試してみます (簡単な順序で):

  1. 方法を変更してください。AddNews新しいデータが新しくない場合は、そのアイテムの変更を保存しないでください。

  2. 削除ループを単一に変更しますdelete from News where Date <= @yesterday

  3. ニュース項目のタイトルと日付のインデックスを見てください。これらは、最もクエリを実行しているフィールドのようです。

  4. AddNewsメソッドをupsert/を実行するものに置き換えることを検討してくださいmerge

  5. あなたre.GetByCategoryIDのDBにヒットしますか?その場合は、それを分割して更新クエリに組み込むか、辞書を作成してより迅速に検索することを検討してください。

基本的に、新しいニュース記事ごとに (最大で) 1 つの DB 操作と、古いニュース記事を削除するための 1 つの DB 操作が必要です。現在、1 日未満の記事ごとに 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

まず、実行時にニュースを削除しないでください。手動またはスケジュールでそれを行うことができます。

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 での遅延実行の利点は何ですか? )。実行時に削除しない場合、サービス操作に大きな遅延はありません (その操作はユーザーのためではなく、データベースのクリーニングのためです)。

2番目に、キャッシュを使用できます

//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

これを解決する 1 つの方法は、ASP.NET 出力キャッシュを使用することです。

出力キャッシュを使用すると、アクションを 1 回実行できます。その後、生成されたページがキャッシュされるため、アクションが毎回実行されることはありません。キャッシュするアクション (またはクラス) と、アイテムをキャッシュし続ける期間を指定できます。キャッシュされたアイテムの有効期限が切れると、アクションが再度実行されます。

ASP.NET サイトに C#/MVC チュートリアルがあります: Improving Performance with Output Caching

リンクを読むことをお勧めしますが、関連する部分をいくつか示します。

たとえば、ASP.NET MVC アプリケーションが Index という名前のビューにデータベース レコードのリストを表示するとします。通常、ユーザーが Index ビューを返すコントローラー アクションを呼び出すたびに、データベース クエリを実行して、データベース レコードのセットをデータベースから取得する必要があります。

一方、出力キャッシュを利用すると、ユーザーが同じコントローラー アクションを呼び出すたびにデータベース クエリを実行することを回避できます。ビューは、コントローラー アクションから再生成する代わりに、キャッシュから取得できます。キャッシュを使用すると、サーバーで冗長な作業を実行することを回避できます。

[OutputCache] 属性を個々のコントローラー アクションまたはコントローラー クラス全体に追加することで、出力キャッシュを有効にします。


編集 - 例として、これは ActionResult を 1 時間キャッシュします。

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

100000 人以上の同時ユーザーと sql データベースへの書き込み、インフラストラクチャ全体を再考せずに達成するのは難しいと思います。

データベースへの書き込み (インデクサー プロセスがある) とユーザーへの結果の生成を分離するのが最善であるという Matt の意見に同意します。このような場合、1 つのライターと 100000 以上のリーダーがあり、単一のサーバー インスタンスには多すぎるため、リレーショナル データベースのスケーリングは困難です。この観点から見ると、特にデータがそれほど重要ではないように見えるため、非リレーショナル永続化ソリューションについて考えます。

情報はユーザー固有ではないため、出力キャッシュが機能するように見えます。質問は、単純な出力キャッシュで十分かどうかです-これには分散キャッシュが必要な場合があります。それを知るには、カウントする必要があります-キャッシュが期限切れになる頻度と、期限切れのキャッシュに対する応答を生成するために必要なリソースの量。単一のサーバーでは 100000 人以上の同時ユーザーにサービスを提供できないため、複数のフロント エンド インスタンスがあり、それぞれがそれをキャッシュするために独自の結果を生成する必要があることに注意してください。これが分散キャッシュが開始される場所です。サーバーは共有できます。生成された結果。

于 2013-05-30T12:07:11.460 に答える
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

XML データの取得とデータベースへの入力をバックエンド プロセスに移行します。このように、アクションはデータベースからデータを取得して返すだけです。

すなわち、

  1. バックグラウンドで実行されるプログラムを作成します (Windows サービスなど)。
  2. ループで、XML データを取得し、データベースを更新してから、希望する遅延期間を待ちます。

たとえば、遅延するには、次を使用して 1 分間 (60 秒) 遅延させることができます。

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

Sleepニーズに合わせて遅延させる最良の方法ではないかもしれませんが、最初はうまくいきます。

于 2013-05-25T14:22:20.197 に答える
0

現在、私の Actionresult は次のことを行います。

  • インターネット URL から XML ファイルを取得する
  • xml データを DB に入力します
  • DB データがビューモデルを満たす
  • モデルをビューに返します

このすべてのステップで、XML ファイルは「弱い/遅いリンク」の 1 つです。

私のアプリケーションでは、XML のシリアル化を(Protobufネット用に)変更したところ、速度が劇的に変化しました。

XML ファイルの読み取り、書き込み、転送により、特に各呼び出しで行う場合は、この手順が遅くなります。

したがって、これが私が変更する最初のポイントであり、より高速な XML シリアライゼーションです。

于 2013-05-31T12:59:34.320 に答える