golang最终一致性分布式内存缓存插件库bcache的使用

Golang 最终一致性分布式内存缓存插件库 bcache 的使用

bcache 是一个 Go 库,用于在应用程序内创建分布式内存缓存。

特性

  • 可配置最大键数的 LRU 缓存
  • 节点之间最终一致性的同步
  • 数据会复制到所有节点
  • 缓存填充机制。当给定键的缓存不存在时,bcache 会协调缓存填充,确保只有一个调用会填充缓存,避免惊群效应或缓存雪崩

使用场景

  • 当外部缓存如 redis 或 memcached 需要额外的网络跳转不可接受时
  • 只需要简单的 Set、Get 和 Delete 操作的缓存
  • 有足够的内存来保存缓存数据

工作原理

  1. 节点使用 Gossip 协议相互发现

    只需要指定一个或几个节点作为引导节点,所有节点都会使用 gossip 协议相互发现

  2. 当有缓存 Set 和 Delete 操作时,事件会传播到所有节点

    因此,所有节点最终都会有同步的数据

缓存填充

缓存填充机制由 GetWithFiller 函数提供。

当给定键的缓存不存在时:

  • 它会调用提供的 Filler 函数
  • 使用 Filler 返回的值设置缓存

即使有很多 goroutine 调用 GetWithFiller,给定的 Filler 函数也只会为每个键调用一次。这样可以避免缓存雪崩。

快速开始

服务器 1

bc, err := New(Config{
    // PeerID:     1, // 留空,会根据 MAC 地址自动设置
    ListenAddr: "192.168.0.1:12345",
    Peers:      nil, // 为 nil 因为我们将此节点用作引导节点
    MaxKeys:    1000,
    Logger:     logrus.New(),
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}
bc.Set("my_key", "my_val", 86400) // 设置缓存,过期时间86400秒

服务器 2

bc, err := New(Config{
    // PeerID:     2, // 留空,会根据 MAC 地址自动设置
    ListenAddr: "192.168.0.2:12345",
    Peers:      []string{"192.168.0.1:12345"}, // 连接到服务器1
    MaxKeys:    1000,
    Logger:     logrus.New(),
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}
bc.Set("my_key2", "my_val2", 86400)

服务器 3

bc, err := New(Config{
    // PeerID:     3, // 会根据 MAC 地址自动设置
    ListenAddr: "192.168.0.3:12345",
    Peers:      []string{"192.168.0.1:12345"}, // 连接到服务器1
    MaxKeys:    1000,
    Logger:     logrus.New(),
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}
val, exists := bc.Get("my_key2") // 获取服务器2设置的缓存

GetWithFiller 示例

c, err := New(Config{
    PeerID:     3,
    ListenAddr: "192.168.0.3:12345",
    Peers:      []string{"192.168.0.1:12345"},
    MaxKeys:    1000,
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}

// 使用填充函数获取缓存
val, exp, err := bc.GetWithFiller("my_key2", func(key string) (string, error) {
    // 从数据库获取值
    // ...
    return value, nil
}, 86400) // 设置过期时间为86400秒

完整示例 Demo

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/iwanbk/bcache"
	"github.com/sirupsen/logrus"
)

func main() {
	// 创建第一个节点作为引导节点
	node1, err := bcache.New(bcache.Config{
		ListenAddr: "127.0.0.1:8001",
		Peers:      nil, // 引导节点没有对等节点
		MaxKeys:    1000,
		Logger:     logrus.New(),
	})
	if err != nil {
		log.Fatal(err)
	}

	// 创建第二个节点连接到第一个节点
	node2, err := bcache.New(bcache.Config{
		ListenAddr: "127.0.0.1:8002",
		Peers:      []string{"127.0.0.1:8001"},
		MaxKeys:    1000,
		Logger:     logrus.New(),
	})
	if err != nil {
		log.Fatal(err)
	}

	// 设置缓存
	node1.Set("key1", "value1", 60) // 60秒过期
	node2.Set("key2", "value2", 60)

	// 等待同步
	time.Sleep(2 * time.Second)

	// 获取缓存
	if val, exists := node1.Get("key2"); exists {
		fmt.Printf("Node1 got key2: %s\n", val)
	}

	if val, exists := node2.Get("key1"); exists {
		fmt.Printf("Node2 got key1: %s\n", val)
	}

	// 使用填充函数
	val, _, err := node1.GetWithFiller("key3", func(key string) (string, error) {
		// 模拟从数据库获取值
		return "value_from_db", nil
	}, 60)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Filled value: %s\n", val)

	// 关闭缓存
	node1.Close()
	node2.Close()
}

这个示例展示了如何创建两个节点,设置和获取缓存,以及使用填充函数。节点之间会自动同步数据,实现最终一致性。


更多关于golang最终一致性分布式内存缓存插件库bcache的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang最终一致性分布式内存缓存插件库bcache的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang bcache库使用指南

bcache是一个基于Golang实现的最终一致性分布式内存缓存插件库,它提供了简单易用的API来实现分布式缓存功能。下面我将详细介绍bcache的使用方法。

安装

go get github.com/allegro/bigcache/bcache

基本使用

1. 创建缓存实例

package main

import (
	"fmt"
	"time"
	
	"github.com/allegro/bigcache/bcache"
)

func main() {
	// 创建缓存配置
	config := bcache.Config{
		Shards:             1024,           // 分片数量
		LifeWindow:         10 * time.Minute, // 条目存活时间
		CleanWindow:        5 * time.Minute,  // 清理间隔
		MaxEntriesInWindow: 1000 * 10 * 60,   // 存活窗口内的最大条目数
		MaxEntrySize:       500,              // 单个条目最大大小(字节)
		Verbose:            true,             // 是否打印详细日志
		HardMaxCacheSize:   8192,             // 最大缓存大小(MB)
	}
	
	// 创建缓存实例
	cache, err := bcache.NewBigCache(config)
	if err != nil {
		panic(err)
	}
	
	// 使用缓存...
}

2. 基本操作

// 设置缓存
err := cache.Set("key1", []byte("value1"))
if err != nil {
	fmt.Println("Set error:", err)
}

// 获取缓存
entry, err := cache.Get("key1")
if err != nil {
	fmt.Println("Get error:", err)
} else {
	fmt.Println("Got value:", string(entry))
}

// 删除缓存
err = cache.Delete("key1")
if err != nil {
	fmt.Println("Delete error:", err)
}

// 检查缓存是否存在
err = cache.Append("key1", []byte("_append"))
if err == bcache.ErrEntryNotFound {
	fmt.Println("Key does not exist")
}

3. 分布式特性使用

bcache本身是一个单机内存缓存,要实现分布式缓存,通常需要结合其他组件。以下是使用bcache作为本地缓存层,结合Redis实现分布式缓存的示例:

package main

import (
	"fmt"
	"time"
	
	"github.com/allegro/bigcache/bcache"
	"github.com/go-redis/redis/v8"
)

type DistributedCache struct {
	localCache *bcache.BigCache
	redisClient *redis.Client
}

func NewDistributedCache() (*DistributedCache, error) {
	// 本地缓存配置
	localConfig := bcache.Config{
		Shards:             1024,
		LifeWindow:         5 * time.Minute,
		CleanWindow:        1 * time.Minute,
		MaxEntriesInWindow: 1000 * 10 * 60,
		MaxEntrySize:       500,
		HardMaxCacheSize:   1024,
	}
	
	localCache, err := bcache.NewBigCache(localConfig)
	if err != nil {
		return nil, err
	}
	
	// Redis客户端
	redisClient := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})
	
	return &DistributedCache{
		localCache:  localCache,
		redisClient: redisClient,
	}, nil
}

func (dc *DistributedCache) Get(key string) ([]byte, error) {
	// 先查本地缓存
	value, err := dc.localCache.Get(key)
	if err == nil {
		return value, nil
	}
	
	// 本地没有则查Redis
	cmd := dc.redisClient.Get(context.Background(), key)
	if cmd.Err() == redis.Nil {
		return nil, bcache.ErrEntryNotFound
	} else if cmd.Err() != nil {
		return nil, cmd.Err()
	}
	
	// 将Redis结果写入本地缓存
	value = []byte(cmd.Val())
	_ = dc.localCache.Set(key, value)
	
	return value, nil
}

func (dc *DistributedCache) Set(key string, value []byte) error {
	// 先设置Redis
	err := dc.redisClient.Set(context.Background(), key, value, 0).Err()
	if err != nil {
		return err
	}
	
	// 再设置本地缓存
	return dc.localCache.Set(key, value)
}

高级特性

1. 自定义序列化

// 使用JSON序列化存储结构体
type User struct {
	ID   int
	Name string
}

func setUser(cache *bcache.BigCache, key string, user User) error {
	data, err := json.Marshal(user)
	if err != nil {
		return err
	}
	return cache.Set(key, data)
}

func getUser(cache *bcache.BigCache, key string) (User, error) {
	data, err := cache.Get(key)
	if err != nil {
		return User{}, err
	}
	
	var user User
	err = json.Unmarshal(data, &user)
	return user, err
}

2. 缓存统计

stats := cache.Stats()
fmt.Printf("Hits: %d, Misses: %d, DelHits: %d, DelMisses: %d\n",
	stats.Hits, stats.Misses, stats.DelHits, stats.DelMisses)

最佳实践

  1. 合理配置分片数:Shards数量应根据预期缓存大小和并发量设置,通常1024是个不错的起点

  2. 设置适当的过期时间:LifeWindow应根据业务需求设置,避免内存无限增长

  3. 处理缓存穿透:对于不存在的键,可以在缓存中设置空值标记

  4. 批量操作优化:对于批量操作,考虑使用pipeline

  5. 监控缓存命中率:定期检查stats,优化缓存策略

bcache作为本地缓存性能极高,在分布式环境中通常作为一级缓存使用,配合Redis等持久化存储实现多级缓存架构,既能保证性能又能确保数据的最终一致性。

回到顶部