2012-03-01 76 views
0

我正在開發一個應用程序,並且需要跟蹤頁面的任何視圖。幾乎就像它是如何做到的。這是一個用於確定給定頁面受歡迎程度的值。延遲寫入SQL Server

我擔心每次需要記錄新視圖時寫入數據庫都會影響性能。我知道這種邊界線的預優化,但我以前遇到過這個問題。無論如何,價值並不需要是實時的;如果延遲10分鐘左右就可以了。我在考慮緩存數據,並且每X分鐘寫一個大寫應該會有所幫助。

我在Windows Azure上運行,因此Appfabric高速緩存可供我使用。我最初的計劃是創建某種複合鍵(PostID:UserID),並用「pageview」標記鍵。 Appfabric允許您通過標籤獲取所有密鑰。因此,我可以讓他們建立起來,並在我的表格中進行一次批量插入,而不是進行很多小寫操作。表格看起來像這樣,但是可以改變。

int PageID | guid userID | DateTime ViewTimeStamp 

該網站仍然會從數據庫中獲得價值,寫入只會延遲,有意義嗎?

I just read Windows Azure Appfabric緩存不支持基於標籤的搜索,所以它幾乎否定了我的想法。

我的問題是,你會如何做到這一點?我是Azure的新手,所以我不確定我的選擇是什麼。有沒有一種方法可以在沒有基於標籤的搜索的情況下使用緩存我只是尋求如何延遲這些寫入SQL的建議。

+0

我不是想追蹤頁面點擊,而且這不記錄。在定義視圖的背後有業務規則。多久用戶必須在頁面上,多長時間,因爲相同的用戶上次訪問,等等... – Joe 2012-03-01 18:37:36

回答

2

您可能想看看http://www.apathybutton.com(以及它鏈接到的雲端封面插曲),其中介紹了一種高度可擴展的計算方法。 (這可能是爲你的需要矯枉過正,但希望它可以給你一些選擇。)

+0

鏈接已死... – 2016-10-12 18:01:10

0

你可能想看看如何在Azure中的「診斷」功能的作品。不是因爲你會使用診斷來處理你正在做的事情,而是因爲它正在處理類似的問題,並可能提供一些啓發。我即將實施數據審計功能,並且希望將其記錄到表存儲中,因此也希望將更新延遲並集中在一起,並且我從診斷中獲得了很多靈感。

現在,Azure中診斷的工作方式是每個角色都啓動一個小的背景「傳輸」線程。因此,無論何時寫入任何跟蹤信息,都會將其存儲在本地內存的列表中,後臺線程將(默認情況下)將所有請求集中起來並每分鐘將其傳輸到表存儲。

在您的方案中,我會讓每個角色實例跟蹤點擊次數,然後使用後臺線程每隔一分鐘左右更新一次數據庫。 我可能會在每個卷軸上使用類似靜態ConcurrentDictionary(或者一個掛掉一個singleton)的東西,每個命中增加頁面標識符的計數器。你需要有一些線程處理代碼來允許多個請求來更新列表中的同一個計數器。或者,只要允許每個「命中」將新記錄添加到共享線程安全列表。
然後,每分鐘一次後臺線程將數據庫中的每頁點擊次數增加一次,並將本地計數器重置爲0或清空共享列表(如果要使用該方法)(再次注意多線程和鎖定)。

重要的是要確保您的數據庫更新是原子的;如果您從數據庫中讀取當前數量,請將其增加並將其寫回,然後您可能會有兩個不同的Web角色實例同時進行此操作,從而失去一個更新。

編輯: 下面是你如何可以去做這個快速樣本。

using System.Collections.Concurrent; 
using System.Data.SqlClient; 
using System.Threading; 
using System; 
using System.Collections.Generic; 
using System.Linq; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     // You would put this in your Application_start for the web role 
     Thread hitTransfer = new Thread(() => HitCounter.Run(new TimeSpan(0, 0, 1))); // You'd probably want the transfer to happen once a minute rather than once a second 
     hitTransfer.Start(); 


     //Testing code - this just simulates various web threads being hit and adding hits to the counter 
     RunTestWorkerThreads(5); 
     Thread.Sleep(5000); 

     // You would put the following line in your Application shutdown 
     HitCounter.StopRunning(); // You could do some cleverer stuff with aborting threads, joining the thread etc but you probably won't need to 
     Console.WriteLine("Finished..."); 
     Console.ReadKey(); 

    } 

    private static void RunTestWorkerThreads(int workerCount) 
    { 
     Thread[] workerThreads = new Thread[workerCount]; 
     for (int i = 0; i < workerCount; i++) 
     { 
      workerThreads[i] = new Thread(
       (tagname) => 
        { 
         Random rnd = new Random(); 
         for (int j = 0; j < 300; j++) 
         { 
          HitCounter.LogHit(tagname.ToString()); 
          Thread.Sleep(rnd.Next(0, 5)); 
         } 
        }); 
      workerThreads[i].Start("TAG" + i); 
     } 

     foreach (var t in workerThreads) 
     { 
      t.Join(); 
     } 
     Console.WriteLine("All threads finished..."); 
    } 
} 

public static class HitCounter 
{ 
    private static System.Collections.Concurrent.ConcurrentQueue<string> hits; 
    private static object transferlock = new object(); 
    private static volatile bool stopRunning = false; 

    static HitCounter() 
    { 
     hits = new ConcurrentQueue<string>(); 
    } 

    public static void LogHit(string tag) 
    { 
     hits.Enqueue(tag); 
    } 

    public static void Run(TimeSpan transferInterval) 
    { 
     while (!stopRunning) 
     { 
      Transfer(); 
      Thread.Sleep(transferInterval); 
     } 
    } 

    public static void StopRunning() 
    { 
     stopRunning = true; 
     Transfer(); 
    } 

    private static void Transfer() 
    { 
     lock(transferlock) 
     { 
      var tags = GetPendingTags(); 
      var hitCounts = from tag in tags 
          group tag by tag 
          into g 
          select new KeyValuePair<string, int>(g.Key, g.Count()); 
      WriteHits(hitCounts); 
     } 
    } 

    private static void WriteHits(IEnumerable<KeyValuePair<string, int>> hitCounts) 
    { 
     // NOTE: I don't usually use sql commands directly and have not tested the below 
     // The idea is that the update should be atomic so even though you have multiple 
     // web servers all issuing similar update commands, potentially at the same time, 
     // they should all commit. I do urge you to test this part as I cannot promise this code 
     // will work as-is 
     //using (SqlConnection con = new SqlConnection("xyz")) 
     //{ 
     // foreach (var hitCount in hitCounts.OrderBy(h => h.Key)) 
     // { 
     //  var cmd = con.CreateCommand(); 
     //  cmd.CommandText = "update hits set count = count + @count where tag = @tag"; 
     //  cmd.Parameters.AddWithValue("@count", hitCount.Value); 
     //  cmd.Parameters.AddWithValue("@tag", hitCount.Key); 
     //  cmd.ExecuteNonQuery(); 
     // } 
     //} 

     Console.WriteLine("Writing...."); 
     foreach (var hitCount in hitCounts.OrderBy(h => h.Key)) 
     { 

      Console.WriteLine(String.Format("{0}\t{1}", hitCount.Key, hitCount.Value)); 
     } 
    } 

    private static IEnumerable<string> GetPendingTags() 
    { 
     List<string> hitlist = new List<string>(); 
     var currentCount = hits.Count(); 
     for (int i = 0; i < currentCount; i++) 
     { 
      string tag = null; 
      if (hits.TryDequeue(out tag)) 
      { 
       hitlist.Add(tag); 
      } 
     } 
     return hitlist; 
    } 
}  
+0

順便說一句,有沒有人知道爲什麼代碼示例不「美化」?我究竟做錯了什麼? – Frans 2012-03-03 18:09:23

1

你可以保持一個隊列在內存和一個計時器漏隊列,通過合計通過頁面的計數崩潰排隊的項目,並在一個SQL批處理/往返寫。例如,使用TVP,您可以使用一個存儲過程調用寫入隊列總數。

這當然不能保證視圖的數量會被寫入,因爲它的內存和潛在寫入,但頁面數量不應該是關鍵數據,崩潰應該是罕見的。