2012-08-07 13 views
14

Sto cercando di implementare un contatore di visualizzazione della pagina nella memoria della tabella blu. Se si dice che due utenti visitano la pagina contemporaneamente e il valore corrente su PageViews = 100, è garantito che PageViews = 102 dopo l'operazione di aggiornamento?Operazioni atomiche nella memoria blu della tabella

+1

Non conosco la domanda, ma perché non aggiungere un database SQL da 100 MB per $ 5 al mese. Affare SQL con serrature. – Paparazzi

risposta

23

La risposta dipende da come si implementa il contatore. :-)

La memoria di tabella non dispone di un operatore di "incremento", quindi è necessario leggere il valore corrente (100) e aggiornarlo al nuovo valore (101). Lo storage di tabelle utilizza una concorrenza ottimistica, quindi se si esegue ciò che viene naturale quando si utilizza la libreria client di archiviazione .NET, è probabile che si verifichi un'eccezione quando due processi hanno tentato di farlo contemporaneamente. Questo sarebbe il flusso:

  1. Processo A legge il valore di PageViews e riceve 100.
  2. processo B legge il valore di PageViews e riceve 100.
  3. Processo A fa un aggiornamento condizionale per PageViews che mezzi "imposta le PageViews su 101 purché siano attualmente 100." Questo succede.
  4. Il processo B esegue le stesse operazioni e non riesce, poiché la condizione preliminare (PageViews == 100) è falsa.

La cosa ovvia da fare quando si riceve l'errore è ripetere il processo. (Leggi il valore corrente, che ora è 101, e aggiorna a 102.) Ciò porterà sempre (eventualmente) al tuo contatore con il valore corretto.

Ci sono altre possibilità e abbiamo fatto un intero episodio di Cloud Cover su come implementare un contatore veramente scalabile: http://channel9.msdn.com/Shows/Cloud+Cover/Cloud-Cover-Episode-43-Scalable-Counters-with-Windows-Azure.

Le operazioni descritte in questo video sono probabilmente eccessive se le collisioni sono improbabili. Ad esempio, se il tuo tasso di successo è di un secondo al secondo, il normale schema di "lettura, incremento, scrittura" sarà sicuro ed efficiente. Se, d'altra parte, ricevi 1000 hit al secondo, vorrai fare qualcosa di più intelligente.

EDIT

Volevo solo per chiarire per le persone che leggono questo per capire la concorrenza ottimistica ... l'operazione condizionale non è realmente "set PageViews a 101 il più a lungo è attualmente 100." È più come "imposta PageViews su 101 finché non è cambiato dall'ultima volta che l'ho guardato." (Ciò si ottiene usando l'ETag che è tornato nella richiesta HTTP.)

+0

Suggerisco di usare AutoRenewLease.DoOnce (dal nostro buon amico smarx, http://blog.smarx.com/posts/managing-concurrency-in-windows-azure-with-leases) :) –

+0

Per quanto mi piaccia la gente usando il mio codice :-), non penso di seguire come sarebbe utile qui? – smarx

+0

Oops, DoOnce era sbagliato, ma avrei usato AutoRenewLease su un BLOB che ha un nome = PageView.PartitionKey + "_" + PageView.RowKey. Una volta bloccato il blob otterrà quel record e aumenterà il conteggio. In questo modo puoi essere sicuro che ogni visualizzazione di pagina è contabilizzata. Tutto ciò utilizzando la gestione dei guasti transitori per garantire che, in caso di problemi, il codice torni fino a quando non è in grado di registrare la visualizzazione di pagina. –

8

Si potrebbe anche riconsiderare la parte "conteggio". Perché non trasformarlo in un processo in 2 fasi?

Fase 1 - Registrazione Page Visualizzazioni

Ogni volta che un utente visualizza una pagina aggiungere un record a una tabella (chiamiamolo PageViews). Le informazioni si dovrebbe aggiungere in uno di questi negozi sarebbe il seguente:

  • PartitionKey = NomePagina
  • RowKey = GUID casuale

Dopo un paio di vista si dovrebbe avere qualcosa di simile:

  • MyPage.aspx - someGuid
  • MyPage.aspx - someGuid
  • SomePage.aspx - someGuid
  • MyPage.aspx - someGuid

Fase 2 - Il conteggio delle pagine visualizzate

Quello che vogliamo fare ora è ottenere tutti quei record, contarli, aumentare un contatore da qualche parte e cancellare tutti i record. Supponiamo che tu abbia più lavoratori in esecuzione. Entrambi i tuoi dipendenti avrebbero un ciclo in esecuzione casuale tra 1 e 10 minuti. Ogni volta che il tempo di lavoro è trascorso, il lease verrà preso su un BLOB se non è stato ancora preso in leasing (questo dovrebbe sempre essere lo stesso blob, è possibile utilizzare AutoRenewLease).

Il primo operaio ricevendo il blocco può andare avanti e fare il conteggio:

  1. ottenere tutti i record dalla tabella PageViewRecordings o dalla cache
  2. Conte tutte pagine viste per pagina
  3. Aggiornamento contano qualche parte
  4. Elimina i record presi in considerazione durante il conteggio

Il problema qui è t è molto difficile trasformare questo in un processo idempotente. Cosa succede se la tua istanza si blocca tra il conteggio e l'eliminazione? Avrai un conteggio delle pagine aumentato, ma dal momento che gli elementi non sono stati eliminati verranno aggiunti al conteggio totale la prossima volta che li elaborerai.

Questo è il motivo per cui suggerirei quanto segue. Nella stessa tabella (PageViews), si registreranno anche le visualizzazioni di pagina totali, nella stessa partizione. Ma i dati sarà un po 'diversa (questo sarà un singolo record in quella partizione che tiene il conteggio totale):

  • PartitionKey = NomePagina
  • RowKey = Guid.Empty (basta non utilizzare un GUID casuale in questo modo conosciamo la differenza tra una visualizzazione di pagina registrata e il record che contiene il conteggio totale).
  • conte = La corrente di visualizzazione di pagine

Ciò è perfettamente possibile perché Storage Table è lo schema di meno. E perché stiamo facendo questo? Perché abbiamo transazioni se ci limitiamo alla stessa tabella + partizione con un massimo di 100 entità. Cosa possiamo fare con questo?

  1. Utilizzando Take, otteniamo 100 record da quella tabella + partizione.
  2. Il primo record che otterremo è il record "contatore". Perché? Perché la sua rowkey è Guid.Empty e ordinamento è lessicografico
  3. Conte questi record (-1 perché il primo record non è una visualizzazione di una pagina, è solo il nostro contatore segnaposto)
  4. Aggiornare la proprietà Count del record contatore
  5. Elimina 99 (o meno) altri record
  6. Salva conversioni utilizzando Batch.
  7. Ripetere fino a quando rimane solo 1 record (il contatore del contatore).

E ogni X minuti i lavoratori vedranno se non c'è un lease sul BLOB, ottenere un leasing e riavviare il processo.

Questa risposta è abbastanza chiara o devo aggiungere del codice?

+0

Ora capisco cosa stai dicendo sui contratti di blob. Questo è sensato, ma preferisco comunque qualcosa come sharding (come nell'episodio di Cloud Cover). – smarx

+0

Mi piace questa idea. Non c'è bisogno di codice, lo capisco chiaramente. Ma intendevi, "se là ** non è ** un leasing, allora il lavoratore prende in affitto e riavvia il processo" giusto? – States

+0

Sì, ho aggiornato la risposta. –

1

Sono arrivato con la stessa domanda. Con la libreria Python di Azure, sto sviluppando un semplice incremento contatore utilizzando eTag e If-Match anziché il blocco. L'idea di base è di riprovare ad aumentare il contatore fino a quando l'aggiornamento non viene eseguito correttamente con determinati criteri, il che significa che nessun altro aggiornamento interferisce con questo aggiornamento in esecuzione. Se la richiesta di aggiornamenti è pesante, è necessario invocare sharding.

https://github.com/flyakite/simple-scalable-datastore/blob/master/datastore/azuretable.py

1

Se utilizzando siti web Azure, quindi Azure code e WebJobs è un'altra opzione. In uno dei miei scenari, tuttavia, seguirò l'approccio di sharding e WebJobs aggiornerà periodicamente gli aggregati. Una tabella di archiviazione tabella di Azure di UserPageViews con PartitionKey = User e RowKey = Page. Non saranno consentiti due utenti simultanei con lo stesso ID utente.

Problemi correlati