当前位置: 代码网 > it编程>编程语言>Asp.net > Redis 全文检索及使用示例

Redis 全文检索及使用示例

2024年08月03日 Asp.net 我要评论
FT.CREATE命令中的可选参数STOPWORDS,将会影响分词命令FT.SYNUPDATE movies group1 爱情 凌虚那么FT.SEARCH movies '爱情'等价于FT.SEARCH movies '凌虚'TFIDF(默认使用)BM25(Elasticsearch 使用的打分算法)DISMAXDOCSCOREHAMMINGFT.SEARCH movies '爱情' RETURN 0 WITHSCORES。

序言

redis 除了我们所熟知的缓存功能之外,还通过 redisjsonredisearchredistimeseriesredisbloom 等模块支持了 json 数据、查询与搜索(包括全文检索、向量搜索、geo 地理位置等)、时序数据、概率计算等等扩展功能。这些模块既可以按需导入,也被全部打包到了 redis stack 中方便我们直接使用。

本文将会简述如何使用 redis 进行全文检索。

redis 全文检索

全文检索

全文检索是一种文本检索技术,其根据用户输入的词语或句子,在大量的文档数据中快速找到相关的内容。

全文检索的核心概念包括:

  • 分词:将文档(文本内容)拆分为一个个独立的词。
  • 倒排索引:一种索引类型,将词与文档进行关联,以便后续查询。
  • 相关度评分:对搜索结果的相关性进行评分。

使用示例

本文将会使用一个公开的电影数据集,构建一个电影搜索系统。

数据集

数据格式如下图所示:

为了行文方便,本文只会使用以下几个字段:

  • _id:唯一标识
  • title:电影标题
  • directors:导演
  • genres:电影类型
  • summary:内容摘要
  • rating:评分

我们使用 redis 的 json 格式存储数,导入数据使用的是 json.set 命令:

json.set movieid:1 $ '{"directors":"马丁·里特","genres":["剧情","动作","西部"],"rating":8.0,"title":"野狼 hombre","summary":"约翰·罗塞尔自幼是老罗塞尔先生从战俘中带回来并抚养他长大的,但是他生性豪放不羁……"}'

需要说明的是,redis 是一个 key-value 数据库,json 只是 value 的格式之一,而 key 总是一个字符串,key 在本文中定义为了 movieid:12345 这种固定前缀加 id 的格式。

使用 go 批量导入的部分代码如下:

func builddataset() {
	movies, _ := readmoviejson()

	rds := getredisclient()
	ctx := context.background()
	for _, v := range movies {
		b, _ := json.marshal(v)
		if r := rds.jsonset(ctx, "movieid:"+v.id, "$", b); r.err() != nil {
			panic(r.err())
		}
	}
}
构建索引

为了进行全文检索,我们必须要使用 ft.create 构建索引:

ft.create movies on json
prefix 1 movieid:
language chinese
schema
	$.title as title text weight 3
	$.directors.*.name as directors tag
	$.genres.* as genres tag
	$.summary as summary text
	$.rating.average as rating numeric

这个命令的意思是:

  • 我们基于 json 数据创建了一个名为 movies 的索引
  • 该索引作用于前缀为 movieid: 的所有 key
  • 使用中文分词
  • 索引有以下字段:
    • title: 类型为 text,权重为 3
    • directors: 类型为 tag
    • genres: 类型为 tag
    • summary: 类型为 text
    • rating: 类型为 numeric

索引是独立存在的,删除索引不会影响原始 key-value 数据。
在创建完索引之后,新增或修改的文档会同步构建索引,而对于创建索引之前已有的文档则会在后台异步构建索引。

使用全文检索
检索基础
  • 全文检索(任何字段包含爱情):
ft.search movies '爱情'
  • return 返回指定字段:
ft.search movies '爱情'
    return 2 title directors
  • highlight 高亮:
ft.search movies '爱情'
    return 2 title directors
    highlight fields 1 title tags <span> </span>
  • sortby 指定字段排序:
ft.search movies '爱情'
    return 3 title directors rating
    sortby rating desc
  • limit offset num 分页:
ft.search movies '爱情'
    return 3 title directors rating
    sortby rating desc
    limit 0 10
  • text 指定字段全文检索(电影标题含有爱情):
ft.search movies '@title:爱情'
    return 2 title directors
  • tag 字段匹配(导演是马丁·里特):
ft.search movies '@directors:{马丁·里特}'
    return 2 title directors
多条件组合
  • or(类型是剧情或者动作):
ft.search movies '@genres:{剧情|动作}'
    return 2 title directors
  • and(类型是剧情或者动作且评分大于等于8.0):
ft.search movies '(@genres:{剧情|动作})(@rating:[8.0,+inf])'
    return 3 title directors rating
前缀后缀、模糊搜索
ft.search movies '@title:爱*' return 1 title
ft.search movies '@title:*情' return 1 title
ft.search movies '@title:*命*' return 1 title
ft.search movies '@title:%人生%' return 1 title
自定义分词

与 elasticsearch 对比,redis 中的自定义分词这块支持比较有限,主要是:

  • 停用词:ft.create 命令中的可选参数 stopwords,将会影响分词
  • 同义词:ft.synupdate 命令
    • 构造同义词:ft.synupdate movies group1 爱情 凌虚
    • 那么 ft.search movies '爱情'
    • 等价于 ft.search movies '凌虚'
自定义打分

redis 只是提供了可选几种不同的打分算法:

  • tfidf(默认使用)
  • tfidf.docnorm
  • bm25(elasticsearch 使用的打分算法)
  • dismax
  • docscore
  • hamming
ft.search movies '爱情' return 0
    withscores
    scorer bm25

如果你想要其它的自定义打分,则只能通过编写扩展的方式实现了,扩展必须用 c 语言或者与 c 有接口的编程语言来写。

索引别名

为底层索引创建一个索引别名,在搜索时则使用索引别名,如果数据需要重建索引,那么只需要将索引别名指向新的底层索引即可,这种情况下搜索端不会受到任何影响。

  • 创建索引别名:
ft.aliasadd aliasname movies
  • 使用索引别名进行搜索:
ft.search aliasname '爱情' return 0
  • 更新索引别名:
ft.aliasupdate aliasname anotherindex
  • 删除索引别名:
ft.aliasdel aliasname
go 示例代码

使用的是 go-redis 库:

func cmdstringtoargs(rdscmd string) (result []interface{}) {
	re := regexp.mustcompile(`\s+`)
	slice := re.split(rdscmd, -1)
	for _, v := range slice {
		if v != "" && v != " " {
			result = append(result, v)
		}

	}
	return
}

func excutecommand(rdscmd string) {
	rds := getredisclient()
	ctx := context.background()

	args := cmdstringtoargs(rdscmd)

	res, err := rds.do(ctx, args...).result()
	if err != nil {
		panic(err)
	}
	fmt.println("result:", res)
}

测试代码:

func testexcutecommand(t *testing.t) {
	cases := []string{
		// 全文检索(任何字段包含爱情):
		`ft.search movies '爱情'`,

		// return 返回指定字段:
		`ft.search movies '爱情' return 2 title directors`,

		// ......
	}
	for _, v := range cases {
		excutecommand(v)
	}
}

总结

相较于 elasticsearch 这个全文搜索领域的榜一大哥,redis 支持的功能特性比较少(例如自定义分词和打分),但是基本的全文检索功能也都具备了。

笔者曾见过只有几十万数据却整了三台 elasticsearch 集群的情况,这实在是大炮打蚊子、严重浪费资源。如果数据体量比较小,而且检索的使用场景也比较简单,那么使用 redis 不仅足够,在性能方面还能有更大的优势。


参考资料:

  • https://redis.io/docs/latest/develop/interact/search-and-query/
  • https://redis.io/docs/latest/commands/ft.create/
  • https://redis.io/docs/latest/commands/ft.search/
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com