这一篇,咱们来讲说搜索引擎最核心的技术,倒排索引技术
,倒排索引可能须要分红几篇文章才说得完,咱们先会说说倒排索引的技术原理,而后会讲讲怎么用一些数据结构和算法来实现一个倒排索引,而后会说一个索引器
怎么经过文档
来生成一个倒排索引。算法
什么是倒排索引呢?索引咱们都知道,就是为了能更快的找到文档的数据结构,好比给文档编个号,那么经过这个号就能够很快的找到某一篇文档,而倒排索引不是根据文档编号,而是经过文档中的某些个词而找到文档的索引结构。缓存
倒排索引技术简单,高效,简直是为搜索引擎这种东西量身定作的,就是靠这个技术,实现一个搜索引擎才成为可能,咱们也才能在海量的文章中经过一个关键词找到咱们想要的内容。微信
咱们看个例子,有下面的几个文档:数据结构
文档编号 | 文档内容 |
---|---|
1 | 这是一个Go语言实现的搜索引擎 |
2 | PHP是世界上最好的语言 |
3 | Linux是C语言和汇编语言实现的 |
4 | 谷歌是一个世界上最好的搜索引擎公司 |
直观的看,咱们经过编号1,2,3,4
能够很快的找到文档,可是咱们须要经过关键词找文档,那么把上面那个表格稍微变化一下,就是倒排索引了数据结构和算法
倒排索引【只列出了部分关键词】函数
关键词 | 文档编号 |
---|---|
Go | 1 |
语言 | 1,2,3 |
实现 | 1,3 |
搜索引擎 | 1,4 |
PHP | 2 |
世界 | 2,4 |
最好 | 2,4 |
汇编 | 3 |
公司 | 4 |
这样就很是好理解了吧,实际上倒排索引就是把文档的内容切词之后从新生成了一个表格,经过这个表格,咱们能够很快的找到每一个关键词对应的文档,好了,没有了,到这里,就是倒排索引的核心原理,也是搜索引擎最基础的基石,不论是谷歌仍是某度,最核心的东西就是这两个表格了,呵呵,没这两表格,啥都干不了。性能
看上去很简单吧,好吧,咱们如今来模拟搜索引擎进行一次搜索,好比,咱们键入关键词搜索引擎
1.咱们在表格2中查到搜索引擎这个词出如今第4行
2.找到第4行的第2列,把文档编号找出来,是1和4
3.去第一个表格经过文档编号把每一个文档的实际内容找出来
4.将1和4的结果显示出来
5.搜索完成ui
上面就是搜索引擎的最基础的技术了,若是来设计一个数据结构和算法来实现表2就成了搜索引擎技术的关键。this
在实现数据结构和算法以前,咱们须要知道搜索引擎搜索的是海量的数据,通常的中型电商的数据都是几十上百G的数据了,因此这个数据结构应该是存储在本地磁盘的而不是在内存中的,基于以上的考虑,为了快速搜索,要么本身实现cache来缓存热数据,要么考虑使用操做系统的底层技术MMAP
,鉴于我本身实现的cache不见得(基本上是不太可能)比操做系统作得好,因此我使用的是MMAP
。搜索引擎
mmap是将一个文件或者其它对象映射进内存。文件被映射到多个页上,若是文件的大小不是全部页的大小之和,最后一个页不被使用的空间将会清零。实现这样的映射关系后,进程就能够采用指针的方式读写操做这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操做而没必要再调用read,write等系统调用函数。
mmap最大的一个好处是操做系统会本身将磁盘上的文件映射到内存,当内存足够的时候,操做文件就像操做内存同样快,而当内存不足的时候,操做系统又会本身将一些页从内存中去掉,实现了一个相似缓存的东西。特别适合于对于巨大文件的读操做,而咱们的倒排索引文件就是这种巨大的文件,并且基本上写入一次之后就不太修改了,每次查询都读操做,因此使用mmap是一个比较好的选择。
mmap是一个系统调用,不一样的操做系统实现有所不一样,Linux下对应的C的调用方法是下面这个,具体的参数含义你们能够man一下:
头文件 <sys/mman.h>
函数原型
void mmap(void start,size_t length,int prot,int flags,int fd,off_t offset);
一个巨大的文件mmap以后,文件读写操做的性能由系统内存决定,系统可用内存越大,那么读写文件的性能越好,由于操做系统的内存足够,系统会将更多的文件载入到内存,提升系统吞吐量。
在Go语言中,对应的MMAP调用是:(须要引入Syscall
包)
func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error)
参数分别是:文件描述符,偏移量,须要映射的长度,指望的内存保护标志【是只读仍是只写仍是读写】,映射方式【是否同步到文件,仍是只是副本修改等】。
由于mmap是基础实现,不少地方都须要使用,因此单独实现了一个mmap的类,在utils.mmap中,提供一些基础的方法:
func NewMmap(file_name string, mode int) (*Mmap, error) 新建一个mmap
func (this *Mmap) ReadInt64(start int64) int64 //从指定位置读取一个int64的值
func (this *Mmap) WriteInt64(start, value int64) error //在指定位置写入一个int64的值
func (this *Mmap) ReadDocIdsArry(start, len uint64) []DocIdNode //从指定位置读取一个docid的链
......
巨大文件的读写技术方案解决了,实际上主要就是解决了表2的第二列的问题,在一个拥有巨大文档数的数据中,表2的第二列占用了绝大多数磁盘空间,咱们会将表2分红两个数据结构来存储,第二列就是一个连续的存储文件,叫倒排文件
,在上述例子中,咱们会将第二列存成:
1 | 1,2,3 | 1,3 | 1,4 | 2 | 2,4 | 2,4 | 3 | 4 |
---|
而第一列咱们将保存关键字和偏移量。这样,表2就被咱们拆分红两个数据结构了,如今的关键是第一列使用什么数据结构能够保证在查询的时候迅速找到对应的关键字,从而找到偏移量获得第二列的具体数据。
好了,如今有几位选手要上场,他们均可以实现第一列的结构,他们分别是:顺序表
,哈希表
,查找树
,前缀树
,下一篇咱们分别看看他们的能力。
文章的更新频率会在一周3到5篇左右吧,欢迎你们扫描一下下面的微信公众号订阅,首先会在这里发出来:)