Elasticsearch分享

 阅读约 11 分钟

Elasticsearch是什么

Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库

但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单

所以,可以理解成Elasticsearch(以下简称Elastic)对Lucene进行了封装,让它变得好用

简化的描述:

  • 文档数据库
  • 分布式的实时分析搜索引擎

Elastic的主要运用领域

Elastic是一个非常著名的开源搜索和分析系统,目前被广泛应用于互联网多种领域中,尤其是以下三个领域特别突出。

  • 搜索领域,成为很多搜索系统的不二之选,比如全文检索;
  • Json文档数据库,相对于MongoDB(笔者未做比较,感兴趣可以比较一下),读写性能更佳,而且支持更丰富的地理位置查询以及数字、文本的混合查询等;
  • 时序数据分析处理,目前是日志处理、监控数据的存储、分析和可视化方面做得非常好,比如咱们公司里面使用的api访问日志的就是Elastic了,然后使用Kibana可视化日志,感觉还是很方便的

Elastic的一些基本概念的介绍

  • 节点(Node):物理概念,一个运行的Elasticearch实例,一般是一台机器上的一个进程,比如我们在自己的机器上面起了两个Elastic,就相当于是两个节点了。
  • 索引(Index),逻辑概念,包括配置信息settings和mapping和倒排正排数据文件,一个索引的数据文件可能会分布于一台机器,也有可能分布于多台机器。索引的另外一层意思是创建一条数据,索引一个文件的意思。
  • 类型(type):是属于index下一层级的概念,index下面的mapping,映射关系,字段集合等,可以理解成类似Mysql的表结构,然后index理解成库;
  • 分片(Shard):为了支持更大量的数据,索引一般会按某个维度分成多个部分,每个部分就是一个分片,分片被节点(Node)管理。一个节点(Node)一般会管理多个分片,这些分片可能是属于同一份索引,也有可能属于不同索引,但是为了可靠性和可用性,同一个索引的分片尽量会分布在不同节点(Node)上,否则一个节点崩了,整个index都没法使用了。分片有两种,主分片和副本分片。
  • 副本(Replica):同一个分片(Shard)的备份数据,一个分片可能会有0个或多个副本,这些副本中的数据保证强一致或最终一致
  • 文档(doc): 在Elastic中,一条数据称为一个文档,就是类似Mysql里面的一行数据了。

elastic的各个版本差异较大,这也是比较坑的地方,有些默认配置可能会不太一样,比如在早期的elastic版本中,一个index里面可以有多个type,但是多个type里面如果有相同的字段,则这些字段的类型必须一样,但是在elastic 6.x版本里面,比如,我们当前使用的是elastic 6.7版本,一个index里面只可以定义一个type;
所以如果没有特别说明,全文的elastic表示6.7版本,在这个版本下面,一个index默认有5个主分片和1个副本分片,然后统一主分片和副本分片不可以在同一个节点下面

  • number_of_shards, 主分片数量,索引一旦创建,不可以更改
  • number_of_replicas, 副本分片数量,默认为1, 这个可以更改

avatar

Elastic的写操作流程

Elastic整体上采用的是一主多副的架构
avatar

  1. 先根据_routing规则选择发给哪个Shard, 集群中找出出该Shard的Primary节点
    Index Request中可以设置使用哪个Filed的值作为路由参数,如果没有设置,则使用Mapping中的配置,如果mapping中也没有配置,则使用_id作为路由参数,然后通过_routing的Hash值选择出Shard
  2. 请求接着会发送给Primary Shard
  3. 在Primary Shard上执行成功后, 再从Primary Shard上将请求同时发送给多个Replica Shard
  4. 请求在多个Replica Shard上执行成功并返回给Primary Shard
  5. 写入请求执行成功,返回结果给客户端

需要说明的一点是,写入成功,是指写入到Lucene,并不包含refresh的操作完成,这个后面会涉及到在实际工作中出现的一个bug

针对上面的写入的方式,写入操作的延时就等于latency = Latency(Primary Write) + Max(Replicas Write),缺点是写入效率比较低,除了写入主分片,还需要写入所有副本分片,好处也很明显,通过副本分片可以拓展读性能,因为读的时候,仅仅需要任一副本分片就可以读取数据,除此之外,就是避免在某个分片所在的节点崩了之后,数据不会丢失。

Elastic的TransLog

是为了减少磁盘的IO来保证读写性能,同时也为了保证对于那些已经写入内存但是还没写进磁盘的数据在发生宕机的时候,数据不会丢失。

avatar

针对上图,需要说明的几点是:

  • refresh表示将Lucene内存中的对象转化为完整的Segment之后,才可以被搜索到,这个时间一般是1秒,所以我们一般写入Elastic的数据,1秒之后才可以被搜索到,Elasticsearch在搜索方面是NRT(Near Real Time)近实时的系统
  • 但是Elastic还有作为NoSQL的一面,如果我们按照GetById方法查询,这个时候就变成了RT系统,这是因为这种查询方式是直接查询TransLog(上面那个),这个是可以直接被查到的,这个也是我后面才看到了,所以当时的那个问题,我并没有采用这种方式
  • 每隔一段时间之后,Lucene会将内存中的Segment flush到磁盘上,此时数据被持久化了,历史的Translog就会被清理掉。

Elastic的读操作

Elasticsearch中每个Shard都会有多个Replica,主要是为了保证数据可靠性,除此之外,还可以增加读能力,因为写的时候虽然要写大部分Replica Shard,但是查询的时候只需要查询Primary和Replica中的任何一个就可以了。
avatat

然后针对上面说的GetById的查询方式和普通的Search,下面有个示意图
avatar

总体而言读的流程就是

  • 在所有分片中根据筛选条件找出docid(主分片和副本分片只需要一个),query
  • 然后根据docid取出完整的文档,fetch
  • 搜索里面有一种算分逻辑是根据TF(Term Frequency)和IDF(Inverse Document Frequency)计算基础分
  • 上面的三个步骤都是在一个shard里面进行的,最后一步将所有内容汇总,根据评分排名,取出部分数据

PS:从上面可以看到Elastic不太适合深度分页,深度分页会比较慢,如果需要获取所以数据,可以使用scan,里面是使用的scroll

TF:目标词在字段中出现的次数,加分
IDF:目标词在所有文档中出现的次数,减分

Elastic在使用中的出现的问题和需要注意的地方

索引index按照日期分组

  • 代码实例
class Record(Document):
    class Index:
        name = "purifier-record-*"
        
    def save(self, **kwargs):
        kwargs['index'] = self.date_created.strftime('purifier-record-%Y%m%d')
        return super(Record, self).save(**kwargs)        
  • 按照日期分index的原因:index一旦建立,主分片的数量是不可以改变的,相当于是无法水平拓展,如果后期数据量比较大,有可能会导致一台机器空间不足,(PS: 如果你要说有钱,加机器硬件,当我没说,但是一个机器的性能毕竟有限,特别是面对海量日志之类的东西的时候),所有我们对index进行切分了,按照月份也可以:
  • 导致的问题:我们在使用Elastic搜索的时候,没有指定index,所以最后相当于是全量搜索es,速度很慢,经常接口超时,lambda是15秒,后来我们查询的时候加上了时间作为筛选项,快了很多,基本都是100ms以下吧

Elastic默认的返回数量是10条,

这个需要注意,否则有可能会遇到明明数据库存在,都是就是查找不出来的情况,坑过

Elastic作为搜索引擎是近乎实时的

这个需要主要,之前我遇到过刚写入数据,然后立马更新缓存或者执行异步任务的时候,缓存更新的是之前的数据,异步任务里面没有搜到刚才那条数据,可能是我搜索方式不对,之后可以使用GetbyId的方式,我试试看

docid作为其他type的字段时候

假如说在Record里面的_id,保存在Task里面为record_id,然后我们使用record_id查询进行filter过滤的的时候,要使用keyword查询,就是不分析,因为docid默认的里面有大写字母,然后进行搜索的时候,如果没有配置分词器的话,使用默认的standard分词器,会将大写全部变为小写,所以就查不到了。

最后放上学习资料
Elastic权威指南,版本比较老,看看一些思想,有些操作在新版本需要修改或者废弃了

Elasticsearch技术探讨,阿里的技术人写的一些分享

最后极客时间,前两天新上了Elasticsearch视频课,我先试试毒,买课如山倒,看课如抽丝

阅读 304更新于 6月28日
推荐阅读
玖色堇
用户专栏

python爱好者,web开发者,伪文艺青年

2 人关注
14 篇文章
专栏主页
目录