缓存
ListCache
基于列表数据结构的有序可重复元素缓存接口
ListCache
ListCache 定义了基于列表(List)数据结构的缓存操作接口,用于管理有序且允许重复元素的列表数据。
接口定义
type ListCache[K any, V any] interface {
Prepend(ctx context.Context, key K, values ...V) error
Append(ctx context.Context, key K, values ...V) error
Range(ctx context.Context, key K, start int64, end int64) ([]V, error)
Index(ctx context.Context, key K, index int64) (*V, error)
Len(ctx context.Context, key K) (int64, error)
Pop(ctx context.Context, key K) (*V, error)
PopLast(ctx context.Context, key K) (*V, error)
Remove(ctx context.Context, key K, count int64, value V) error
Delete(ctx context.Context, key K) error
}泛型参数
字段
类型
数据特点
- 有序性:元素按插入顺序排列
- 可重复:允许存储相同的元素
- 双端操作:支持头尾插入和弹出
message:queue → ["msg1", "msg2", "msg3", "msg2"]
↑ ↑
头部 尾部方法说明
Prepend
将一个或多个值插入到列表头部(左侧)。
Prepend(ctx context.Context, key K, values ...V) error参数:
ctx- context.Context 上下文key- 列表键values- 要插入的值(可变参数)
返回值:
error- 错误信息
注意: 多个值按顺序插入,最后一个值会在最前面。
Append
将一个或多个值追加到列表尾部(右侧)。
Append(ctx context.Context, key K, values ...V) error参数:
ctx- context.Context 上下文key- 列表键values- 要追加的值(可变参数)
返回值:
error- 错误信息
Range
按索引范围获取列表元素。
Range(ctx context.Context, key K, start int64, end int64) ([]V, error)参数:
ctx- context.Context 上下文key- 列表键start- 起始索引(支持负数)end- 结束索引(支持负数)
返回值:
[]V- 元素切片error- 错误信息
索引说明:
0表示第一个元素-1表示最后一个元素-2表示倒数第二个元素
Index
获取指定索引位置的元素。
Index(ctx context.Context, key K, index int64) (*V, error)参数:
ctx- context.Context 上下文key- 列表键index- 索引位置(支持负数)
返回值:
*V- 指向元素的指针error- 错误信息
Len
获取列表的长度。
Len(ctx context.Context, key K) (int64, error)参数:
ctx- context.Context 上下文key- 列表键
返回值:
int64- 列表长度error- 错误信息
Pop
从列表头部弹出一个元素并返回。
Pop(ctx context.Context, key K) (*V, error)参数:
ctx- context.Context 上下文key- 列表键
返回值:
*V- 指向弹出元素的指针error- 错误信息
PopLast
从列表尾部弹出一个元素并返回。
PopLast(ctx context.Context, key K) (*V, error)参数:
ctx- context.Context 上下文key- 列表键
返回值:
*V- 指向弹出元素的指针error- 错误信息
Remove
从列表中移除指定数量的匹配元素。
Remove(ctx context.Context, key K, count int64, value V) error参数:
ctx- context.Context 上下文key- 列表键count- 移除数量count > 0:从头部开始移除count < 0:从尾部开始移除count = 0:移除所有匹配项
value- 要移除的值
返回值:
error- 错误信息
Delete
删除整个列表。
Delete(ctx context.Context, key K) error参数:
ctx- context.Context 上下文key- 列表键
返回值:
error- 错误信息
实现示例
消息队列缓存
import (
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
xCache "github.com/bamboo-services/bamboo-base-go/major/cache"
)
type Message struct {
ID string `json:"id"`
Content string `json:"content"`
Time int64 `json:"time"`
}
type MessageQueueCache struct {
*xCache.Cache
}
// Prepend 在队列头部插入消息
func (c *MessageQueueCache) Prepend(ctx context.Context, queueName string, messages ...string) error {
key := "queue:" + queueName
values := make([]interface{}, len(messages))
for i, msg := range messages {
values[i] = msg
}
return c.RDB.LPush(ctx, key, values...).Err()
}
// Append 在队列尾部追加消息
func (c *MessageQueueCache) Append(ctx context.Context, queueName string, messages ...string) error {
key := "queue:" + queueName
values := make([]interface{}, len(messages))
for i, msg := range messages {
values[i] = msg
}
return c.RDB.RPush(ctx, key, values...).Err()
}
// Range 获取指定范围的消息
func (c *MessageQueueCache) Range(ctx context.Context, queueName string, start, end int64) ([]string, error) {
key := "queue:" + queueName
return c.RDB.LRange(ctx, key, start, end).Result()
}
// Index 获取指定位置的消息
func (c *MessageQueueCache) Index(ctx context.Context, queueName string, index int64) (*string, error) {
key := "queue:" + queueName
val, err := c.RDB.LIndex(ctx, key, index).Result()
if err == redis.Nil {
return nil, nil
}
return &val, err
}
// Len 获取队列长度
func (c *MessageQueueCache) Len(ctx context.Context, queueName string) (int64, error) {
key := "queue:" + queueName
return c.RDB.LLen(ctx, key).Result()
}
// Pop 从队列头部弹出消息
func (c *MessageQueueCache) Pop(ctx context.Context, queueName string) (*string, error) {
key := "queue:" + queueName
val, err := c.RDB.LPop(ctx, key).Result()
if err == redis.Nil {
return nil, nil
}
return &val, err
}
// PopLast 从队列尾部弹出消息
func (c *MessageQueueCache) PopLast(ctx context.Context, queueName string) (*string, error) {
key := "queue:" + queueName
val, err := c.RDB.RPop(ctx, key).Result()
if err == redis.Nil {
return nil, nil
}
return &val, err
}
// Remove 移除指定消息
func (c *MessageQueueCache) Remove(ctx context.Context, queueName string, count int64, message string) error {
key := "queue:" + queueName
return c.RDB.LRem(ctx, key, count, message).Err()
}
// Delete 删除整个队列
func (c *MessageQueueCache) Delete(ctx context.Context, queueName string) error {
key := "queue:" + queueName
return c.RDB.Del(ctx, key).Err()
}使用消息队列
type MessageQueueService struct {
cache *MessageQueueCache
}
func NewMessageQueueService(cache *xCache.Cache) *MessageQueueService {
return &MessageQueueService{
cache: &MessageQueueCache{Cache: cache},
}
}
// Enqueue 入队(追加到尾部)
func (s *MessageQueueService) Enqueue(ctx context.Context, queueName string, message string) error {
return s.cache.Append(ctx, queueName, message)
}
// Dequeue 出队(从头部弹出)
func (s *MessageQueueService) Dequeue(ctx context.Context, queueName string) (*string, error) {
return s.cache.Pop(ctx, queueName)
}
// Peek 查看队列头部(不弹出)
func (s *MessageQueueService) Peek(ctx context.Context, queueName string) (*string, error) {
return s.cache.Index(ctx, queueName, 0)
}
// Size 获取队列大小
func (s *MessageQueueService) Size(ctx context.Context, queueName string) (int64, error) {
return s.cache.Len(ctx, queueName)
}
// GetRecent 获取最近的 N 条消息
func (s *MessageQueueService) GetRecent(ctx context.Context, queueName string, count int64) ([]string, error) {
return s.cache.Range(ctx, queueName, -count, -1)
}使用场景
消息队列
type MessageQueueCache interface {
xCache.ListCache[string, string]
}适用于:
- 任务队列
- 消息队列
- 事件队列
操作历史
type HistoryCache interface {
xCache.ListCache[string, string]
}适用于:
- 用户操作历史
- 浏览历史
- 搜索历史
排行榜
type LeaderboardCache interface {
xCache.ListCache[string, string]
}适用于:
- 实时排行榜
- 热门列表
- 推荐列表
栈结构
type StackCache interface {
xCache.ListCache[string, string]
}适用于:
- 撤销/重做功能
- 状态栈
- 调用栈
最佳实践
1. 队列模式
使用 Append + Pop 实现 FIFO 队列:
// 入队
cache.Append(ctx, "queue", "msg1", "msg2", "msg3")
// 出队
msg, _ := cache.Pop(ctx, "queue") // 返回 "msg1"2. 栈模式
使用 Prepend + Pop 实现 LIFO 栈:
// 入栈
cache.Prepend(ctx, "stack", "item1", "item2", "item3")
// 出栈
item, _ := cache.Pop(ctx, "stack") // 返回 "item3"3. 限制列表长度
使用 LTrim 保持固定长度:
func (c *MessageQueueCache) AppendWithLimit(ctx context.Context, queueName string, message string, maxLen int64) error {
key := "queue:" + queueName
// 追加消息
if err := c.RDB.RPush(ctx, key, message).Err(); err != nil {
return err
}
// 保留最新的 maxLen 条
return c.RDB.LTrim(ctx, key, -maxLen, -1).Err()
}4. 分页获取
使用 Range 实现分页:
func (s *MessageQueueService) GetPage(ctx context.Context, queueName string, page, pageSize int64) ([]string, error) {
start := (page - 1) * pageSize
end := start + pageSize - 1
return s.cache.Range(ctx, queueName, start, end)
}5. 批量操作
使用可变参数批量插入:
// ✅ 批量追加
cache.Append(ctx, "queue", "msg1", "msg2", "msg3")
// ❌ 逐个追加
cache.Append(ctx, "queue", "msg1")
cache.Append(ctx, "queue", "msg2")
cache.Append(ctx, "queue", "msg3")高级操作
阻塞弹出
实现阻塞队列(等待元素):
// 阻塞弹出(等待 5 秒)
func (c *MessageQueueCache) BlockingPop(ctx context.Context, queueName string, timeout time.Duration) (*string, error) {
key := "queue:" + queueName
result, err := c.RDB.BLPop(ctx, timeout, key).Result()
if err == redis.Nil {
return nil, nil
}
if err != nil {
return nil, err
}
if len(result) < 2 {
return nil, nil
}
return &result[1], nil
}列表间移动
将元素从一个列表移动到另一个:
// 从源队列弹出并推入目标队列
func (c *MessageQueueCache) Move(ctx context.Context, srcQueue, dstQueue string) error {
srcKey := "queue:" + srcQueue
dstKey := "queue:" + dstQueue
_, err := c.RDB.RPopLPush(ctx, srcKey, dstKey).Result()
return err
}插入到指定位置
在某个元素前后插入:
// 在 pivot 元素之前插入
func (c *MessageQueueCache) InsertBefore(ctx context.Context, queueName, pivot, value string) error {
key := "queue:" + queueName
return c.RDB.LInsertBefore(ctx, key, pivot, value).Err()
}
// 在 pivot 元素之后插入
func (c *MessageQueueCache) InsertAfter(ctx context.Context, queueName, pivot, value string) error {
key := "queue:" + queueName
return c.RDB.LInsertAfter(ctx, key, pivot, value).Err()
}性能优化
使用 Pipeline
批量操作多个列表:
func (c *MessageQueueCache) EnqueueMultiple(ctx context.Context, messages map[string][]string) error {
pipe := c.RDB.Pipeline()
for queueName, msgs := range messages {
key := "queue:" + queueName
values := make([]interface{}, len(msgs))
for i, msg := range msgs {
values[i] = msg
}
pipe.RPush(ctx, key, values...)
}
_, err := pipe.Exec(ctx)
return err
}避免大列表
限制列表长度,避免性能问题:
const MaxQueueSize = 10000
func (c *MessageQueueCache) Append(ctx context.Context, queueName string, messages ...string) error {
key := "queue:" + queueName
// 检查长度
length, err := c.RDB.LLen(ctx, key).Result()
if err != nil {
return err
}
// 超过限制则拒绝
if length+int64(len(messages)) > MaxQueueSize {
return errors.New("队列已满")
}
values := make([]interface{}, len(messages))
for i, msg := range messages {
values[i] = msg
}
return c.RDB.RPush(ctx, key, values...).Err()
}注意事项
- 索引范围:
Range的end是包含的,不同于 Go 切片 - 负数索引:
-1表示最后一个元素,-2表示倒数第二个 - 空列表:弹出空列表返回
nil,不是错误 - 内存占用:大列表会占用较多内存,考虑分片
- 性能:头部插入/删除比尾部慢,优先使用尾部操作
- 过期时间:整个列表过期,无法为单个元素设置 TTL