Structure de base d'un système de cache
Les données mises en cache doivent être stockées en mémoire pour être accessibles rapidement. Quelle structure de données devrait être utilisée pour stocker les éléments de données? En général, une table de hachage est utilisée pour stocker les éléments de données, car cela offre de meilleures performances pour accéder aux données. En Go, nous n'avons pas besoin d'implémenter notre propre table de hachage car le type intégré map
implémente déjà une table de hachage. Ainsi, nous pouvons directement stocker les éléments de données du cache dans une map
.
Puisque le système de cache prend également en charge le nettoyage des données expirées, les éléments de données du cache doivent avoir une durée de vie. Cela signifie que les éléments de données du cache doivent être encapsulés et enregistrés dans le système de cache. Pour ce faire, nous devons tout d'abord implémenter l'élément de données du cache. Créez un nouveau répertoire cache
dans le répertoire GOPATH/src
, et créez un fichier source cache.go
:
package cache
import (
"encoding/gob"
"fmt"
"io"
"os"
"sync"
"time"
)
type Item struct {
Object interface{} // L'élément de données réel
Expiration int64 // Durée de vie
}
// Vérifie si l'élément de données est expiré
func (item Item) Expired() bool {
if item.Expiration == 0 {
return false
}
return time.Now().UnixNano() > item.Expiration
}
Dans le code ci-dessus, nous définissons une structure Item
qui a deux champs. Object
est utilisé pour stocker des objets de données de tout type, et Expiration
stocke l'heure d'expiration de l'élément de données. Nous fournissons également une méthode Expired()
pour le type Item
, qui renvoie une valeur booléenne indiquant si l'élément de données est expiré. Il est important de noter que l'heure d'expiration d'un élément de données est un timestamp Unix mesuré en nanosecondes. Comment déterminons-nous si un élément de données est expiré? C'est en fait assez simple. Nous enregistrons l'heure d'expiration de chaque élément de données, et le système de cache vérifie périodiquement chaque élément de données. Si l'heure d'expiration d'un élément de données est antérieure à l'heure actuelle, l'élément de données est supprimé du système de cache. Pour ce faire, nous utiliserons le module time pour implémenter des tâches périodiques.
Avec cela, nous pouvons maintenant implémenter le cadre du système de cache. Le code est le suivant :
const (
// Drapeau pour aucune heure d'expiration
NoExpiration time.Duration = -1
// Heure d'expiration par défaut
DefaultExpiration time.Duration = 0
)
type Cache struct {
defaultExpiration time.Duration
items map[string]Item // Stocke les éléments de données du cache dans une map
mu sync.RWMutex // Verrouillage lecture-écriture
gcInterval time.Duration // Intervalle de nettoyage des données expirées
stopGc chan bool
}
// Nettoie les éléments de données du cache expirés
func (c *Cache) gcLoop() {
ticker := time.NewTicker(c.gcInterval)
for {
select {
case <-ticker.C:
c.DeleteExpired()
case <-c.stopGc:
ticker.Stop()
return
}
}
}
Dans le code ci-dessus, nous avons implémenté la structure Cache
, qui représente la structure du système de cache. Le champ items
est une map utilisée pour stocker les éléments de données du cache. Comme vous pouvez le voir, nous avons également implémenté la méthode gcLoop()
, qui programme l'exécution périodique de la méthode DeleteExpired()
à l'aide d'un time.Ticker
. Un ticker
créé avec time.NewTicker()
enverra des données à partir de son canal ticker.C
à l'intervalle spécifié gcInterval
. Nous pouvons utiliser cette caractéristique pour exécuter périodiquement la méthode DeleteExpired()
.
Pour vous assurer que la fonction gcLoop()
peut se terminer normalement, nous écoutons les données du canal c.stopGc
. Si des données sont envoyées à ce canal, nous arrêtons l'exécution de gcLoop()
. Notez également que nous définissons les constantes NoExpiration
et DefaultExpiration
, où la première représente un élément de données qui n'expire jamais et la seconde représente un élément de données avec une heure d'expiration par défaut. Comment implémentons-nous DeleteExpired()
? Considérez le code ci-dessous :
// Supprime un élément de données du cache
func (c *Cache) delete(k string) {
delete(c.items, k)
}
// Supprime les éléments de données expirés
func (c *Cache) DeleteExpired() {
now := time.Now().UnixNano()
c.mu.Lock()
defer c.mu.Unlock()
for k, v := range c.items {
if v.Expiration > 0 && now > v.Expiration {
c.delete(k)
}
}
}
Comme vous pouvez le voir, la méthode DeleteExpired()
est assez simple. Nous avons juste besoin d'itérer sur tous les éléments de données et de supprimer ceux qui sont expirés.