1.2ELK基础知识
1.2.1概述
ELasticsearch是一套分布式系统,可以处理大量数据。可以作为一个分布式数据存储系统。
文档数据:es可以存储和操作json文档类型的数据,这也是es的核心数据结构。
存储系统:es可以对json文档类型的数据进行存储,查询,创建,更新,删除等。
1.2.2它隐藏了复杂的分布式机制。
(一)分片机制(我们将document插入到es集群中不需要关心其怎么分片,数据存储到哪个节点.
(二)cluster discovery(集群发现机制)
(三)shard负载均衡(es会自动均匀分配,以保证每一个节点读写请求均衡)
(四)shard副本,请求路由,集群扩容,shard重分配
1.3.1手动id和自动id
(一)手动Id
适合场景:从系统系统导入到es,就是采用系统中已有数据的唯一标识作为文档的id。
put /index/type/1 { "username": 'lai' }
(二)自动id
适合场景:es管理数据。自动生成长度20位的,url安全,BASE64编码,采用GUID(在分布式系统下不同节点同一时间产生的id不会冲突)
post /index/type { "username": 'lai }
1.3.2 元数据
1.3.2.1_source元数据
在创建document时,放在request body中的json串。get请求将返回所有内容。
全部返回:get /index/type/1 #返回索引index类型type中文档id为1的元数据
部分返回:get/index/type/1?_source=field1,field2 #返回索引index类型type中文档id为1的元数据filed1.filed2
1.3.2.2 document元数据
(一)document全量替换
(1)语法与创建文档一样,若ducoment_id不存在,则创建。若存在,那么全量替换,替换document的json串内容。
(2)document的内容是不可变得。es会先将老的document标记为deleted,然后新增给定的document,并在合适的时间删除标记为deleted的document
(二)document的强制创建
创建文档和全量替换的语法一致。那么有时我们需要新建而非替换文档。
PUT /index/type/id?op_type=create PUT /index/type/id/_create
(三)文档的删除
(1)DELETE /index/type/id
(2)同样不会直接物理删除,先标记为deleted,在适当的时间自动删除。
1.3.2.3 _all元数据
filed中有一个字段all。就是把所有其他字段中的值,以空格为分隔符组成一个大字符串,然后被分析和索引。但是不存储。 _all查询会占用更多的cpu和磁盘空间。
_all能让你在不知道查找的内容属于哪些字段的情况下进行搜索。
1.3.3悲观锁与乐观锁
(一)悲观锁
优点:方便,直接加锁对应用程序透明。
缺点:并发能力低,单线程
行级锁,表级锁,读锁,写锁。
(二)乐观锁
优点:并发能力高,多线程,无锁。
缺点:麻烦。每次更新都需要对比版本号。不同重新加载数据再次修改。这样的操作可能要重复好几次。
(三)elasticsearch内部基于_versioin乐观锁进行并发控制
(1) 版本变化的时间
刚创建的时候版本是1,每次修改或者删除自动加1.
(2)我们可以利用_version优点来确保我们程序修改数据冲突时无法提交从而避免数据丢失。elasticsearch使用 _version来确保所有的操作都被正常排序。旧版本出现在新版本之前,那么就会被忽略。采用内部版本号进行并发控制时,版本号相同那么才允许修改。
制定版本进行请求: PUT /index/type/1?version=1
(3) version_type=external
es提供了一个特点,我们可以不提供内部版本号来进行并发控制。基于自己维护的一个版本号进行并发控制。举个例子,版本在数据库中也有一份,我们可以使用数据库中的版本进行并发控制,只要数据库版本号大于es版本号那么才允许修改。一旦修改成功,外部版本号将会替换es内部版本号。重新基于最新版本号进行更新。
PUT /index/type/id?version=3&versiono_type=external
(4)retry策略:
基于最新版本号更新,若非最新重新获取document数据然后修改。最多尝试retry_on_conflict次数。
PUT /index/type/id/_update?retry_on_conflict=5
(5)consistency
我们在发送任何一个增删改操作的时候都可以加上一个参数consistency,指明我们想要的一致性是什么。consistency的参数固定三个:
one: 要求只要有一个primay shard 是active活跃的就可以执行
quroum :写之前确保大多数shard可用才可执行。算法:int( (primary + number_of_replicas) / 2) +1 当number_of_replicas > 1才生效。
注意两个特殊情况:3个primary shard,replica=1,要求至少3个shard是active,3个shard按照之前学习的shard&replica机制,必须在不同的节点上,如果说只有1台机器的话,是不是有可能出现说,3个shard都没法分配齐全,此时就可能会出现写操作无法执行的情况。1个primary shard,replica=3,quorum=((1 + 3) / 2) + 1 = 3,要求1个primary shard + 3个replica shard = 4个shard,其中必须有3个shard是要处于active状态的。如果这个时候只有2台机器的话,会出现什么情况呢?es提供了一种特殊的处理场景,就是说当number_of_replicas>1时才生效,因为假如说,你就一个primary shard,replica=1,此时就2个shard(1 + 1 / 2) + 1 = 2,要求必须有2个shard是活跃的,但是可能就1个node,此时就1个shard是活跃的,如果你不特殊处理的话,导致我们的单节点集群就无法工作。
all:要求所有的primary shard 和replica shard是活跃的才能操作。
1.3.4 请求与路由
1.3.4.1 路由算法
shard = hash(routing) % number_of_primary_shardsrouting: 每次删除改查document的时候,都会带来一个routing number,默认routing就是id(可以手动的指定)
对routing进行hash,然后除以主分片数量,得到一个值([0-number_of_primary_shards-1])
注意:手动指定routing是很有用的例如(put /index/type/id?routing=user_id) 这样可以把某一类的document一定路由到同一个shard上,后续进行应用级别的负载均衡,以及批量读取是很有帮助的。
同时解释一下primary_shard为啥不可变:一旦primary shard中Index建立,若修改,那么路由结果改变可能就找不到数据了。
1.3.4.2 请求过程分析
(1)用户访问的时候随机选择一个node,然后将请求发送到改node(任意一个node都知道document在哪个node上,所以请求哪个node都可以)。我们将接受到请求的node叫协调节点。
(2)协调节点对document进行路由将请求转发给对应的node。一般会采用roud-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡。
(3)接受请求的Node返回document给coordinate node
(4)协调节点接受到document,可能会做一些处理后(例如分页)返回客户端
补充:
query phase:
协调节点会构建一个priority queue,长度以分页操作的from和size为准(from+size),默认为10。协调节点发送请求到所有的shard,每个shard本地搜索,并且会建立一个priority queue。各个shard把自己的优先队列返回协调节点,协调节点进行merge后形成一份from+size大小的优先队列,全局排序后的队列,放到自己的队列中.
fetch phase:
协调节点构建完priority queue之后(拿到文档id),就发送mget请求获取所有shard上对应的文档。各个shard将文档返回给协调节点,协调节点合并后再返回给client客户端。
1.3.4.3 分词
(一)normalization
在建立倒排索引的时候,会执行normalization。也就是说会对拆分出来的单词进行相应的处理,以提高后面搜索的recall召回率(命中概率)。例如时态的装换,单复数的装换,同义词的转换,大小写的转换等。
(二)内置分词器
standard analyzer(默认分词器: 大小写转换,按照空格进行拆分,去掉一些符号)
simple analyzer(更加简单,去掉一些符号)
whitespace analyzer(根据空格拆分,不会去掉符号,也不会大小写转换)
language analyzer(特定语音的分词器,例如english分词器)
1.3.4.4 解析请求结果
GET /_search { "took": 6, #请求花费了多少时间 "timed_out": false, #请求是否超时 "_shards": { #一共影响多少shards,成功几个,失败几个 "total": 6, "successful": 6, "failed": 0 }, "hits": { #搜索命中结果 "total": 10, #一共返回多少条记录 "max_score": 1, #最大相关分数多少 "hits": [ #每一个命中 { "_index": ".kibana", "_type": "config", "_id": "5.2.0", "_score": 1, "_source": { #元数据 "buildNum": 14695 } } ] } }
注意:默认timeout为空,但是我们可以手动指定timeout。在指定timeout时间内返回多少条数据就返回多少条。 GET /_search?timeout=10m(m-分钟 s-秒)
1.3.5CRUD
1.3.5.1批量操作
(一)mget
好处:介绍网络请求,提高性能
指令:
GET /index/_mget 查询同一个index下所有的文档
GET /index/type/_mget查询同一个index同一个type下 的所有的文档
(二) bulk
(1)语法:严格的语法要求,json不能换行,只能放在一行,json串之间必须有一个换行
{"action": {"metadata"}}
{"data"}
(2)哪些类型的操作可以执行
delete 删除文档,一个json串就可以
create PUT /index/type/id/create 强制创建
index 普通的put操作,可以是创建文档,也可以是全量替换文档
update 执行的partial update操作
(3)bulk操作,任何一个操作失败,不会影响其他的操作,但是在返回结果里,会告诉你异常日志。
注意bulk request太大加载到内存中性能反而会下降。
(4)buil请求过程:
不用将其转换为json对象,不会在内存中出现相同的数据copy,直接按照换行切割json。
对每两个一组的json,对取meta,进行document路由
直接将对应的json发送到node上去。
相反若采用json格式:
将json数组解析为JSONArray对象,这个时候整个数据就会在内存中出现一份一模一样的拷贝,一份是json文本,一份是JSONArray对象。
解析json数组里的每个json,对每个请求中的document进行路由。
为路由到同一个shard上的多个请求,创建一个请求数组。
将这个请求数组序列化
将序列化后的请求数组发送到对应的节点上
最大的好处就是避免了浪费非内存空间,尽可能的保证了性能。
(三)查询总结:
(1) /_search:所有索引,所有type下的所有数据都搜索出来
(2)/index/_search:指定一个index,搜索其下所有type的数据
(3)/index1,index/_search 同时搜索index1、index2下的的数据
(4)/ * 1, / * 2/_search ,模糊查询多个索引
(5)/_all/type1,type2/search 查询所有index下指定type的数据
(6)不同类型的数据查询的时候是不一样的,例如对于时间类型数据默认是全匹配。字符串会拆分。
精确匹配exact value:2017-01-01,exact value,搜索的时候,必须输入2017-01-01,才能搜索出来
模糊匹配 full text: cn vs. china like liked likes。 并非完整匹配一个值,而是可以对值进行拆分后进行匹配,也可以通过缩写,时态,大小写,同义词等进行匹配。
(7) get + request body模式发送查询请求。但是HTTP协议,一般不允许get请求带上request body,但是大多数浏览器都支持GET+request body模式。遇到不支持的场景当然可以POST /_search
(8)bool查询可以有多个子查询(多个查询条件),每个子查询都会计算一个document针对它的相关度分数,然后汇总bool所有的分数,合并为一个分数,当然filter不计算相关度分数
(四) 手动mapping,创建索引的时候制定类型对应的文档的域
PUT /website { "mappings":{ "article":{ "properties":{ "author_id":{ "type": "long" }, "content":{ "type": "text" }, "post_date": { "type": "date" }, "publisher_id":{ "type": "text", "index": false } } } } }
(五)filter与query
filter:仅仅是对按照搜索条件查询出来的数据进行过滤,不计算任何相关度分数,也不对相关度分数产生影响。同时其自动的cache最常用的filter数据。
query:会计算每一个document相对于搜索条件的相关度,并且按照相关度进行排序,而且无法cache结果。
GET /book/history/_search { "query":{ "bool": { "must": [ { "match": { "join_date": "2018-01-01" } } ] }, "filter": { "range": { "pageSize": { "gte": 30 "lte": 1000 } } } } }
(六)定制排序规则---------通过sort来定制查询规则
注意:对于string field进行排序,往往结果不正确,因为分词后是多个单词,在排序就不是我们想要的结果。通常解决方案就是将一个string field建立两次索引,一个用来分词,一个用来排序。
GET /website/article/_search { "query": { "match_all": {} }, "sort": [ { "title.raw": { "order": "desc" } } ] }
(七)搜索引擎相关度评分算法
elasticsearch使用的是term frequency/inverse document frequency算法。TF/IDF算法
term frequency: 搜索文本中的各个词在field文本中出现的次数,次数越多相关度越高。
inverse document frequency:搜索文本的各个词在整个索引的所有文档中出现的次数,次数越多越不相关。
我们可以通过指令 GET /test_index/test_type/_search? _explain查看评分 _score是如何被计算出来的。
(八)倒排索引与正排索引
在建立索引的时候,一方面会建立倒排索引用于搜索。另一方面要建立正排索引,即doc values。以供排序,聚合,过滤等操作使用。 若内存不够,os会将doc values写入磁盘。若内存足够,缓存到内存中。
(九)scoll滚动查询大批量数据
使用scroll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索完全部数据。
scroll搜索会在每一次搜索的时候保存一个快照,之后会基于该旧的视图快照提供数据搜索,若这个期间数据发生变更,是不会让用户看到的。
采用基于_doc进行排序的方式性能较高。
每次发送scroll请求,我们还需制定一个scroll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内可以完成就可以。
GET /test_index/test_type/_search?scroll=1m
scroll看起来挺像分页的,但是其使用场景不一样,分页主要是一页页搜索返回给用户看的,而scroll则是批量处理数据,让系统进行处理。