缓存
SetCache
基于集合数据结构的无序唯一元素缓存接口
SetCache
SetCache 定义了基于集合(Set)数据结构的缓存操作接口,用于管理无序且元素唯一的集合数据。
接口定义
type SetCache[K any, V any] interface {
Add(ctx context.Context, key K, members ...V) error
Members(ctx context.Context, key K) ([]V, error)
IsMember(ctx context.Context, key K, member V) (bool, error)
Count(ctx context.Context, key K) (int64, error)
Remove(ctx context.Context, key K, members ...V) error
Delete(ctx context.Context, key K) error
}泛型参数
字段
类型
数据特点
- 无序性:成员没有固定顺序
- 唯一性:相同成员只会存储一次
- 高效性:成员检查时间复杂度 O(1)
user:tags:123 → {"golang", "redis", "docker"}方法说明
Add
将一个或多个成员添加到集合中。
Add(ctx context.Context, key K, members ...V) error参数:
ctx- context.Context 上下文key- 集合键members- 要添加的成员(可变参数)
返回值:
error- 错误信息
注意: 已存在的成员会被忽略。
Members
获取集合中的所有成员。
Members(ctx context.Context, key K) ([]V, error)参数:
ctx- context.Context 上下文key- 集合键
返回值:
[]V- 所有成员的切片error- 错误信息
IsMember
检查指定成员是否存在于集合中。
IsMember(ctx context.Context, key K, member V) (bool, error)参数:
ctx- context.Context 上下文key- 集合键member- 要检查的成员
返回值:
bool- 成员是否存在error- 错误信息
Count
获取集合中的成员数量。
Count(ctx context.Context, key K) (int64, error)参数:
ctx- context.Context 上下文key- 集合键
返回值:
int64- 成员数量error- 错误信息
Remove
从集合中移除指定的成员。
Remove(ctx context.Context, key K, members ...V) error参数:
ctx- context.Context 上下文key- 集合键members- 要移除的成员(可变参数)
返回值:
error- 错误信息
Delete
删除整个集合。
Delete(ctx context.Context, key K) error参数:
ctx- context.Context 上下文key- 集合键
返回值:
error- 错误信息
实现示例
用户标签缓存
import (
"github.com/gin-gonic/gin"
xCache "github.com/bamboo-services/bamboo-base-go/major/cache"
)
type UserTagsCache struct {
*xCache.Cache
}
// Add 添加用户标签
func (c *UserTagsCache) Add(ctx context.Context, userID string, tags ...string) error {
key := "user:tags:" + userID
return c.RDB.SAdd(ctx, key, tags).Err()
}
// Members 获取所有标签
func (c *UserTagsCache) Members(ctx context.Context, userID string) ([]string, error) {
key := "user:tags:" + userID
return c.RDB.SMembers(ctx, key).Result()
}
// IsMember 检查标签是否存在
func (c *UserTagsCache) IsMember(ctx context.Context, userID string, tag string) (bool, error) {
key := "user:tags:" + userID
return c.RDB.SIsMember(ctx, key, tag).Result()
}
// Count 获取标签数量
func (c *UserTagsCache) Count(ctx context.Context, userID string) (int64, error) {
key := "user:tags:" + userID
return c.RDB.SCard(ctx, key).Result()
}
// Remove 移除标签
func (c *UserTagsCache) Remove(ctx context.Context, userID string, tags ...string) error {
key := "user:tags:" + userID
return c.RDB.SRem(ctx, key, tags).Err()
}
// Delete 删除所有标签
func (c *UserTagsCache) Delete(ctx context.Context, userID string) error {
key := "user:tags:" + userID
return c.RDB.Del(ctx, key).Err()
}使用标签缓存
type UserTagsService struct {
cache *UserTagsCache
}
func NewUserTagsService(cache *xCache.Cache) *UserTagsService {
return &UserTagsService{
cache: &UserTagsCache{Cache: cache},
}
}
// AddTags 添加用户标签
func (s *UserTagsService) AddTags(ctx context.Context, userID string, tags ...string) error {
// 更新数据库
if err := s.addTagsToDB(userID, tags...); err != nil {
return err
}
// 更新缓存
return s.cache.Add(ctx, userID, tags...)
}
// GetTags 获取用户标签
func (s *UserTagsService) GetTags(ctx context.Context, userID string) ([]string, error) {
// 先从缓存获取
tags, err := s.cache.Members(ctx, userID)
if err != nil {
return nil, err
}
// 如果缓存为空,从数据库加载
if len(tags) == 0 {
tags, err = s.getTagsFromDB(userID)
if err != nil {
return nil, err
}
// 写入缓存
if len(tags) > 0 {
_ = s.cache.Add(ctx, userID, tags...)
}
}
return tags, nil
}
// HasTag 检查用户是否有指定标签
func (s *UserTagsService) HasTag(ctx context.Context, userID string, tag string) (bool, error) {
return s.cache.IsMember(ctx, userID, tag)
}
// RemoveTags 移除用户标签
func (s *UserTagsService) RemoveTags(ctx context.Context, userID string, tags ...string) error {
// 更新数据库
if err := s.removeTagsFromDB(userID, tags...); err != nil {
return err
}
// 更新缓存
return s.cache.Remove(ctx, userID, tags...)
}使用场景
用户标签
type UserTagsCache interface {
xCache.SetCache[string, string]
}适用于:
- 用户兴趣标签
- 技能标签
- 分类标签
权限管理
type PermissionCache interface {
xCache.SetCache[string, string]
}适用于:
- 用户权限列表
- 角色权限
- 资源访问权限
在线用户
type OnlineUsersCache interface {
xCache.SetCache[string, string]
}适用于:
- 在线用户集合
- 活跃用户统计
- 房间成员列表
去重场景
type UniqueItemsCache interface {
xCache.SetCache[string, string]
}适用于:
- 已处理的任务 ID
- 去重的消息 ID
- 唯一的访客 IP
最佳实践
1. 批量添加
使用可变参数一次添加多个成员:
// ✅ 批量添加
cache.Add(ctx, userID, "golang", "redis", "docker")
// ❌ 逐个添加
cache.Add(ctx, userID, "golang")
cache.Add(ctx, userID, "redis")
cache.Add(ctx, userID, "docker")2. 成员检查
使用 IsMember 而不是 Members:
// ✅ 高效检查
exists, _ := cache.IsMember(ctx, userID, "golang")
// ❌ 低效检查
members, _ := cache.Members(ctx, userID)
for _, m := range members {
if m == "golang" {
exists = true
break
}
}3. 数量统计
使用 Count 而不是 Members:
// ✅ 高效统计
count, _ := cache.Count(ctx, userID)
// ❌ 低效统计
members, _ := cache.Members(ctx, userID)
count := len(members)4. 空集合处理
区分空集合和不存在的集合:
func (s *UserTagsService) GetTags(ctx context.Context, userID string) ([]string, error) {
tags, err := s.cache.Members(ctx, userID)
if err != nil {
return nil, err
}
// 空切片表示集合存在但为空
// nil 表示集合不存在
if tags == nil {
tags = []string{}
}
return tags, nil
}高级操作
集合运算
虽然接口不直接支持,但可以使用 Redis 命令:
// 交集:获取两个用户的共同标签
func (c *UserTagsCache) CommonTags(ctx context.Context, userID1, userID2 string) ([]string, error) {
key1 := "user:tags:" + userID1
key2 := "user:tags:" + userID2
return c.RDB.SInter(ctx, key1, key2).Result()
}
// 并集:获取两个用户的所有标签
func (c *UserTagsCache) AllTags(ctx context.Context, userID1, userID2 string) ([]string, error) {
key1 := "user:tags:" + userID1
key2 := "user:tags:" + userID2
return c.RDB.SUnion(ctx, key1, key2).Result()
}
// 差集:获取用户1独有的标签
func (c *UserTagsCache) UniqueTags(ctx context.Context, userID1, userID2 string) ([]string, error) {
key1 := "user:tags:" + userID1
key2 := "user:tags:" + userID2
return c.RDB.SDiff(ctx, key1, key2).Result()
}随机获取
随机获取成员(用于推荐等场景):
// 随机获取一个标签
func (c *UserTagsCache) RandomTag(ctx context.Context, userID string) (string, error) {
key := "user:tags:" + userID
return c.RDB.SRandMember(ctx, key).Result()
}
// 随机获取多个标签
func (c *UserTagsCache) RandomTags(ctx context.Context, userID string, count int64) ([]string, error) {
key := "user:tags:" + userID
return c.RDB.SRandMemberN(ctx, key, count).Result()
}移动成员
将成员从一个集合移动到另一个:
// 将标签从一个用户移动到另一个用户
func (c *UserTagsCache) MoveTag(ctx context.Context, fromUserID, toUserID, tag string) error {
fromKey := "user:tags:" + fromUserID
toKey := "user:tags:" + toUserID
return c.RDB.SMove(ctx, fromKey, toKey, tag).Err()
}性能优化
使用 Pipeline
批量操作多个集合:
func (c *UserTagsCache) AddTagsToMultipleUsers(ctx context.Context, userTags map[string][]string) error {
pipe := c.RDB.Pipeline()
for userID, tags := range userTags {
key := "user:tags:" + userID
pipe.SAdd(ctx, key, tags)
}
_, err := pipe.Exec(ctx)
return err
}成员数量限制
避免单个集合成员过多:
func (c *UserTagsCache) Add(ctx context.Context, userID string, tags ...string) error {
key := "user:tags:" + userID
// 检查成员数量
count, err := c.RDB.SCard(ctx, key).Result()
if err != nil {
return err
}
// 限制最多 100 个标签
if count+int64(len(tags)) > 100 {
return errors.New("标签数量超过限制")
}
return c.RDB.SAdd(ctx, key, tags).Err()
}注意事项
- 无序性:不要依赖成员的顺序
- 唯一性:重复添加相同成员不会报错,但只存储一次
- 内存占用:大量成员会占用较多内存
- 序列化:复杂类型需要序列化为字符串
- 过期时间:整个集合过期,无法为单个成员设置 TTL