mirror of
https://github.com/redis/go-redis.git
synced 2026-10-28 16:35:06 +08:00
* feat(csc): add standalone client-side caching (#3748) * Add local cache * add context support to Cache.Get, time-gated stale placeholder recovery in Reserve * fix:Nil waitCh guard causes busy loop ignoring context & cloneBytes conflates nil and empty byte slices * Refactor local cache and move to internal/cache, defer public surface * fix GET acquires exclusive lock on every cache hit * Record cache-hit recency atomically instead of write-locking * Bound Get's in-progress wait by StaleTimeout * feat(csc): add command key extraction and caching logic (#3773) * feat(csc): add command key extraction and caching logic * fix: lastKey incorrectly reads first key position instead of last * fix: extractRedisKeys over-extracts non-key arguments as keys * csc: Support standalone client (#3794) * feat(csc): add command key extraction and caching logic * fix: lastKey incorrectly reads first key position instead of last * fix: extractRedisKeys over-extracts non-key arguments as keys * CSC: Support standalone client * fix race condition in tests * perf(csc): record cache-hit recency atomically instead of write-locking * fix(csc): bound Get wait by StaleTimeout, restrict CSC to DB 0, warn on RESP2 * Exclude XREAD from cacheable commands * feat(csc): client-side caching SharedTracking standalone connection (#3851) * feat(csc): multi-strategy client-side caching * fix linting issues * fix spellcheck issues * Add drainer optimizations * Fix spellcheck * Fix bugs and address comments * Fix csc per connection issues * Fix Orphan cache after sidecar start error * Fix clone misses conn eviction hook * reduce to sharedTracking only * Address comments * Address testing issue & remove xpending from cacheable * Address codex comments * Remove csc command stat api residue * fix(csc): harden cache and connection lifecycle Serialize cache publication with connection removal and reinitialization, evict entries whenever tracking coverage is lost, and preserve CSC lifecycle state across derived clients. Guard connection-state commands, cache negative replies safely, support wrapped transports with bounded probes, and make background drainers obey pool limits and timeouts. Add lifecycle, race, transport, negative-cache, command-guard, and derived-client regression coverage and document CSC credential restrictions. * Address comments * fix(csc): harden command guards and close handling - normalize pointer-encoded string arguments consistently with the RESP writer - detect pointer-based CLIENT TRACKING and SORT_RO BY/GET arguments - reject raw SUBSCRIBE commands on CSC-managed pooled connections - preserve typed Pub/Sub support through dedicated connections - keep ordinary client Close synchronous and return teardown errors - defer handler-context Close to prevent background drainer self-deadlocks - serialize concurrent and repeated CSC teardown * add acl to wordlist * docs: remove CSC strategy guide from PR * chore: restore gitignore * update comment in csc_integration.go to be more accurate * fix RE test * Add an example and readme * fix spellcheck ci * fix doctests * fix(csc): harden cache and connection lifecycle Serialize cache publication with connection lifecycle changes and evict entries when tracking coverage is lost. * fix issues * Rename from multiDB to NonZeroDB
85 lines
2.1 KiB
Go
85 lines
2.1 KiB
Go
// Example: use Redis server-assisted client-side caching with a standalone
|
|
// go-redis client.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
addr := os.Getenv("REDIS_ADDR")
|
|
if addr == "" {
|
|
addr = "localhost:6379"
|
|
}
|
|
|
|
cached := redis.NewClient(&redis.Options{
|
|
Addr: addr,
|
|
Protocol: 3,
|
|
DB: 0,
|
|
ClientSideCacheConfig: &redis.ClientSideCacheConfig{
|
|
MaxEntries: 1_000,
|
|
},
|
|
})
|
|
defer cached.Close()
|
|
|
|
// Use a separate client to show that a write from another connection
|
|
// invalidates the cached value.
|
|
writer := redis.NewClient(&redis.Options{Addr: addr})
|
|
defer writer.Close()
|
|
|
|
if err := cached.Ping(ctx).Err(); err != nil {
|
|
log.Fatalf("connect cached client: %v", err)
|
|
}
|
|
if err := writer.Ping(ctx).Err(); err != nil {
|
|
log.Fatalf("connect writer: %v", err)
|
|
}
|
|
|
|
const key = "example:csc:greeting"
|
|
if err := writer.Set(ctx, key, "hello", 0).Err(); err != nil {
|
|
log.Fatalf("set initial value: %v", err)
|
|
}
|
|
|
|
first := get(ctx, cached, key) // Cache miss: fetched from Redis.
|
|
second := get(ctx, cached, key) // Cache hit: served from local memory.
|
|
stats := cached.CSCStats()
|
|
|
|
fmt.Printf("first read: %s\n", first)
|
|
fmt.Printf("second read: %s\n", second)
|
|
fmt.Printf("cache stats: %d hit, %d miss\n", stats.Hits, stats.Misses)
|
|
|
|
if err := writer.Set(ctx, key, "hello again", 0).Err(); err != nil {
|
|
log.Fatalf("update value: %v", err)
|
|
}
|
|
|
|
// Invalidation notifications are processed asynchronously. Wait until the
|
|
// cache observes the update rather than relying on a fixed sleep.
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for {
|
|
value := get(ctx, cached, key)
|
|
if value == "hello again" {
|
|
fmt.Printf("after invalidation: %s\n", value)
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
log.Fatal("timed out waiting for cache invalidation")
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func get(ctx context.Context, client *redis.Client, key string) string {
|
|
value, err := client.Get(ctx, key).Result()
|
|
if err != nil {
|
|
log.Fatalf("get %q: %v", key, err)
|
|
}
|
|
return value
|
|
}
|