ElasticStack系列,第三章
一、全文搜索
全文搜索两个最重要的方面是:
- 相关性(Relevance) 它是评价查询与其结果间的相关程度,并根据这种相关程度对结果排名的一种能力。
- 分词(Analysis) 它是将文本块转换为有区别的、规范化的 token 的一个过程,目的是为了创建倒排索引以及查询倒排索引。
1、构造数据
PUT /lee
{
"settings":{
"index":{
"number_of_shards":"1",
"number_of_replicas":"0"
}
},
"mappings":{
"person":{
"properties":{
"name":{
"type":"text"
},
"age":{
"type":"integer"
},
"mail":{
"type":"keyword"
},
"hobby":{
"type":"text",
"analyzer":"ik_max_word" ##增加了一个IK分词器
}
}
}
}
}
批量插入数据:
POST http://IP:9200/lee/_bulk
{"index":{"_index":"lee","_type":"person"}}
{"name":"张三","age": 20,"mail": "111@qq.com","hobby":"羽毛球、乒乓球、足球"}
{"index":{"_index":"lee","_type":"person"}}
{"name":"李四","age": 21,"mail": "222@qq.com","hobby":"羽毛球、乒乓球、足球、篮球"}
{"index":{"_index":"lee","_type":"person"}}
{"name":"王五","age": 22,"mail": "333@qq.com","hobby":"羽毛球、篮球、游泳、听音乐"}
{"index":{"_index":"lee","_type":"person"}}
{"name":"赵六","age": 23,"mail": "444@qq.com","hobby":"跑步、游泳、篮球"}
{"index":{"_index":"lee","_type":"person"}}
{"name":"孙七","age": 24,"mail": "555@qq.com","hobby":"听音乐、看电影、羽毛球"}
2、单词搜索
POST /lee/person/_search
{
"query":{
"match":{
"hobby":"音乐"
}
},
"highlight":{
"fields":{
"hobby":{
}
}
}
}
结果:
{
"took": 496,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.81652206,
"hits": [
{
"_index": "lee",
"_type": "person",
"_id": "-gvDqm4BUj3xNCBuUsDV",
"_score": 0.81652206,
"_source": {
"name": "王五",
"age": 22,
"mail": "333@qq.com",
"hobby": "羽毛球、篮球、游泳、听音乐"
},
"highlight": {
"hobby": [
"羽毛球、篮球、游泳、听<em>音乐</em>"
]
}
},
{
"_index": "lee",
"_type": "person",
"_id": "_AvDqm4BUj3xNCBuUsDV",
"_score": 0.81652206,
"_source": {
"name": "孙七",
"age": 24,
"mail": "555@qq.com",
"hobby": "听音乐、看电影、羽毛球"
},
"highlight": {
"hobby": [
"听<em>音乐</em>、看电影、羽毛球"
]
}
}
]
}
}
过程说明:
- 检查字段类型
- 爱好 hobby 字段是一个 text 类型( 指定了IK分词器),这意味着查询字符串本身也应该被分词。
- 分析查询字符串
- 将查询的字符串 “音乐” 传入IK分词器中,输出的结果是单个项 音乐。因为只有一个单词项,所以 match 查询执行的是单个底层 term 查询
- 查找匹配文档
- 用 term 查询在倒排索引中查找 “音乐” 然后获取一组包含该项的文档,本例的结果是文档:3 、5
- 为每个文档评分
- 用 term 查询计算每个文档相关度评分 _score ,这是种将 词频(即词 “音乐” 在相关文档的hobby 字段中出现的频率)和 反向文档频率(即词 “音乐” 在所有文档的hobby 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。
3、多次搜索
三种匹配度:and or minimum_should_match
POST /lee/person/_search
{
"query":{
"match":{
"hobby":"音乐 篮球" ##默认是OR的关系
}
},
"highlight":{
"fields":{
"hobby":{
}
}
}
}
结果:
{
"took": 17,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 1.3192271,
"hits": [
{
"_index": "lee",
"_type": "person",
"_id": "-gvDqm4BUj3xNCBuUsDV",
"_score": 1.3192271,
"_source": {
"name": "王五",
"age": 22,
"mail": "333@qq.com",
"hobby": "羽毛球、篮球、游泳、听音乐"
},
"highlight": {
"hobby": [
"羽毛球、<em>篮球</em>、游泳、听<em>音乐</em>"
]
}
},
{
"_index": "lee",
"_type": "person",
"_id": "_AvDqm4BUj3xNCBuUsDV",
"_score": 0.81652206,
"_source": {
"name": "孙七",
"age": 24,
"mail": "555@qq.com",
"hobby": "听音乐、看电影、羽毛球"
},
"highlight": {
"hobby": [
"听<em>音乐</em>、看电影、羽毛球"
]
}
},
{
"_index": "lee",
"_type": "person",
"_id": "-wvDqm4BUj3xNCBuUsDV",
"_score": 0.6987338,
"_source": {
"name": "赵六",
"age": 23,
"mail": "444@qq.com",
"hobby": "跑步、游泳、篮球"
},
"highlight": {
"hobby": [
"跑步、游泳、<em>篮球</em>"
]
}
},
{
"_index": "lee",
"_type": "person",
"_id": "-QvDqm4BUj3xNCBuUsDV",
"_score": 0.50270504,
"_source": {
"name": "李四",
"age": 21,
"mail": "222@qq.com",
"hobby": "羽毛球、乒乓球、足球、篮球"
},
"highlight": {
"hobby": [
"羽毛球、乒乓球、足球、<em>篮球</em>"
]
}
}
]
}
}
可以看到,包含了“音乐”、“篮球”的数据都已经被搜索到了。
可是,搜索的结果并不符合我们的预期,因为我们想搜索的是既包含“音乐”又包含“篮球”的用户,显然结果返回的**“or”**的关系。
在Elasticsearch中,可以指定词之间的逻辑关系,如下:
POST /lee/person/_search
{
"query":{
"match":{
"hobby":{
"operator":"and",
"query":"音乐 篮球"
}
}
},
"highlight":{
"fields":{
"hobby":{
}
}
}
}
结果:
{
"took": 10,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1.3192271,
"hits": [
{
"_index": "lee",
"_type": "person",
"_id": "-gvDqm4BUj3xNCBuUsDV",
"_score": 1.3192271,
"_source": {
"name": "王五",
"age": 22,
"mail": "333@qq.com",
"hobby": "羽毛球、篮球、游泳、听音乐"
},
"highlight": {
"hobby": [
"羽毛球、<em>篮球</em>、游泳、听<em>音乐</em>"
]
}
}
]
}
}
前面我们测试了“OR” 和 “AND”搜索,这是两个极端,其实在实际场景中,并不会选取这2个极端,更有可能是选取这种,或者说,只需要符合一定的相似度就可以查询到数据,在Elasticsearch中也支持这样的查询,通过minimum_should_match来指定匹配度,如:70%;
{
"query":{
"match":{
"hobby":{
"minimum_should_match":"80%",
"query":"音乐 篮球"
}
}
},
"highlight":{
"fields":{
"hobby":{
}
}
}
}
4、组合搜索
在搜索时,也可以使用过滤器中讲过的bool组合查询,示例:
POST /lee/person/_search
{
"query":{
"bool":{
"must":{
"match":{
"hobby":"篮球"
}
},
"must_not":{
"match":{
"hobby":"音乐"
}
},
"should":[
{
"match":{
"hobby":"游泳"
}
}
]
}
},
"highlight":{
"fields":{
"hobby":{
}
}
}
}
搜索结果中必须包含篮球,不能包含音乐,如果包含了游泳,那么它的相似度更高。
评分的计算规则
bool 查询会为每个文档计算相关度评分 _score
再将所有匹配的 must 和 should 语句的分数 _score 求和
最后除以 must 和 should 语句的总数.
must_not 语句不会影响评分; 它的作用只是将不相关的文档排除.
默认情况下,should中的内容不是必须匹配的,如果查询语句中没有must,那么就会至少匹配其中一个。当然了,也可以通过minimum_should_match参数进行控制,该值可以是数字也可以的百分比。
POST /lee/person/_search
{
"query":{
"bool":{
"should":[
{
"match":{
"hobby":"游泳"
}
},
{
"match":{
"hobby":"篮球"
}
},
{
"match":{
"hobby":"音乐"
}
}
],
"minimum_should_match":2 ##必须有两个比配
}
},
"highlight":{
"fields":{
"hobby":{
}
}
}
}
minimum_should_match为2,意思是should中的三个词,至少要满足2个。
5、权重
有些时候,我们可能需要对某些词增加权重来影响该条数据的得分。如下:
- 搜索关键字为“游泳篮球”,如果结果中包含了“音乐”权重为10,包含了“跑步”权重为2。
POST /lee/person/_search
{
"query":{
"bool":{
"must":{
"match":{
"hobby":{
"query":"游泳篮球",
"operator":"and"
}
}
},
"should":[
{
"match":{
"hobby":{
"query":"音乐",
"boost":10
}
}
},
{
"match":{
"hobby":{
"query":"跑步",
"boost":2
}
}
}
]
}
},
"highlight":{
"fields":{
"hobby":{
}
}
}
}
结果:
{
"took": 19,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 9.484448,
"hits": [
{
"_index": "lee",
"_type": "person",
"_id": "-gvDqm4BUj3xNCBuUsDV",
"_score": 9.484448,
"_source": {
"name": "王五",
"age": 22,
"mail": "333@qq.com",
"hobby": "羽毛球、篮球、游泳、听音乐"
},
"highlight": {
"hobby": [
"羽毛球、<em>篮球</em>、<em>游泳</em>、听<em>音乐</em>"
]
}
},
{
"_index": "lee",
"_type": "person",
"_id": "-wvDqm4BUj3xNCBuUsDV",
"_score": 5.4279313,
"_source": {
"name": "赵六",
"age": 23,
"mail": "444@qq.com",
"hobby": "跑步、游泳、篮球"
},
"highlight": {
"hobby": [
"<em>跑步</em>、<em>游泳</em>、<em>篮球</em>"
]
}
}
]
}
}
如果不设置权重:
{
"took": 6,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 3.630794,
"hits": [
{
"_index": "lee",
"_type": "person",
"_id": "-wvDqm4BUj3xNCBuUsDV",
"_score": 3.630794,
"_source": {
"name": "赵六",
"age": 23,
"mail": "444@qq.com",
"hobby": "跑步、游泳、篮球"
},
"highlight": {
"hobby": [
"<em>跑步</em>、<em>游泳</em>、<em>篮球</em>"
]
}
},
{
"_index": "lee",
"_type": "person",
"_id": "-gvDqm4BUj3xNCBuUsDV",
"_score": 2.135749,
"_source": {
"name": "王五",
"age": 22,
"mail": "333@qq.com",
"hobby": "羽毛球、篮球、游泳、听音乐"
},
"highlight": {
"hobby": [
"羽毛球、<em>篮球</em>、<em>游泳</em>、听<em>音乐</em>"
]
}
}
]
}
}
权重影响了 王五和赵六的排序
二、集群
1、集群节点
1>、master节点–主节点
node.master: true表示为master的候选节点,可以参加选举.
elasticsearch正常运行时只能有一个master,大于1时会发生脑裂。
主节点将在节点重新启动期间持久化群集状态保存在data/目录
作用:
master节点用于控制整个集群的操作。比如创建或删除索引,管理其它非master节点。
以及决定将哪些分片分配给哪些节点。
配置:
node.master: true
node.data: false
2>、data节点–数据节点
node.data: true表示为数据节点.
会分配在该node上的shard的数据存储,并负责这些shared的写入、查询等.此节点操作的I/O、内存和cpu属于密集型。
作用:
data节点主要用于执行数据相关的操作。比如文档的CRUD
官方建议:
最好将专用的master和data节点分离.
配置:
node.master: false
node.data: true
3>、client节点–路由节点
node.master和node.data都为false时,该节点作为client(代理)节点,接收请求并转发、结果聚合等
作用:
该节点不能作为master节点,也不能作为data节点。
可以作为客户端节点,用于响应用户的请求,把请求转发到其他节点.
配置:
node.master: false
node.data: false
4>、tribe节点
tribe节点一种特殊类型的仅协调节点,充当跨多个群集联合客户端。
作用:
tribe节点的工作原理是从所有连接的集群中检索集群状态,并将它们合并为全局集群状态。
但是tribe节点不能创建index
配置:
tribe:
es1:
cluster.name: my-application
discovery.zen.ping.unicast.hosts: ["172.1.0.44","172.1.0.43"]
es2:
cluster.name: my-application2
discovery.zen.ping.unicast.hosts: ["172.1.0.2","172.1.0.34"]
on_conflict: prefer_es2
node.name: tribe-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 172.31.0.2
http.port: 9200
node.master: false
node.data: false
5>、 ingest 节点–预处理节点
不负责数据,也不负责集群相关事务。
作用:
它能够在索引之前预处理文档:如,拦截文档的index和bulk请求,施加转换,
然后再将文档传回给index或bulk API.
用户可以定义一个管道,指定一系列的预处理器。
注意:
一个节点可以充当一个或多个角色,默认三个角色都有
协调节点:一个节点只作为接收请求、转发请求到其他节点、汇总各个节点返回数据等功能的节点。就叫协调节点
2、集群的搭建前思考
1>、角色分配
①、小规模集群,不需严格区分。
②、中大规模集群(十个以上节点),应考虑单独的角色充当。
特别并发查询量大,查询的合并量大,可以增加独立的协调节点。
角色分开的好处是分工分开,不互影响。
如不会因协调角色负载过高而影响数据节点的能力。
2>、脑裂问题
什么是脑裂:
一个集群中只有一个A主节点,A主节点因为需要处理的东西太多或者网络过于繁忙,
从而导致其他从节点ping不通A主节点,这样其他从节点就会认为A主节点不可用了,
就会重新选出一个新的主节点B。过了一会A主节点恢复正常了,这样就出现了两个主节点,
导致一部分数据来源于A主节点,另外一部分数据来源于B主节点,出现数据不一致问题,
这就是脑裂。
如何避免脑裂:
discovery.zen.minimum_master_nodes: (有master资格节点数/2) + 1
这个参数控制的是,选举主节点时需要看到最少多少个具有master资格的活节点,才能进行选举。
官方的推荐值是(N/2)+1,其中N是具有master资格的节点的数量。
常用做法(中大规模集群):
A、Master 和 dataNode 角色分开,配置奇数个master,如3
B、单播发现机制,配置master资格节点:
discovery.zen.ping.multicast.enabled: false —— 关闭多播发现机制,默认是关闭的
discovery.zen.ping.unicast.hosts: ["master1", "master2", "master3"] —— 配置单播发现的主节点ip地址,其他从节点要加入进来,就得去询问单播发现机制里面配置的主节点我要加入到集群里面了,主节点同意以后才能加入,然后主节点再通知集群中的其他节点有新节点加入
C、配置选举发现数,及延长ping master的等待时长
discovery.zen.ping_timeout: 30(默认值是3秒)——其他节点ping主节点多久时间没有响应就认为主节点不可用了
discovery.zen.minimum_master_nodes: 2 —— 选举主节点时需要看到最少多少个具有master资格的活节点,才能进行选举
3>、设置多少个分片合适
分片过多的影响:
分片对应的存储实体是索引;
每个分片本质上就是一个Lucene索引, 因此会消耗相应的文件句柄, 内存和CPU资源。
每个搜索请求会调度到索引的每个分片中. 如果分片分散在不同的节点倒是问题不太. 但当分片开始竞争相同的硬件资源时, 性能便会逐步下降。
ES使用词频统计来计算相关性. 当然这些统计也会分配到各个分片上. 如果在大量分片上只维护了很少的数据, 则将导致最终的文档相关性较差。
分片设置参考原则:
ElasticSearch推荐的最大JVM堆空间是30~32G, 所以把你的分片最大容量限制为30GB, 然后再对分片数量做合理估算. 例如, 你认为你的数据能达到200GB, 推荐你最多分配7到8个分片。
在开始阶段, 一个好的方案是根据你的节点数量按照1.5~3倍的原则来创建分片. 例如,如果你有3个节点, 则推荐你创建的分片数最多不超过9(3x3)个。当性能下降时,增加节点,ES会平衡分片的放置。
对于基于日期的索引需求, 并且对索引数据的搜索场景非常少. 也许这些索引量将达到成百上千, 但每个索引的数据量只有1GB甚至更小. 对于这种类似场景, 建议只需要为索引分配1个分片。如日志管理就是一个日期的索引需求,日期索引会很多,但每个索引存放的日志数据量就很少。
4>、分片设置多少个副本合适
副本过多的影响:
备份数据保证高可用数据不丢失,高并发的时候参与数据查询.
一般一个分片有1-2个副本即可保证高可用
副本多浪费存储空间、占用资源、影响性能
副本设置参考原则:
为保证高可用,副本数设置为2即可。要求集群至少要有3个节点,来分开存放主分片、副本。
如发现并发量大时,查询性能会下降,可增加副本数,来提升并发查询能力。
注意:新增副本时主节点会自动协调,然后拷贝数据到新增的副本节点
3、集群搭建
1》、准备三台虚拟机:
192.168.1.101 、192.168.1.102、192.168.1.103
2》、分别安装好es:
3》、修改配置:
进入config目录,修改elasticsearch.yml的配置,如下:
a、IP访问限制
这里有两个需要提醒下,第一个就是IP访问限制,第二个就是es实例的默认端口号9200。IP访问限制可以限定具体的IP访问服务器,这有一定的安全过滤作用。
network.host: 192.168.1.101
如果设置成0.0.0.0则是不限制任何IP访问。一般在生产的服务器可能会限定几台IP,通常用于管理使用。
b、访问端口
默认的端口9200在一般情况下也有点风险,可以将默认的端口修改成另外一个,这还有一个原因就是怕开发人员误操作,连接上集群。当然,如果你的公司网络隔离做的很好也无所谓。
http.port: 9200
transport.tcp.port: 9300
这里的9300是集群内部通讯使用的端口,这个也可以修改掉。因为连接集群的方式有两种,通过扮演集群node也是可以进入集群的,所以还是安全起见,修改掉默认的端口。
注意:记得修改安装了ES的3台虚拟机(三个节点)的相同配置,要不然节点之间无法建立连接工作,也会报错。
c、集群发现IP列表
紧接着修改集群节点IP地址,这样可以让集群在规定的几个节点之间工作。elasticsearch,默认是使用自动发现IP机制。就是在当前网段内,只要能被自动感知到的IP就能自动加入到集群中。这有好处也有坏处。好处就是自动化了,当你的es集群需要云化的时候就会非常方便。但是也会带来一些不稳定的情况,如,master的选举问题、数据复制问题。
导致master选举的因素之一就是集群有节点进入。当数据复制发生的时候也会影响集群,因为要做数据平衡复制和冗余。这里面可以独立master集群,剔除master集群的数据节点能力。
固定列表的IP发现有两种配置方式,一种是互相依赖发现,一种是全量发现。各有优势吧,我是使用的依赖发现来做的。这有个很重要的参考标准,就是你的集群扩展速度有多快。因为这有个问题就是,当全量发现的时候,如果是初始化集群会有很大的问题,就是master全局会很长,然后节点之间的启动速度各不一样。所以我采用了靠谱点的依赖发现。
你需要在192.168.1.101的elasticsearch中配置成:
discovery.zen.ping.unicast.hosts: [ "192.168.1.102:9300","192.168.1.103:9300" ]
让他去发现129,130的机器,以此内推,完成剩下的129和130机器的配置。
d、集群名称
集群中的所有节点的集群名称必须一样,只有集群名称一样才能组成一个逻辑集群。
cluster.name: mycluster
e、节点名称
node.name: node-1
f、选举master节点时,最少具有的活master节点数:
discovery.zen.minimum_master_nodes: 2
(规则:master节点数/2+1)
说明:
这里搭建的是一个简单的集群,没有做集群节点角色的区分,所以3个节点默认的角色有主节点、数据节点、协调节点
选举ES主节点的逻辑:
选举的大概逻辑,它会根据分片的数据的前后新鲜程度来作为选举的一个重要逻辑。(日志、数据、时间都会作为集群master全局的重要指标)
因为考虑到数据一致性问题,当然是用最新的数据节点作为master,然后进行新数据的复制和刷新其他node。