2013-08-28 20 views
13

Sto scrivendo alcuni webservices Go (anche implementando il server web in Go con http.ListenAndServe). Ho una mappa di strutture che vorrei conservare in memoria (con una dimensione approssimativa dei dati di 100Kb) per essere utilizzata da diverse richieste http.Golang: memorizzazione/memorizzazione nella cache dei valori da servire nelle seguenti richieste http

Qual è il modo migliore per ottenere questo in Go? Nella tua esperienza, è meglio usare variabili globali del pacchetto o sistemi di caching (come memcache/groupcache)?

risposta

2

Non concedersi un'ottimizzazione prematura. Definisci un pacchetto API Go per incapsulare i dati e quindi puoi modificare l'implementazione in qualsiasi momento. Ad esempio, semplicemente scarabocchiare,

package data 

type Key struct { 
    // . . . 
} 

type Data struct { 
    // . . . 
} 

var dataMap map[Key]Data 

func init() { 
    dataMap = make(map[Key]Data) 
} 

func GetData(key Key) (*Data, error) { 
    data := dataMap[key] 
    return &data, nil 
} 
+0

peterSO, quale tipo di implementazione utilizzi personalmente? –

+1

Sia l'ottimizzazione prematura che la generalizzazione prematura sono cattive. :) –

+0

Potrei sbagliarmi, ma poiché i dati devono essere mantenuti per tutte le richieste, che sono concomitanti, qualsiasi accesso a una variabile globale che lo trattiene dovrebbe essere protetto da un mutex. – kostix

3

La soluzione semplice che risolve ancora il problema è generalmente l'approccio giusto. Memorizzare i dati localmente sul server è semplice ed efficiente e può essere fatto in molte circostanze, anche se viene utilizzato un sistema più ricco.

A titolo di esempio, il mese scorso ho messo un trucco veloce vivere (http://ubuntu-edge.info), che ha preso un po 'di carico a causa della notizia diffondendo in tutto la campagna di crowdfunding, e il processo di Go avuto molto basso carico la maggior parte del tempo, in una piccola macchina EC2. I dati erano banali e venivano semplicemente memorizzati nella cache, aggiornati una volta al minuto dal database che veniva aggiornato da un processo esterno.

Generalmente guardo memcache (e ora groupcache) quando lo faccio io stesso significherebbe replicare più delle loro funzionalità. Ad esempio, quando ci sono troppi dati da conservare in memoria o quando la perdita di un server può influire sulle prestazioni dell'intero sistema perché i dati memorizzati nella cache sono andati tutti in una volta, o perché le prestazioni sono influenzate dalla contesa.

Come nota a margine, groupcache è in realtà una libreria, quindi è possibile incorporarla nel server anziché come un vero sistema esterno.

12

In aggiunta alle risposte che hai già ricevuto, considera l'utilizzo del ricevitore al numero method values e http.HandlerFunc.

Se i dati sono dati che vengono caricati prima dell'avvio del processo, si potrebbe andare con qualcosa di simile:

type Common struct { 
    Data map[string]*Data 
} 

func NewCommon() (*Common, error) { 
    // load data 
    return c, err 
} 

func (c *Common) Root(w http.ResponseWriter, r *http.Request) { 
    // handler 
} 

func (c *Common) Page(w http.ResponseWriter, r *http.Request) { 
    // handler 
} 

func main() { 
    common, err := NewCommon() 
    if err != nil { ... } 

    http.HandleFunc("/", common.Root) 
    http.HandleFunc("/page", common.Page) 

    http.ListenAndServe(...) 
} 

Questo funziona bene se tutti i dati Common è di sola lettura. Se i dati Common è di lettura/scrittura, quindi ti consigliamo di avere qualcosa di più simile:

type Common struct { 
    lock sync.RWMutex 
    data map[string]Data // Data should probably not have any reference fields 
} 

func (c *Common) Get(key string) (*Data, bool) { 
    c.lock.RLock() 
    defer c.lock.RUnlock() 
    d, ok := c.data[key] 
    return &d, ok 
} 

func (c *Common) Set(key string, d *Data) { 
    c.lock.Lock() 
    defer c.lock.Unlock() 
    c.data[key] = *d 
} 

Il resto è fondamentalmente la stessa, tranne che invece di accesso ai dati attraverso i campi del ricevitore direttamente, faresti accesso loro attraverso i getter ei setter. In un server web in cui viene letta la maggior parte dei dati, è probabile che si desideri un RWMutex, in modo che le letture possano essere eseguite simultaneamente l'una con l'altra. Un altro vantaggio del secondo approccio è che hai incapsulato i dati, quindi puoi aggiungere scritture trasparenti a e/o letture da un memcache o una groupcache o qualcosa del genere in futuro se la tua applicazione dovesse crescere.

Una cosa che mi piace molto su come definire i miei gestori come i metodi su un oggetto è che rende molto più facile per unità di testarli: si può facilmente definire un table driven test che include i valori desiderati e l'output che ci si aspetta, senza dover per andare in giro con le variabili globali.

+0

Questo tipo di soluzione non è buono per il garbage collector e non va bene per il caricamento -balancing dei server http, perché ognuno risponderà con dati diversi su richieste client diverse. –

Problemi correlati