简单理解
存储到es时,es首先会对数据进行分词,比如一句话分成多个词语,每个词对应一个id;在查找数据时,会先对数据进行分词,再对每一个词进行查找,先找到对应的id,然后由id找出对应的文档库,在对每一个词进行查找时,利用了树结构加快了效率。
es基本概念
- Elasticsearch 是一个实时的分布式存储、搜索和分析的引擎,与数据库相比,es的强大之处在于可以更快更精确的进行模糊匹配和相关性查询,数据库虽然也能通过 name like %模糊查询% 的方式进行查询,但是这种方式不走索引,如果数据量很大,查询就会很费时。
- Lucene是一个jar包,它是实际保存数据并进行索引和搜索的地方,里面有一些封装好的倒排索引,ES 位于它之上,可以并行运行数千个 Lucene 实例。
- ES的最高级别单元是集群,集群是ES节点和索引的集合。
- 节点Node是ES的实例,每个ES进程是一个节点,节点有不同的类型,最值得关注的两个节点类型是:数据节点和主候选节点。数据节点运行所有数据操作,即存储、索引和检索数据。主候选节点具有投票master的权限,用于管理集群和索引。
- 索引是对数据的高级抽象,本身不保存数据,我们对数据执行的任何操作都会对索引产生影响。
- 分片shard是Lucene的实例,一个分片包含多个文档,分片是数据存储、索引和搜索的实际对象。它只属于一个节点和索引。
为什么要用es、任务记录为什么要迁移到es中?
- 首先是查询速度:对于上万条,或上十万条数据,数据库的查询速度是比较慢的,而es是基于倒排索引实现的,当我们想搜索一个词语时,es就会返回这个出现了这个词语的文档列表,这样就不用花多余的时间在其他文档上了,因此检索速度得到了很多的提升。
- 在查询任务记录时,查询条件是任意组合的,如果使用mysql,就得创建很多组合的B+树索引。
- 分词功能:数据库不支持分词功能,而es可以支持中文分词器,它将用户输入的搜索词语进行分词,然后分别找到包含各个词的文档列表,查询数据非常方便。
- 任务记录数据量的增长是非常快的,es相比mysql可以更轻松的进行节点的扩展,因为es是一个分布式的存储和搜索的引擎。
中文分词器(ik)
- ik分词器有两种设置:ik_smart 和 ik_max_word,其中ik_smark是最大粒度的切分,更符合我们的使用;ik_max_word是全切分,会最细粒度的进行切分,穷尽各种可能的组合。比如“我是中国人”,ik_smart 会将它拆分为“我”,“是”,“中国人”。而ik_max_word 会将它拆分的更细,比如会拆出“中国”,“国人”。
- 在使用时,我们需要在 mapping 字段的 properties 中,设置 analyzer 为我们想要的设置,比如:
"properties": {
"name": {
"analyzer": "ik_max_word",
"type": "text"
}
}
es性能比数据库好,为什么还要数据库呢?
- 虽然es性能很好,但是他并不是一个数据库,并不能保证实时性,因为es的写入机制是新增的文档会被收集到Indexing Buffer,然后写入到文件系统缓存中,到了文件系统缓存中才可以像其他的文件一样被索引到。然而默认情况文档从Indexing Buffer到文件系统缓存(即Refresh操作)是每秒自动刷新的,所以这就是我们说ES是近实时搜索而非实时的原因:文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。所以对于实时性比较高的业务比如订单数据,直接走数据库查询会更好。
- es不支持事务,mysql支持事务。
- 执行一些复杂的查询用MySQL这样的关系型数据库很方便,用es会很比较复杂。
倒排索引
- 正向索引:根据“完整的条件”查找一条记录叫做正向索引;我们一本书的章节目录就是正向索引,通过章节名称就能找到对应的页码。
- 倒排索引:通过倒排索引,可以根据某个词快速获取包含这个词的文档列表。
倒排索引的具体实现
-
当插入一条记录时,先用分词器将文本分解为词语,接着把每一个词语以及它对应的文档id存下来,词语叫做term,它出现过的文档id集合叫做posting list。词语的集合叫做term dictionary,一个term dictionary中有很多单词。单词存放在内存中,但是内存资源很宝贵,所以使用的是字典树这个数据结构 只是在内存中存储了单词的前缀。通过这个字典树可以找到单词所在的块,也就是单词的大致位置,然后再在块里进行二分查找,找到对应的单词,再根据单词找到对应的文档列表。
-
这里的字典树是一棵 trie 树,它可以充分的利用单词前缀,来压缩存储空间。它存储在内存中,查询速度快,查询的时间复杂度为 O( len(所查字符串))。
-
es对文档列表也做了优化,所采用的优化技术是 FOR 编码技术,它分为三步:
- 首先是采用了增量编码,增量编码指的是只记录元素与元素之间的增量。因为document中id的范围是0到2的32次方减一,所以如果不进行处理,每个元素都会占用4个字节,如果采用增量编码的方式去记录增量的话,那么记录的元素就会大大减小,自然也就减少了所占用的字节数了。
- 然后将元素分割成块,每个块中有256个文档id。
- 最后按需分配空间,比如某个块中最大的元素是227,小于256(2的8次方),那么就需要8位,于是给这个块中的元素都分配8位的空间。如果第二个块中最大的元素只有30,小于32(2的5次方),那么就需要5位,于是给这个块中的元素都分配5位的空间。
es中各字段的概念
- index 相当于数据库
- type 相当于数据库的 table
- document 相当于数据库的一行记录
- field 相当于数据库的列 column
- mapping 相当于数据库的表结构(模式 schema )
- DSL 相当于数据库的 SQL,它是我们读取es数据的API
- 一个es集群有多个es节点,节点就是运行着es进程的机器。这这些节点中有一个 master node 主节点,它负责维护索引元数据、切换主分片和副本分片等工作。如果主节点挂了,会选举出一个新的主节点。
- 一个 index 的数据分发到不同的节点上进行存储,这个操作过程就叫做分片。比如现在我集群里边有4个节点,我现在有一个Index,想将这个Index在4个节点上存储,那我们可以设置为4个分片。这4个分片的数据合起来就是Index的数据。
为什么要分片?
- 如果一个Index的数据量太大,只有一个分片,那只会在一个节点上存储,随着数据量的增长,一个节点很难把一个Index存储下来。
- 多个分片在写入或查询的时候就可以并行操作。从而提高吞吐量。
主分片和副本分片的区别:
- 分片有两种类型:primary 和 replica(复制),它们基本上是完全相同的,搜索过程并行运行在所有分片上,不同的是拥有相同数据的所有分片中,只有一个属于primary,而这个是唯一可以接受索引请求的分片。如果这个主分片挂了,副本分片将接管成为主分片,然后ES创建一个新的副本并复制数据。
为什么要分为主分片和副本分片?(进行分片后,其中一个节点挂了导致丢数据,怎么办?)
- 分片分为主分片和副本分片,这么做的目的是实现高可用。数据写入的时候是写到主分片,副本分片会复制主分片的数据,读取的时候主分片和副本分片都可以读。如果某个节点挂了,Master Node就会把对应的副本分片提拔为主分片,这样即便节点挂了,数据也不会丢。(Index需要分为多少个主分片和副本分片都是可以通过配置设置的)
es相关度排序的原理
- es中有一个相关性打分机制,具体的算法是通过 逆向文档频率 乘以 词的权重 乘以 单词频率权重 来得到。 idf x boost x tfNorm
- IDF意思是逆向文档频率,指的是一个文档集合中包含某个单词的文档数量,表示的是单词在文档集合中的重要程度,越稀有则权重越高,随着单词的增加而降低。它通过文档总数 除以 包含单词的文档数 来得到。(es对它进行改造)
- boost意思是查询时,指定的词的权重,不指定时权重为1。
- tfNorm意思是单词频率权重。
- TF意思是词频,指的是一个文档中出现某个单词的频率,表示的是单词在文本中的重要程度,随着单词的增加而增加。它通过词在文档中出现的次数 除以 文档中所有词的次数和 来得到。
了解内容
- 另外es还采用了bitmap来求交并集,因为在搜索数据时,通常会有多个查询条件,将各个查询条件获得的结果在内存中做交并集,(即使没有多条件查询,Lucene也需要频繁求并集,因为Lucene是分片存储的)。那假设查询出的结果,也就是一个数组,这个数组包含了很多个文档id,那肯定会占用很多内存空间,所以就采用了Bitmap位数组来表示数组中的数据,数组的下标代表文档id,值是0或者1 ,那么存储一个元素只需要八分之一个字节,另外 0 1 天然就适合做交并位运算,求交集,就与一下,求并集就或一下。位运算也非常快。当然也不是所有的数据都采用bitmap来存储,对于数据量很少的数据,只用Integer数组会更加节省内存空间,有一个临界值,当数组的大小小于临界值时,采用数组,否则就用bitmap。
主节点选举机制(有缺陷的)
- ES中只有主候选节点才具有投票权,在集群启动或主节点离开集群时,所有主候选节点就会投票选举新的主节点。为了防止出现两个节点票数相同的情况,需要具有 2n+1 和主候选节点。
ES查询
es根据索引进行删除操作。
es有两种查询方式:
- 根据id查询document。(项目用的是这种)
- 根据搜索词(query)去查询匹配的document。