搜索引擎(mmap系统调用) | 青训营笔记

218 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第6篇笔记

了解了倒排索引之后,如何来设计一个数据结构和算法来实现倒排索引表就成了搜索引擎技术的关键。

搜索引擎搜索的是海量的数据,一般的中型电商项目都是几十上百G的数据了,因此这个数据结构应该存储在本地磁盘而不是内存中,基于以上的考虑,为了快速搜索,要么自己实现cache来缓存热数据,要么考虑使用操作系统的底层数据MMAP,自己实现cache不太可能吧,,也不见得比操作系统做的好,所以可以使用mmap。

mmap, memory map,地址的映射,是一种内存映射文件的方法。 在linux系统中,文件的独写操作通常通过read()和write()这两个系统调用来实现,这个过程会产生频繁的内存拷贝,操作系统首先会将数据从磁盘拷贝到内核缓冲区(页缓存),然后再把数据拷贝至用户进程中。

mmap只需要一次拷贝,即操作系统读取磁盘文件到页缓存。内存映射的含义就在于,把内核中特定部分的内存空间映射到用户级程序的内存空间中。也就是用户空间和内核空间共享一块相同的内存。只需要将磁盘上的数据拷贝至那块共享内存中,用户进程就可以直接获取到信息,这样相比,mmap会减少一次拷贝数据,这对于海量数据来说,带来的性能提升是巨大的。

使用内存访问来取代read()和write()系统调用能够简化一些应用程序的逻辑。能够比传统的I/O系统调用执行文件I/O这种做法提供更好的性能。

  • 正常的read()或write()需要两次传输:一次是在文件和内核高速缓冲区之间,另一次是在高速缓冲区和用户空间缓冲区之间。使用mmap()就不需要第二次传输了。对于输入来讲,一旦内核将相应的文件块映射进内存之后,用户进程就能够使用这些数据了;对于输出来讲,用户进程仅仅需要修改内核中的内容,然后可以依靠内核内存管理器来自动更新底层的文件。
  • 除了节省内核空间和用户空间之间的一次传输之外,mmap()还能够通过减少所需使用的内存来提升性能。当使用read()或write()时,数据将被保存在两个缓冲区中:一个位于用户空间,另个一位于内核空间。当使用mmap()时,内核空间和用户空间会共享同一个缓冲区。此外,如果多个进程正在同一个文件上执行I/O,那么它们通过使用mmap()就能够共享同一个内核缓冲区,从而又能够节省内存的消耗。

除了节省内核空间和用户空间之间的一次传输之外,mmap()还能够通过减少所需使用的内存来提升性能。当使用read()或write()时,数据将被保存在两个缓冲区中:一个位于用户空间,另个一位于内核空间。当使用mmap()时,内核空间和用户空间会共享同一个缓冲区。此外,如果多个进程正在同一个文件上执行I/O,那么它们通过使用mmap()就能够共享同一个内核缓冲区,从而又能够节省内存的消耗。

在Go语言中,对应的MMAP调用是:(需要引入Syscall包)

func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error)
//fd: 文件描述符
//offset:偏移量
//length: 需要映射的长度
//prot: 期望的内存保护标志[只读|只写|读写]
//映射方式:是否同步到文件,还是只是副本修改等

因为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,31,22,32,33

那么第1列就是保存关键字和偏移量,这样倒排索引表就被拆分为两个数据结构了,现在的关键是第1列使用什么数据结构可以保证在查询的时候迅速找到对应的关键字,从而找到偏移量得到第二列的具体数据。

分别是:顺序表哈希表查找树前缀树,下一篇我们分别看看他们的能力。

目前是直接将倒排索引表存储在内存中的某个数据结构中,接下来试着value只存储key在倒排文件中的偏移量,这样也可以减少内存的使用。