1. 连接 Redis
go
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
Protocol: 2, // 推荐 RESP2
// UnstableResp3: true, // 若要体验 RESP3 + Raw*
})
2. 准备示例数据
go
user1 := map[string]interface{}{
"name": "Paul John",
"email": "paul.john@example.com",
"age": 42,
"city": "London",
}
user2 := map[string]interface{}{
"name": "Eden Zamir",
"email": "eden.zamir@example.com",
"age": 29,
"city": "Tel Aviv",
}
user3 := map[string]interface{}{
"name": "Paul Zamir",
"email": "paul.zamir@example.com",
"age": 35,
"city": "Tel Aviv",
}
3. 为 JSON 数据建索引
go
_, err := rdb.FTCreate(
ctx, "idx:users",
&redis.FTCreateOptions{
OnJSON: true, // 针对 JSON
Prefix: []interface{}{"user:"},// 仅索引 user:* 键
},
// schema
&redis.FieldSchema{ // 全文本字段
FieldName: "$.name",
As: "name",
FieldType: redis.SearchFieldTypeText,
},
&redis.FieldSchema{ // TAG 用于精确匹配/聚合
FieldName: "$.city",
As: "city",
FieldType: redis.SearchFieldTypeTag,
},
&redis.FieldSchema{ // 数值范围查询
FieldName: "$.age",
As: "age",
FieldType: redis.SearchFieldTypeNumeric,
},
).Result()
if err != nil { panic(err) }
写入 JSON 文档
go
_, _ = rdb.JSONSet(ctx, "user:1", "$", user1)
_, _ = rdb.JSONSet(ctx, "user:2", "$", user2)
_, _ = rdb.JSONSet(ctx, "user:3", "$", user3)
RediSearch 监听 user:
前缀,写入即自动索引。
4. 查询示例
4.1 复合搜索
go
res, _ := rdb.FTSearch(ctx,
"idx:users",
"Paul @age:[30 40]",
).Result()
fmt.Printf("匹配总数:%d\n", res.Total)
4.2 指定返回字段(RETURN
)
go
cities, _ := rdb.FTSearchWithArgs(
ctx, "idx:users", "Paul",
&redis.FTSearchOptions{
Return: []redis.FTSearchReturn{
{FieldName: "$.city", As: "city"},
},
},
).Result()
for _, d := range cities.Docs {
fmt.Println(d.Fields["city"])
}
// London / Tel Aviv
4.3 仅计数不取文档
go
cnt, _ := rdb.FTSearchWithArgs(
ctx, "idx:users", "Paul",
&redis.FTSearchOptions{CountOnly: true},
).Result()
fmt.Println(cnt.Total) // 输出 2
4.4 聚合:统计每个城市的用户数
go
agg, _ := rdb.FTAggregateWithArgs(
ctx, "idx:users", "*",
&redis.FTAggregateOptions{
GroupBy: []redis.FTAggregateGroupBy{
{
Fields: []interface{}{"@city"},
Reduce: []redis.FTAggregateReducer{
{Reducer: redis.SearchCount, As: "count"},
},
},
},
},
).Result()
for _, row := range agg.Rows {
fmt.Printf("%s - %v\n", row.Fields["city"], row.Fields["count"])
}
// London - 1
// Tel Aviv - 2
5. 切换到 Hash 模式的差异
- 建索引
go
_, err := rdb.FTCreate(
ctx, "hash-idx:users",
&redis.FTCreateOptions{
OnHash: true,
Prefix: []interface{}{"huser:"},
},
&redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText},
&redis.FieldSchema{FieldName: "city", FieldType: redis.SearchFieldTypeTag},
&redis.FieldSchema{FieldName: "age", FieldType: redis.SearchFieldTypeNumeric},
).Result()
OnHash:true
且 不需要 As 别名------字段名即 Hash 的 key。
- 写入数据
go
rdb.HSet(ctx, "huser:1", user1)
rdb.HSet(ctx, "huser:2", user2)
rdb.HSet(ctx, "huser:3", user3)
- 查询语法相同,但结果字段直接展开:
go
docs, _ := rdb.FTSearch(ctx,
"hash-idx:users",
"Paul @age:[30 40]",
).Result()
fmt.Println(docs.Docs[0].Fields["city"]) // 直接获取 city
6. 常见坑位
问题 | 解决方案 |
---|---|
返回 "dialect version not supported" |
显式升级 RediSearch ≥ 2.4,或在服务器 FT.CONFIG SET DEFAULT_DIALECT 2 |
Cannot create index while write traffic is on |
在生产写高峰创建索引时加 FT.CREATE ... ON JSON ... STOPWORDS 0 或使用 FT.ALTER 增量添加字段 |
RESP3 报 unsupported |
使用 Protocol:2 ,或启用 UnstableResp3 并用 RawResult() 解析 |
查询结果字段在 $ 键下 |
这是 JSON 模式的设计:所有字段存入 Fields["$"] 字符串里,需要二次解析或用 RETURN $.field |
7. 结语
通过 FTCreate → FTSearch → FTAggregate 等指令,Redis 在单节点即可完成近实时全文搜索与 OLAP 式聚合。
配合 go-redis,你可以:
- 统一接口同时操作 JSON 与 Hash;
- 利用 RETURN / CountOnly / GROUPBY 精确控制返回量;
- 轻松在 Go 服务中嵌入"搜索引擎 + KV 存储"二合一的能力。
赶快复制示例代码试一把,让你的业务查询飞起来 🚀