Mise en cache en Go (Golang) : Redis et autres outils
La mise en cache est l'un des mécanismes clés pour optimiser les performances des applications web et des microservices. Dans l'écosystème Go (Golang), il existe de nombreuses bibliothèques pour implémenter la mise en cache, allant de simples solutions en mémoire à des systèmes distribués basés sur Redis. Dans cet article, nous allons détailler trois approches principales : les structures de données intégrées (sync.Map), les bibliothèques en mémoire populaires (go-cache, freecache, bigcache) et l'intégration avec Redis.
1. Outils intégrés : sync.Map
Go fournit une map thread-safe — sync.Map. Elle convient pour une mise en cache simple avec un faible volume de données et des mises à jour peu fréquentes. Cependant, ses performances sous forte charge sont inférieures à celles des bibliothèques spécialisées.
Exemple d'utilisation de sync.Map :
package main
import ( "fmt" "sync" "time")
func main() { var cache sync.Map
// Écriture cache.Store("user:42", map[string]interface{}{ "name": "Alice", "age": 30, })
// Lecture avec vérification if val, ok := cache.Load("user:42"); ok { fmt.Println("Cache trouvé :", val) }
// Suppression cache.Delete("user:42")
// Mise en cache avec TTL (à implémenter manuellement) cache.Store("session:abc", time.Now().Unix())}Inconvénients : absence de TTL automatique, ensemble d'opérations limité, problèmes de performance possibles en cas d'écritures fréquentes.
2. Bibliothèques en mémoire
Pour des scénarios plus complexes, utilisez des bibliothèques spécialisées. Examinons trois bibliothèques populaires :
2.1 go-cache
Bibliothèque simple avec support du TTL. Idéale pour les petits projets.
import "github.com/patrickmn/go-cache"
func main() { c := cache.New(5*time.Minute, 10*time.Minute) // TTL par défaut, intervalle de nettoyage
c.Set("key", "value", cache.DefaultExpiration) val, found := c.Get("key") if found { fmt.Println(val) }}2.2 freecache
Bibliothèque avec une taille mémoire fixe (pas de surcharge GC). Supporte le TTL et offre des performances élevées.
import "github.com/coocood/freecache"
func main() { cache := freecache.NewCache(100 * 1024 * 1024) // 100 Mo
cache.Set([]byte("mykey"), []byte("myvalue"), 60) // TTL 60 sec val, err := cache.Get([]byte("mykey")) if err == nil { fmt.Println(string(val)) }}2.3 bigcache
Optimisée pour le stockage de grands volumes de données sans pauses GC. Fonctionne avec des données sérialisées.
import "github.com/allegro/bigcache/v3"
func main() { cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute))
cache.Set("key", []byte("value")) val, _ := cache.Get("key") fmt.Println(string(val))}3. Redis : mise en cache distribuée
Pour les systèmes distribués et les microservices, Redis reste le standard de facto. En Go, utilisez la bibliothèque go-redis/redis (v9+).
Connexion et opérations de base :
import ( "context" "fmt" "github.com/redis/go-redis/v9")
func main() { ctx := context.Background() rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, })
// Définition d'une valeur avec TTL err := rdb.Set(ctx, "user:42", `{"name":"Bob"}`, 10*time.Minute).Err() if err != nil { panic(err) }
// Lecture val, err := rdb.Get(ctx, "user:42").Result() if err == redis.Nil { fmt.Println("Cache non trouvé") } else if err != nil { panic(err) } else { fmt.Println("Valeur :", val) }}Fonctionnalités avancées de Redis en Go :
- Pipeline : regroupez plusieurs commandes pour réduire la latence réseau.
- Transactions : utilisez MULTI/EXEC pour des opérations atomiques.
- Pub/Sub : implémentez des notifications en temps réel.
- Redis Cluster : distribution automatique des données sur plusieurs nœuds.
4. Mise en cache de groupe
Pour les applications avec des charges de lecture élevées, la mise en cache de groupe (groupcache) évite les écrasements de cache (cache stampede). La bibliothèque groupcache de Google offre :
- Distribution décentralisée du cache entre plusieurs nœuds.
- Protection contre les écrasements de cache grâce à un mécanisme de déduplication des requêtes.
- Interface simple pour la récupération des données.
Exemple d'utilisation de groupcache :
import ( "fmt" "github.com/golang/groupcache")
func main() { peers := groupcache.NewHTTPPool("http://localhost:8080")
cache := groupcache.NewGroup("users", 64<<20, groupcache.GetterFunc( func(ctx groupcache.Context, key string, dest groupcache.Sink) error { // Chargement des données depuis la source (base de données, API, etc.) dest.SetString("Valeur pour " + key) return nil }, ))
var result string err := cache.Get(nil, "user:42", groupcache.StringSink(&result)) if err != nil { fmt.Println("Erreur :", err) } else { fmt.Println("Résultat :", result) }}5. Comparaison des performances
| Outil | Débit (ops/sec) | Latence (μs) | Utilisation mémoire | Distribution |
|---|---|---|---|---|
| sync.Map | ~5 000 000 | ~200 | Élevée (GC) | Non |
| go-cache | ~3 000 000 | ~300 | Moyenne | Non |
| freecache | ~10 000 000 | ~100 | Faible (pas de GC) | Non |
| bigcache | ~8 000 000 | ~150 | Faible (pas de GC) | Non |
| Redis (local) | ~100 000 | ~10 000 | Faible | Oui |
| groupcache | ~2 000 000 | ~500 | Moyenne | Oui |
6. Recommandations
- Pour les petits projets : utilisez
go-cacheousync.Map. - Pour des charges élevées avec des données volumineuses :
freecacheoubigcache. - Pour les systèmes distribués : Redis ou groupcache.
- Pour la protection contre les écrasements de cache : groupcache.
Conclusion
Le choix de l'outil de mise en cache dépend des exigences spécifiques de votre projet : volume de données, exigences de latence, besoin de distribution et budget mémoire. En Go, vous disposez d'un large éventail d'outils — des solutions intégrées simples aux systèmes distribués puissants. Expérimentez avec différentes bibliothèques et mesurez les performances dans votre contexte particulier.
Article original : PythonLib