由于业务需要同时兼容 redis 及 redis-cluster,且希望能直接通过 配置 进行切换,因此决定开发该 redis 包。同时,添加了一些统一的 hook 以减少业务方的工作量。
longredis 是在 go-redis 的基础上做了封装。
主要封装了如下能力:
- Client 和 连接池管理 (统一的连接池)
- Client 和 Prefix 绑定
- Opentracing
- Metrics
- Error log
- Slow query log
- 超时管理
- 探活与重新连接
// 基础 client 初始化
NormalClient, err := longredis.NewClient(longredis.Option{
Addrs: []string{"127.0.0.1:6379"},
Password: "xxxxxx",
RedisType: longredis.ParseRedisType("single"), // 默认为 cluster
RequestTimeout: time.Second * 1,
})
if err != nil {
return err
}
// 增加不同的 prefix client => 不同业务使用不同的前缀以做区分
SessionCLient := NormalClient.SubPrefix("session")
FreqLimitClient := NormalClient.SubPrefix("limitation")
// ……
// 使用
sess, err := SessionCLient.Get(ctx, user.ID)
if err != nil {
// ……
}
特别的:
- GetBind / HGetBind
targetObj := &TarObj{}
err := xxClient.GetBind(ctx, "xxkey", targetObj)
if err != nil {
// ……
}
// 同理,HGetBind
- GetBool
isTrue, err := xxClient.GetBool(ctx, "xxkey")
if err != nil {
// ……
}
// 注意,bool 的 parse 方法为 strconv.ParseBool(),因此只能识别以下字符
// true => "1", "t", "T", "true", "TRUE", "True"
// false => "0", "f", "F", "false", "FALSE", "False"
// 当不是上述字符时,会返回 error
- TxPipeline
目前对 pipeline 没有做太多封装,是直接返回的 go-redis 的 pipeline,在使用时,需要注意 prefix 的问题。在一些场景下,需要显式使用 xxClient.WithPrefix(key) 进行 prefix 的添加。
c := redis.XxClient
pipe := c.TxPipeline(ctx)
pipe.Set(ctx, c.WithPrefix(key1), "xxx")
pipe.HSet(ctx, c.WithPrefix(key2), "field1", "xxx")
results, err := pipe.Exec()
if err != nil {
// ……
}
- 默认值
在 longredis 中,设置了一系列的默认值,具体可以查看 longredis/opt 每项的注释。 不过,绝大多数默认值你都无需关心,除非你遇到了特殊需求。
这几项重要的默认值你应当知道:
- RedisType: redis 类型,枚举值,可为 cluster single sentinel , 默认为 cluster。
- RequestTimeout: 请求超时时间,time.Duration ,默认为 3s,但会以你传的 ctx 的最小超时时间为准。
- SlowLog.SlowTime: 慢查询时间,time.Duration, 默认为 300ms
- Metrics、Tracing、ErrorLog、SlowLog 默认都是开启状态。
Option 设置了 yaml 和 json 的字段,为蛇形命名风格,因此可以通过 scan 的方式将 配置项中的字段 扫描到 option 中。 假设 config.yaml 中的内容有一段是这样的:
redis:
api_prefix: "api"
sess_prefix: "session"
figma_prefix: "fig"
options:
addrs: ["127.0.0.1:6379"]
redis_type: "cluster"
password: "xxx"
request_timeout: 1s
metrics:
namespace: mastergo
我们在项目中可以用这样的方式实例化 redis client:
var (
BaseClient *longredis.Client // 基础 client
ApiClient *longredis.Client // 用于 普通 api 的 client
SessClient *longredis.Client // 用于 session 的 client
FigmaClient *longredis.Client // 用于 figma 的 client
)
func Startup() error {
opt := longredis.Option{}
err := config.DeepGet("redis.options").Scan(&opt)
if err != nil {
return errors.Wrap(err, "init redis scan option fail")
}
// 设置 默认 logger
longredis.SetDefaultLogger(log.NewOctopusLog(longredis.LevelDebug))
apiPrefix := config.DeepGet("redis.api_prefix").String("api")
sessPrefix := config.DeepGet("redis.sess_prefix").String("session")
figmaPrefix := config.DeepGet("redis.figma_prefix").String("fig")
BaseClient, err = longredis.NewClient(opt) // prefix 为 ""
if err != nil {
return errors.Wrap(err, "init redis create client fail")
}
ApiClient = BaseClient.SubPrefix(apiPrefix) // prefix 为 api:
SessClient = ApiClient.SubPrefix(sessPrefix) // prefix 为 api:session:
FigmaClient = ApiClient.SubPrefix(fimaPrefix) // prefix 为 api:fig:
return nil
}
- 由于一些特殊场景的需要,把内部的 go-redis 的 client 开放了出来 (通过 xxClient.RedisConn() 获取),虽然这个 client 看起来也能使用一系列的 Get/Set 等方法,但该 client 缺少 prefix 的管理,会造成预期的 key 和实际的 key 不一致的问题,因此,不要直接使用 内部client,除非你知道自己在做什么。
// !!! 错误例子,引以为戒 !!!!
SessClient.RedisConn().Get(ctx, 1001001) // => 对应到命令为 GET 1001001
// 正确使用
SessClient.Get(ctx, 1001001) // => 对应的命令为 GET api:session:1001001
- client 自带了 连接池的管理能力,自带了 prefix 的管理能力,因此仅需要初始化一个
基础client
(baseClient、normalClient), 其他业务所需的特定前缀的 client,应当使用baseClient.SubPrefix(xxx)
的方式生成。而无需进行多次 longredis.NewClient(opt) 的实例化。
// !!! 错误例子,引以为戒 !!!!
BaseClient := longredis.NewClient(longredis.Option{
Addr: ["127.0.0.1:6379"],
Prefix: "",
})
ApiClient := longredis.NewClient(longredis.Option{
Addr: ["127.0.0.1:6379"],
Prefix: "api",
})
SessClient := longredis.NewClient(longredis.Option{
Addr: ["127.0.0.1:6379"],
Prefix: "api:session",
})
// 正确使用
BaseClient := longredis.NewClient(longredis.Option{
Addr: ["127.0.0.1:6379"],
})
ApiClient := BaseClient.SubPrefix("api")
SessClient := ApiClient.SubPrefix("session")
由于时间有限、水平有限,有所遗漏之处还请担待,发现 bug、有无法满足的需求 等,欢迎提 issue 或者 pr。
希望这个库能成为一个 开箱即用的、工程化的 redis 库,还有如下部分没有完成:
- pipeline 未封装,容易弄混 prefix
- 还有大量命令未封装
- 需要基于该 redis 库,集成多种常用的 redis 工具,例如
- 布隆过滤器
- 分布式锁
- metrics backend
- session
- 分布式 limiter
- 排行榜
- 任务队列
- id 生成器
- 签到表
- 需要更多的 example
可以参考 https://github.com/iamlongalong/redis-instances 进行操作
redis 真的是一个很牛逼的项目啊,不仅 redis 本身,还有其扩展部分 redis-stack,包含了
- redisinsight (图形操作面板 + 教程)
- Search (文档查询、全文索引)
- JSON (文档存储,类似 mongodb)
- graph (图数据库)
- Time series (时间序列数据库)
- 其他常用的使用的封装
- 布隆过滤器
- 布谷鸟过滤器
- 最小计数草图 (可以参考这篇文档介绍)
- top-k
- tdigest
具体可以查看官网信息
另外,redis 官方也做了很多教程啊!!! 可以查看 官方站点 不得不说,redis-stack 把实验教程直接融入到 redis-insight 项目中,真的是太新手友好了!具体可以查看 官方说明