2012-03-01 1 views
0

나는 응용 프로그램을 작성 중이며 페이지의보기 상태를 추적해야합니다. 거의 그렇게하는 것처럼. 주어진 페이지의 인기도를 결정하는 데 사용되는 값입니다.SQL 서버에 쓰기 지연

새로운 관점을 기록해야 할 때마다 DB에 글을 쓰는 것이 성능에 영향을 줄 수 있습니다. 이 경계선 사전 최적화를 알고 있지만 이전에 문제가 발생했습니다. 어쨌든, 그 값은 실시간 일 필요는 없습니다. 10 분 정도 지연되면 괜찮습니다. 데이터를 캐싱하고 X 분마다 한 번만 큰 쓰기를하면 도움이 될 것이라고 생각했습니다.

Windows Azure에서 실행 중이므로 Appfabric 캐시를 사용할 수 있습니다. 내 독창적 인 계획은 일종의 복합 키 (PostID : UserID)를 만들고 키에 "pageview"태그를 지정하는 것이 었습니다. Appfabric을 사용하면 모든 키를 태그별로 가져올 수 있습니다. 따라서 나는 그것들을 쌓아 올릴 수 있었고, 많은 작은 글씨 대신에 나의 테이블에 하나의 대량 삽입을 할 수 있었다. 표는 이와 같지만 변경이 가능합니다.

int PageID | guid userID | DateTime ViewTimeStamp 

웹 사이트는 여전히 데이터베이스에서 가치를 얻을 수 있지만 쓰기가 지연 될 수 있습니까?

나는 윈도우 애저 Appfabric 캐시는 태그 기반 검색을 지원하지 않는 just read, 그래서 꽤 많이 내 생각을 부정한다.

내 질문은 어떻게 수행하겠습니까? 나는 Azure를 처음 사용하기 때문에 어떤 옵션이 있는지 확신 할 수 없습니다. 태그 기반 검색없이 캐시를 사용할 수 있습니까? 나는 SQL에 대한 이러한 기록을 지연하는 방법에 대한 조언을 찾고있다.

+0

페이지 히트를 추적하려고하지 않고 기록하지 않습니다. 뷰를 정의하는 비즈니스 규칙이 있습니다. 사용자가 페이지에 있어야하는 시간, 같은 사용자가 마지막으로 방문한 이후의 시간 등 ... – Joe

답변

2
당신은 http://www.apathybutton.com 봐 (그리고에 연결하는 클라우드 커버 에피소드) 일을 계산 할 수있는 확장 성이 뛰어난 방법에 대해 이야기 취할 수도 있습니다

. (그것은 당신의 요구에 과잉 될 수도 있지만 잘하면 그것은 당신에게 몇 가지 옵션을 제공합니다.)

+0

링크가 죽었습니다 ... –

0

당신은 푸른에서 "진단"기능의 작동 방법에 대해 살펴을 할 수 있습니다. 당신이하는 일에 대해 진단을 사용할 것이기 때문에가 아니라 비슷한 문제를 다루고 있으며 영감을 줄 수 있기 때문입니다. 나는 데이터 감사 기능을 구현하려고하고 있으며이를 테이블 스토리지에 로깅하여 업데이트를 함께 지연시키고 묶어서 진단을 통해 많은 영감을 얻었습니다.

이제 Azure의 진단 기능은 각 역할이 약간의 배경 "전송"스레드를 시작한다는 것입니다. 따라서 흔적을 작성할 때마다 로컬 메모리의 목록에 저장되며 배경 스레드는 (기본적으로) 모든 요청을 묶어 매분마다 테이블 저장소로 전송합니다. 시나리오에서

, 나는 각 역할 인스턴스가 히트의 수를 추적하고 데이터베이스 그렇게 모든 분을 업데이트 백그라운드 스레드를 사용하게됩니다. 아마도 페이지 식별자에 대한 카운터를 증가시키면서 각 웹 롤마다 정적 ConcurrentDictionary (또는 싱글 톤을 걸기)를 사용할 것입니다. 목록에있는 동일한 카운터를 업데이트하기 위해 여러 요청을 허용하는 스레드 처리 코드가 필요합니다. 또는 각 "히트"가 공유 스레드 안전 목록에 새 레코드를 추가하도록 허용하십시오.
그런 다음 분당 한 번 백그라운드 스레드를 사용하여 마지막 시간 이후 페이지 당 히트 수를 증가시키고 로컬 카운터를 0으로 재설정하거나 해당 방법으로가는 경우 공유 목록을 비 웁니다 (다시, 멀티 스레딩 및 잠금).

중요한 것은 데이터베이스 업데이트가 확실한지 확인하는 것입니다. 데이터베이스에서 read-current-count를 수행하고 증분 한 다음 다시 쓰면 두 개의 다른 웹 역할 인스턴스가 동시에이를 수행하여 하나의 업데이트가 손실 될 수 있습니다.

편집 : 여기에 의 간단한 샘플은 일 수 있습니다.

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

1

당신은 페이지로 수를 합계하여 대기 항목을 축소 한 SQL 배치/왕복 쓰기, 큐 메모리와 타이머 드레인의 큐를 유지 할 수있다. 예를 들어 TVP를 사용하면 하나의 sproc 호출로 대기열에있는 합계를 쓸 수 있습니다.

뷰 카운트가 메모리와 잠정적으로 작성 되었기 때문에 작성된 것은 보증하지 않지만 페이지 카운트는 중요한 데이터가 아니어야하며 충돌은 거의 발생하지 않아야합니다.