dot Redis 8 来了——它是开源的

了解更多

Go 中的 JSON 和搜索支持

现在,您可以使用 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。

JSON 支持

像往常一样创建与 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()

前面的代码执行以下操作:

  1. 清理名为 json_session_idx 的可能存在的索引及其索引的数据
  2. 定义购物车中需要索引的数据。指定的字段包括
    1. 所有商品的 product ID,我们要求使用 精确匹配,通过 TAG 字段索引
    2. 用户的地理位置,以(经度,纬度)对表示,我们要求进行GEO 地理索引
    3. 对于购物车中商品的价格,我们要求使用NUMERIC 字段类型提供的数值搜索。
  3. 创建索引,指定应索引前缀为“session:”的 JSON 文档。

索引创建完成后,数据库中所有现有和新文档都将被添加。索引创建是异步的,但只要添加新文档,索引就会同步发生。如果您在现有数据集上创建新索引,索引过程将非常快速。现在,让我们添加几个会话。

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

现在,我们将结合多个条件来搜索价格。以下命令返回在所有会话中都存在的那些商品的价格

  • 其 product ID 等于“hp-printer”
  • 位于指定位置 60 公里范围内
  • 价格高于 50 美元
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()

前面的代码清理了索引和已索引的文档;然后,它为向量创建了一个具有以下特性的索引:

  • 向量是 64 字节的浮点数。
  • 向量包含 1536 个元素,由我们稍后将介绍的嵌入模型确定。
  • 距离指标是余弦距离
  • 索引方法类型为HNSW
  • 我们正在索引前缀为“json:doc:”的 JSON 文档
  • 向量存储在 JSON 文档的“$.v”路径

我们将创建向量嵌入表示来生成数据库文档。为此,我们借助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 满足所有用例

您可以使用免费的 Redis Cloud 数据库或 Docker Hub 上提供的Redis 8 M02 Docker 镜像来测试本文中的示例。此外

  • 请参阅Go 指南页面,了解更多关于 go-redis 客户端库的信息
  • GitHub 仓库查找更多示例 
  • 可视化您的数据,分析查询执行,或向 Copilot 询问关于 Redis 或您数据的任何问题。Copilot 可以利用 AI 帮助您根据数据库中的数据构建查询。下载 Redis Insight;它是免费的。

您将使用 Redis 构建什么?