生活中的数据



搜索引擎是对数据的检索,所以我们先从生活中的数据说起。我们生活中的数据总体分为两种:



  • 结构化数据
  • 非结构化数据



结构化数据



非结构化数据:



说明:



根据两种数据分类,搜索也相应的分为两种:



  • 结构化数据搜索
  • 非结构化数据搜索



对于结构化数据,因为它们具有特定的结构,所以我们一般都是可以通过关系型数据库(MySQL,Oracle 等)的二维表(Table)的方式存储和搜索,也可以建立索引。



对于非结构化数据,也即对全文数据的搜索主要有两种方法:



  • 顺序扫描
  • 全文检索



顺序扫描



例如给你一张报纸,让你找到该报纸中“平安”的文字在哪些地方出现过。你肯定需要从头到尾把报纸阅读扫描一遍然后标记出关键字在哪些版块出现过以及它的出现位置。



这种方式无疑是最耗时的最低效的,如果报纸排版字体小,而且版块较多甚至有多份报纸,等你扫描完你的眼睛也差不多了。



全文搜索



这种方式就构成了全文检索的基本思路。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之为索引。



这种方式的主要工作量在前期索引的创建,但是对于后期搜索却是快速高效的。



先说说 Lucene



通过对生活中数据的类型作了一个简短了解之后,我们知道关系型数据库的 SQL 检索是处理不了这种非结构化数据的。



全文搜索 ,而目前市场上开放源代码的最好 全文检索引擎工具包



但是 Lucene 只是一个工具包,它不是一个完整的全文检索引擎。Lucene 的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。



目前以 Lucene 为础建立的开源可用全文搜索引擎主要是 Solr 和 Elasticsearch。



Solr 和 Elasticsearch 都是比较成熟的全文搜索引擎,能完成的功能和性能也基本一样。



但是 ES 本身就具有分布式的特性和易安装使用的特点,而 Solr 的分布式需要借助第三方来实现,例如通过使用 ZooKeeper 来达到分布式协调管理。



不管是 Solr 还是 Elasticsearch 底层都是依赖于 Lucene,而 Lucene 能实现全文搜索主要是因为它实现了倒排索引的查询结构。



为了创建倒排索引,我们通过分词器将每个文档的内容域拆分成单独的词(我们称它为词条或 Term),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。



这种结构由文档中所有不重复词的列表构成,对于其中每个词都有一个文档列表与之关联。



这种由属性值来确定记录的位置的结构就是倒排索引。带有倒排索引的文件我们称为倒排文件。



其中主要有如下几个核心术语需要理解:



  • 词条(Term):索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
  • 词典(Term Dictionary):或字典,是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
  • 倒排表(Post list):一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。



每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。



  • 倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。



从上图我们可以了解到倒排索引主要由两个部分组成:



  • 词典
  • 倒排文件



词典和倒排表是 Lucene 中很重要的两种数据结构,是实现快速检索的重要基石。词典和倒排文件是分两部分存储的,词典在内存中而倒排文件存储在磁盘上。



ES 核心概念



一些基础知识的铺垫之后我们正式进入今天的主角 Elasticsearch 的介绍。



ES 是使用 Java 编写的一种开源搜索引擎,它在内部使用 Lucene 做索引与搜索,通过对 Lucene 的封装,隐藏了 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。



然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 



它可以被下面这样准确的形容:



  • 一个分布式的实时文档存储,每个字段可以被索引与搜索。
  • 一个分布式实时分析搜索引擎。
  • 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。



官网对 Elasticsearch 的介绍是 Elasticsearch 是一个分布式、可扩展、近实时的搜索与数据分析引擎。



我们通过一些核心概念来看下 Elasticsearch 是如何做到分布式,可扩展和近实时搜索的。



小结:



  • 将数据分片是为了提高可处理数据的容量和易于进行水平扩展,为分片做副本是为了提高集群的稳定性和提高并发量。
  • 副本是乘法,越多消耗越大,但也越保险。分片是除法,分片越多,单分片数据就越少也越分散。
  • 副本越多,集群的可用性就越高,但是由于每个分片都相当于一个 Lucene 的索引文件,会占用一定的文件句柄、内存及 CPU。



并且分片间的数据同步也会占用一定的网络带宽,所以索引的分片数和副本数也不是越多越好。



为什么说 ES 是近实时搜索引擎而文档的 CRUD (创建-读取-更新-删除) 操作是实时的?



延迟写策略。



索引文档以段的形式存储在磁盘上,何为段?索引文件被拆分为多个子文件,则每个子文件叫作段,每一个段本身都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改。



在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。



段被写入到磁盘后会生成一个提交点,提交点是一个用来记录所有提交后段信息的文件。



一个段一旦拥有了提交点,就说明这个段只有读的权限,失去了写的权限。 相反,当段在内存中时,就只有写的权限,而不具备读数据的权限,意味着不能被检索。



如果索引有更新,就需要重新全量创建一个索引来替换原来的索引。这种方式在数据量很大时效率很低,并且由于创建一次索引的成本很高,所以对数据的更新不能过于频繁,也就不能保证时效性。

索引文件分段存储并且不可修改,那么新增、更新和删除如何处理呢?



  • 新增,新增很好处理,由于数据是新的,所以只需要对当前文档新增一个段就可以了。
  • 删除,由于不可修改,所以对于删除操作,不会把文档从旧的段中移除而是通过新增一个 .del 文件,文件中会列出这些被删除文档的段信息。



这个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。



  • 更新,不能修改旧的段来进行反映文档的更新,其实更新相当于是删除和新增这两个动作组成。会将旧的文档在 .del 文件中标记删除,然后文档的新版本被索引到一个新的段中。



可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就会被移除。

段被设定为不可修改具有一定的优势也有一定的缺点,优势主要表现在:



  • 不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
  • 一旦索引被读入内核的文件系统缓存,便会留在哪里。由于其不变性,只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。也不会有数据一致性的问题。这提供了很大的性能提升。
  • 其它缓存(像 Filter 缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
  • 写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和需要被缓存到内存的索引的使用量。



以及 Elasticsearch 是怎样保证更新被持久化在断电时也不丢失数据?



数据被分配到特定的分片和副本上之后,最终是存储到磁盘上的,这样在断电的时候就不会丢失数据。



还有为什么删除文档不会立刻释放空间?



当对旧数据进行删除时,旧数据不会马上被删除,而是在 .del 文件中被标记为删除。而旧数据只能等到段更新时才能被移除,