go缓存设计 redis 发布订阅
一般缓存 有内存缓存, 没有就读redis, redis没有就读tidb;如何防止缓存穿透,这里我们用golang.org/x/sync/singleflight解决,还有缓存更新,比如多个节点如何更新,这里借用redis 发布订阅;需要更新redis 就发送一条发布消息, 订阅的所有结点就删除内存缓存
缓存代码如下:
package main import ( "context" "encoding/json" "fmt" "github.com/patrickmn/go-cache" "github.com/redis/go-redis/v9" "golang.org/x/sync/singleflight" "time" ) var globalCache *cache.Cache var channelKey = "cache_Channel" var rdb *redis.Client func init() { globalCache = cache.New(5*time.Minute, 10*time.Minute) rdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB }) ctx := context.Background() if _, err := rdb.Ping(ctx).Result(); err != nil { panic(err) } pubSub := rdb.Subscribe(ctx, channelKey) go func() { ch := pubSub.Channel() // 获取一个 channel,用于接收消息 for msg := range ch { // 循环读取消息 if key := msg.Payload; len(key) > 0 { rdb.Del(ctx, key).Err() globalCache.Delete(key) fmt.Println(fmt.Sprintf("redis 订阅%s", key)) } } }() } func GetCacheData[T any](ctx context.Context, key string, t T, load func() (T, error)) error { val, ok := globalCache.Get(key) if ok { t, ok = val.(T) if ok { fmt.Println(fmt.Sprintf("内存 读取 %s", key)) return nil } } client := rdb var sg singleflight.Group val, err, _ := sg.Do(key, func() (interface{}, error) { //读取redis str, err := client.Get(ctx, key).Result() if err == nil && len(str) > 0 { err = json.Unmarshal([]byte(str), &t) if err == nil { globalCache.Set(key, t, time.Minute*1) } fmt.Println(fmt.Sprintf("redis 读取 %s", key)) return t, err } if err != nil && err != redis.Nil { return nil, err } t, err = load() if err != nil { return nil, err } fmt.Println(fmt.Sprintf("db 读取 %s", key)) jsonByte, err := json.Marshal(t) if err != nil { return nil, err } //设置redis 缓存 err = client.Set(ctx, key, string(jsonByte), time.Minute*5).Err() if err != nil { return t, err } return t, nil }) if err != nil { return err } t = val.(T) globalCache.Set(key, t, time.Minute*1) return err } func RemoveCache(ctx context.Context, key string) error { //通知其他节点 err := rdb.Publish(ctx, channelKey, key).Err() if err != nil { return err } fmt.Println(fmt.Sprintf("redis 发布 %s", key)) return err }
这里redis 部分 是否需要方法 singleflight.Group 根据实际情况, 但是tidb部分需要放到里面,使用代码
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
key := "firstDemo111"
p := new(Person)
err := GetCacheData[*Person](ctx, key, p, f)
//fmt.Println(fmt.Sprintf("%+v", p))
err = GetCacheData[*Person](ctx, key, p, f)
// fmt.Println(fmt.Sprintf("%+v", p))
RemoveCache(ctx, key)
err = GetCacheData[*Person](ctx, key, p, f)
//fmt.Println(fmt.Sprintf("%+v", p))
err = GetCacheData[*Person](ctx, key, p, f)
ch := make(chan int, 1)
val := <-ch
fmt.Println(val)
fmt.Println(err)
}
type Person struct {
Name string
Age int
}
func f() (*Person, error) {
return &Person{Name: "demo", Age: 19}, nil
}
运行效果 确认,多个节点 redis 订阅 发布可以通信

windows技术爱好者
浙公网安备 33010602011771号