本文已参与「新人创作礼」活动,一起开启掘金创作之路。
想要建一个自己的博客或者知识wiki都离不开内容的全文搜索功能,但是就自己那写文章和文档就让我上ES好像又太重了。
下面我要为大家介绍一个轻便的全文搜索框架wukong
先上代码
这个全文搜索框架哦超好用,5行代码就能搞定 中文分词、索引和搜索
1. searcher := engine.Engine{} //初始化
2. searcher.Init(types.EngineInitOptions{SegmenterDictionaries: config.App.BasePath + "dictionary.txt"}) //分词在这里
3. searcher.IndexDocument(uint64(这里是文章的ID), types.DocumentIndexData{Content:"文本文本,把你的内容都放这里吧"}, false) //导入文本和对应的
4. searcher.FlushIndex() //刷新索引
5. resp := searcher.Search(types.SearchRequest{Text: keywords}) //通过关键字搜索吧
是不是超级简单
wukong简介
wukong,是一款golang实现的高性能、支持中文分词的全文搜索引擎。
第一次知道wukong,其实是在今年做个人网站的时候遇到需要做全文搜索,然后了解到了wukong的作者陈辉。 他是作为第一个演讲嘉宾,在google 2016大会上分享了“Go与人工智能”。在这个presentation中,陈辉详细讲解了wukong搜索引擎以及其他几个关联的开源项目,比如:sego等。
在golang世界中,做full text search的可不止wukong一个。另外一个比较知名的是bleve
但默认情况下,bleve并不支持中文分词和搜索,需要结合中文分词插件才能支持,比如:gojieba。
wukong基本上是陈辉一个人打造的项目,在陈辉在阿里任职期间,他将其用于阿里内部的一些项目中,但总体来说,wukong的应用还是很小众的,相关资料也不多,基本都集中在其github站点上。
关于wukong源码的分析,倒是在国外站点上发现一篇:《Code reading: wukong full-text search engine》。
不过我个人觉得它最大的特点恰恰是不像ElasticSearch那样庞大和功能完备,而是可以以一个Library的形式快速集成到你的应用或服务中去。小而美,简直就是优秀!
完整例子
给一个能跑的例子吧,
package main
import (
"fmt"
"github.com/huichen/wukong/engine"
"github.com/huichen/wukong/types"
)
var (
docId uint64
text1 = "中国内地和澳门地区约有5.15亿人在2月10日之前,通过中央电视台收看了北京冬奥会。估计有5亿人收看了冬奥会的开幕式。在香港,冬奥会由TVB独家转播,近300万人收看了开幕式"
text2 = "在欧洲,探索频道转播了这些赛事。比赛开始仅4天,流媒体观众人数就超过了2018年平昌冬奥会观众的总人数。澳大利亚人也对冬奥会表现出了兴趣,官方的第七频道网16日吸引了1170万观众。"
)
func main() {
searcher := engine.Engine{} //初始化
searcher.Init(types.EngineInitOptions{
SegmenterDictionaries: "../../webapp/dist/dictionary.txt", //分词字典,这个可以去作者的git仓库里面找到
})
docId++
searcher.IndexDocument(uint64(docId), types.DocumentIndexData{Content: text1}, false)
docId++
searcher.IndexDocument(uint64(docId), types.DocumentIndexData{Content: text2}, false)
searcher.FlushIndex()
fmt.Printf("%#v\n", searcher.Search(types.SearchRequest{Text: "欧洲"}))
fmt.Printf("%#v\n", searcher.Search(types.SearchRequest{Text: "中国"}))
searcher.Close()
}
执行结果:
2022/02/18 19:03:45 载入sego词典 ../../webapp/dist/dictionary.txt
2022/02/18 19:03:46 sego词典载入完毕
types.SearchResponse{Tokens:[]string{"欧洲"}, Docs:[]types.ScoredDocument{types.ScoredDocument{DocId:0x2, Scores:[]float32{1}, TokenSnippetLocations:[]int(nil), TokenLocations:[][]int(nil)}}, Timeout:false, NumDocs:1}
types.SearchResponse{Tokens:[]string{"中国"}, Docs:[]types.ScoredDocument{types.ScoredDocument{DocId:0x1, Scores:[]float32{1}, TokenSnippetLocations:[]int(nil), TokenLocations:[][]int(nil)}}, Timeout:false, NumDocs:1}
持久化索引和启动恢复
wukong引擎建立的文档索引都是存放在内存中的,程序退出后,这些数据也就随之消失了。 每次都创建索引显然是不合理的,好在wukong支持将已建立的索引持久化到磁盘文件中,并在程序重启时从文件中间索引数据恢复出来, 并在后续的关键词搜索时使用。wukong底层支持两种持久化引擎,一个是boltdb,另外一个是cznic/kv。默认采用boltdb。
// index_create.go
... ...
func main() {
searcher.Init(types.EngineInitOptions{
IndexerInitOptions: &types.IndexerInitOptions{
IndexType: types.DocIdsIndex,
},
UsePersistentStorage: true,
PersistentStorageFolder: "./index",
SegmenterDictionaries: "./dict/dictionary.txt",
StopTokenFile: "./dict/stop_tokens.txt",
})
defer searcher.Close()
os.MkdirAll("./index", 0777)
docId++
searcher.IndexDocument(docId, types.DocumentIndexData{Content: text1}, false)
docId++
searcher.IndexDocument(docId, types.DocumentIndexData{Content: text2}, false)
docId++
searcher.IndexDocument(docId, types.DocumentIndexData{Content: text3}, false)
searcher.FlushIndex()
log.Println("Created index number:", searcher.NumDocumentsIndexed())
}
分布式索引和搜索
当文档数量较多无法在一台机器内存中索引时,可以将文档按照文本内容的hash值裂分(sharding),不同块交由不同服务器索引。 在查找时同一请求分发到所有裂分服务器上,然后将所有服务器返回的结果归并重排序作为最终搜索结果输出。
为了保证裂分的均匀性,建议使用Go语言实现的Murmur3 hash函数
按照上面的原理很容易用悟空引擎实现分布式搜索(每个裂分服务器运行一个悟空引擎),但这样的分布式系统多数是高度定制的,比如任务的调度依赖于分布式环境,有时需要添加额外层的服务器来实现均衡负载。
小结
wukong是一个轻量级的全文搜索引擎,简单易用,虽然没有ES强大,不过小而美的它很适合使用到我们的个人博客或者是wik搜索中。 文章主要介绍了wukong的使用,并在最后探讨了wukong在大规模搜索方案中的分布式索引方案。虽然wukong很优秀,但是它的缺点也是很明显的 由于wukong引擎基本上是作者一个人的项目,社区参与度不高,资料很少;查询功能简单,仅支持关键词的AND查询;wukong的索引建立和搜索精确度一定程度上 取决于分词引擎的分词精确性。
but 如果你只是要一个简单的搜素功能,wukong绝对超你预期!