现在,您可以使用 go-redis 客户端库探索高级数据建模,该库支持二级索引、JSON 文档以及搜索数据的功能。
通过最新增加的功能,go-redis 正在逐步支持 Redis Community Edition(Redis Stack 和 Redis 8)、Redis Software 和 Redis Cloud 提供的所有功能。JSON 数据建模无需多言:作为最流行的交换格式,它受到所有 Redis 版本的支持,能够实现灵活的数据管理,解决将分层数据存储在数据库中的许多现代问题。然而,只有通过搜索功能,您才能最大程度地利用您的数据。Redis 查询引擎支持索引哈希和 JSON 文档,并简化了任何单个数据片的检索。
让我们看一些最近增加功能的示例。要测试本文提出的示例,请在 AWS、GCP 或 Azure 上创建一个免费的 Redis Cloud 账户。如果您更喜欢在笔记本电脑上运行 Redis,请查看 Docker Hub 上的 Redis 8(M02 版本已可用),或使用首选的安装方法安装 Redis Stack。
像往常一样创建与 Redis 的连接(使用您的 Redis Cloud 账户凭据连接。Docker 镜像默认密码为空)。
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
Protocol: 2,
})
尝试创建简单的 JSON 文档并按如下方式执行标准命令
client.JSONSet(ctx, "greeting", "$", `{"hello": "world"}`)
greeting := client.JSONGet(ctx, "greeting", "$.hello")
fmt.Println(greeting.Val())
// Output: ["world"]
expandedJSON, e := greeting.Expanded()
if e != nil {
panic(e)
}
fmt.Println(expandedJSON.([]interface{})[0])
// Output: world
client.JSONSet(ctx, "data", "$", `{"a": 10, "b": {"a": [12, 13, 17, 23, 55]}}`)
res := client.JSONGet(ctx, "data", "$.b.a[0]")
fmt.Println(res.Val())
// Output: [12]
让我们尝试一些更高级的内容。想象一下您想将购物车建模为 JSON 文档
{
"lastAccessedTime": 1673354843,
"creationTime": 1673354843,
"cart":[
{
"id": "hp-printer",
"price": 59.90,
"quantity": 1
},
{
"id":" MacBook",
"price": 2990.99,
"quantity": 1
}
],
"location": "13.456260,43.300751",
"visited": ["www.redis.io"]
}
您可以使用以下命令创建文档
session := `{"lastAccessedTime":1673354843, "creationTime":1673354843, "cart":[{"id":"hp-printer","price":59.90,"quantity":1}, {"id":"MacBook","price":2990.99,"quantity":1}], "location":"13.456260,43.300751", "visited":["www.redis.io"]}`
client.JSONSet(ctx, "session:3rf2iu23fu", "$", session)
除了选择性地读取或写入购物车中的不同商品外,您还可以使用JSONPath 语法在购物车内搜索。因此,如果您想查看购物车中有哪些商品价格低于 2500 美元,解决方案如下
res = client.JSONGet(ctx, "session:3rf2iu23fu", "$.cart[?(@.price<2500)].id")
fmt.Println(res.Val())
// Output: ["hp-printer"]
使用 JSON 格式建模数据的能力发展了简单而高效的哈希数据结构。只有 JSON 格式支持分层数据和使用 JSONPath 进行内部搜索。但事情变得更有趣了,因为您可以跨不同的 JSON 文档进行搜索。
go-redis 客户端库现在支持 Redis 查询引擎 中的所有功能。回到我们的购物车示例,如果您想检查哪些购物车包含特定商品,或者可能进一步细化搜索以包含所需的条件,这可以使用 go-redis 中提供的 API 实现。考虑以下示例,我们将创建一个索引。
// Clean up the index
client.FTDropIndexWithArgs(ctx, "json_session_idx", &redis.FTDropIndexOptions{DeleteDocs: true})
// Fields definition
id_idx := &redis.FieldSchema{FieldName: "$.cart[*].id", FieldType: redis.SearchFieldTypeTag, As: "id"}
loc_idx := &redis.FieldSchema{FieldName: "$.location", FieldType: redis.SearchFieldTypeGeo, As: "location"}
price_idx := &redis.FieldSchema{FieldName: "$.cart[*].price", FieldType: redis.SearchFieldTypeNumeric, As: "price"}
// Create the index
client.FTCreate(ctx, "json_session_idx",
&redis.FTCreateOptions{OnJSON: true, Prefix: []interface{}{"session:"}}, id_idx, loc_idx, price_idx).Result()
前面的代码执行以下操作:
索引创建完成后,数据库中所有现有和新文档都将被添加。索引创建是异步的,但只要添加新文档,索引就会同步发生。如果您在现有数据集上创建新索引,索引过程将非常快速。现在,让我们添加几个会话。
session := `{"lastAccessedTime":1673354843, "creationTime":1673354843, "cart":[{"id":"hp-printer","price":59.90,"quantity":1}, {"id":"MacBook","price":2990.99,"quantity":1}], "location":"13.456260,43.300751", "visited":["www.redis.io"]}`
session_2 := `{"lastAccessedTime":1705182581, "creationTime":1705182581, "cart":[{"id":"hp-printer","price":59.90,"quantity":1}], "location":"-94.582306,39.082520", "visited":["www.redis.io"]}`
// Add the documents to the index
client.JSONSet(ctx, "session:3rf2iu23fu", "$", session)
client.JSONSet(ctx, "session:2fh2p9349h", "$", session_2)
第一个示例是精确匹配操作,用于检索有多少个会话包含购物车中的特定商品。
// Search for sessions with a cart that contains an item with id "hp-printer"
result := client.FTSearchWithArgs(ctx, "json_session_idx", "@id:{hp\\-printer}", &redis.FTSearchOptions{NoContent: true})
fmt.Println(result.Val().Total)
// Output: 2
现在,我们将结合多个条件来搜索价格。以下命令返回在所有会话中都存在的那些商品的价格
result = client.FTSearchWithArgs(ctx, "json_session_idx", "@id:{hp\\-printer} @location:[$lon $lat $radius $units] @price:[50 +inf]", &redis.FTSearchOptions{Return: []redis.FTSearchReturn{{FieldName: "price"}, {FieldName: "__v_score"}}, Params: map[string]interface{}{"lon": "13.482410", "lat": "43.486019", "radius": 60, "units": "km"}, DialectVersion: 2})
for _, doc := range result.Val().Docs {
fmt.Println(doc.Fields["price"])
}
// Output: 59.9
查询语法文档解释了要使用的查询语法。搜索我们的 Redis 文档确实功能强大,但当我们在应用程序中引入语义搜索时,事情会变得更加有趣。
go-redis 客户端完全支持向量搜索,这使得语义搜索成为可能,它是Redis for AI(用于开发生成式 AI 应用程序的集成包)的构建块之一。让我们通过一个简单的例子来探索这一功能:我们将存储三个句子,并测试数据库中的哪个句子与查询句子在语义上最相似。向量搜索支持哈希和 JSON 文档;在此示例中,我们将再次将句子建模为 JSON 文档。
doc_1 := "This is a technical document, it describes the SID sound chip of the Commodore 64"
doc_2 := "The Little Prince is a short story by Antoine de Saint-Exupéry, the best known of his literary productions, published on April 6, 1943 in New York"
doc_3 := "Pasta alla carbonara is a characteristic dish of Lazio and more particularly of Rome, prepared with popular ingredients and with an intense flavour."
q := "The Adventures of Pinocchio is a fantasy novel for children written by Carlo Collodi, pseudonym of the journalist and writer Carlo Lorenzini, published for the first time in Florence in February 1883."
定义好数据集和查询句子后,我们来创建索引。
client.FTDropIndexWithArgs(ctx, "json_doc_idx", &redis.FTDropIndexOptions{DeleteDocs: true})
hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT64", Dim: 1536, DistanceMetric: "COSINE"}
client.FTCreate(ctx, "json_doc_idx",
&redis.FTCreateOptions{OnJSON: true, Prefix: []interface{}{"json:doc:"}},
&redis.FieldSchema{FieldName: "$.v", FieldType: redis.SearchFieldTypeVector, As: "v", VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).Result()
前面的代码清理了索引和已索引的文档;然后,它为向量创建了一个具有以下特性的索引:
我们将创建向量嵌入表示来生成数据库文档。为此,我们借助OpenAI 嵌入模型。这个辅助函数将一个句子转换为一个包含 1536 个 float64 元素的数组。选择的 OpenAI 嵌入模型是 text-embedding-ada-002。
func createEmbedding(ctx context.Context, text string) []float64 {
// Set OPENAI_API_KEY environment variable
client := openai.NewClient()
// Using text-embedding-ada-002 model
params := openai.EmbeddingNewParams{
Input: openai.F[openai.EmbeddingNewParamsInputUnion](shared.UnionString(text)),
Model: openai.F(openai.EmbeddingModelTextEmbeddingAda002),
EncodingFormat: openai.F(openai.EmbeddingNewParamsEncodingFormatFloat),
}
response, err := client.Embeddings.New(ctx, params)
if err != nil {
return nil
}
return response.Data[0].Embedding
}
请注意,您可以按如下方式安装OpenAI 库的官方 Go 库
go get github.com/openai/openai-go
现在我们可以将我们的句子及其语义表示存储在数据库中。
client.JSONSet(ctx, "json:doc:1", "$", map[string]interface{}{"v": createEmbedding(ctx, doc_1), "content": doc_1})
client.JSONSet(ctx, "json:doc:2", "$", map[string]interface{}{"v": createEmbedding(ctx, doc_2), "content": doc_2})
client.JSONSet(ctx, "json:doc:3", "$", map[string]interface{}{"v": createEmbedding(ctx, doc_3), "content": doc_3})
我们已准备好搜索与查询句子相似的句子!
searchOptionsJSON := &redis.FTSearchOptions{
Return: []redis.FTSearchReturn{{FieldName: "$.content"}, {FieldName: "__v_score"}},
SortBy: []redis.FTSearchSortBy{{FieldName: "__v_score", Asc: true}},
DialectVersion: 2,
Params: map[string]interface{}{"vec": convertFloatsToString(createEmbedding(ctx, q))},
}
result := client.FTSearchWithArgs(ctx, "json_doc_idx", "*=>[KNN 1 @v $vec]", searchOptionsJSON)
// RESP2
for _, doc := range result.Val().Docs {
fmt.Println(doc.Fields["$.content"])
}
// Output: The Little Prince is a short story by Antoine de Saint-Exupéry, the best known of his literary productions, published on April 6, 1943 in New York
正如预期的那样,查询句子(关于“匹诺曹的冒险”)在语义上与“小王子”更相似,而不是其他文档。
您可以使用免费的 Redis Cloud 数据库或 Docker Hub 上提供的Redis 8 M02 Docker 镜像来测试本文中的示例。此外
您将使用 Redis 构建什么?