1、实战项目Search
流程:爬虫获取数据 => ES => 前端搜索
定义索引的maping
POST /jd_goods/_mapping
{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"price": {
"type": "text",
"index": false
},
"keyword": {
"type": "text",
"analyzer": "ik_max_word"
}
"commit": {
"type": "text",
"index": false // 评论数
},
"shop": {
"type": "text",
"analyzer": "ik_smart"
},
"img": {
"type": "text",
"index": false
}
}
}
项目结构
测试页面
源码:https://gitee.com/jacobmj/study-demo/tree/master/jacob-jd-search
2、ES集群
核心概念
Cluster:集群,一个集群有很多个节点
Shards:分片,我们在创建索引的时候需要填写分片数和副本数,是因为ES会把一个完整的索引,分割成分片,这些分片存储在不同的节点上,就可以构成分布式搜索,达到高性能,高可用的目的。分片数在索引创建后不能修改,除非重建索引。
Replicas: 副本,分片的一个副本(拷贝),副本的作用;容错性,高可用。如果主分片坏了,ES会将一个副本分片中升级为主分片,再拷贝一个副本。副本分片可以分担读的负载,提高查询效率,这就是所谓的ES自带负载均衡
为什么要实现集群
-
业务量增加,索引变多,数据量变大,单个es服务器节点已无法支撑正常使用,查询变慢,存储变慢
-
采用ES集群来解决这些问题,我们可以将单个索引,分片到不同的节点(物理服务器)存储,横向扩展,实现高可用,高性能,高并发
ES集群中,索引是由 分片 和 副本构成,如下图,粗框为主分片,细框为副本分片
集群的规划
1、我们需要多大规模(多少个节点)的集群
考虑方向:
1、当前的数据量有多大?数据量增长情况?(业务)
2、机器的配置?CPU、多大内存、硬盘容量(物理硬件)
推算依据:
1、ES 默认的 java heap占用内存消耗很大,最大可以设置 32G
2、32G 内存大概可以处理的数据量是 10T ,如果你的服务器有128G内存,这个时候你可以在一个机器上运行多个ES节点构成集群(节点一般分布在不同服务器,避免服务器宕了,整个ES挂了)
集群规划要满足当前数据的规模+数据的增长速度,后面按需扩展即可!
场景:
1、构建业务搜索(垂直搜索),数据规模: 几千万 到 数十亿, ES集群需要2~4 台机器即可。
2、大规模的数据 OLAP(联机处理分析), ELK数据规模:千亿级别, ES集群需要几十上百的节点。
2、集群中节点如何分配
默认情况下,每个es节点都承担了以下功能:
Master: 主节点
DataNode: 数据节点
Coordinate node : 协调节点 (只用来接收,转发请求到其他节点,汇聚所有的节点返回的数据等等….),客户端连接ES集群时,就会选择一个节点作为协调节点,主要作用就是通过hash算法路由到读写数据(文档)所在的分片上
规则:
1、小规模集群, 不需要严格区分角色,也就是不分配。
2、中大规模集群(10个节点),如果压力很大,可以适当增加一些协调节点(分工,降低压力)
配置文件配置下即可
node.mater: true # 是主节点
node.data: true # 是数据节点
3、如何避免脑裂问题
脑裂问题
一个集群中只有一个主节点A,A节点十分忙。因为网络故障, 其他节点ping不通A节点。这样的话其他节点就认为 A 节点不可用,就会重新选举一个 B为主节点。这个时候A又正常了,就会出现两个主节点? 数据可能一部分来自A,一部分来自B,出现数据不一致的问题,这就是脑裂。
解决方案:
discovery.zen.minimum_master_nodes: 配置最小候选master主节点票数
ES中master主节点是要经过多个有资格成为master(node.master=true)的节点选举后才能成为新的节点,不是你一个节点自己选自己就能决定的,当一个候选节点得到的票数 >=discovery.zen.minimum_master_nodes 的配置值,那它才能成为新的master节点。
这个配置值也叫 quorum数量,一般不用配置discovery.zen.minimum_master_nodes,当业务量很大的时候才需要配置。
官方推荐:discovery.zen.minimum_master_nodes = 有资格选举 master节点的数量/2+1举例:
1、如果有10个节点,都是data node,也是master的候选节点。则quorum=10/2+1=6
2、如果有3个master候选节点,100个数据节点。则quorum=3/2+1=2
3、如果有2个节点,都是data node,也是master的候选节点。则quorum=2/2+1=2(有问题),无法防止脑裂,所以一个ES集群最少有3个节点。
discovery.zen.minimum_master_nodes除了在配置文件设置,也可以动态设置
PUT /_cluster/settings
{
"persistent":{
"discovery.zen.minimum_master_nodes":2
}
}
集群配置(常用)
1、直接将 master 候选节点 和 data 数据节点分开,配置上奇数个的master候选节点 , 最少3个。
2、配置单播,ping(默认是关闭的)
discovery.zen.ping.multicast.enabled: false # 关闭多播发现机制(默认就是关闭的)
discovery.zen.ping.unicast.hosts: ["localhost:9001","localhost:9101","localhost:9701"]
单播配置一些master候选节点的ip地址,其他的节点要加入进来。需要在这个单播配置的master候选节点中去等待统一。等待master主节点同意,就会自动加入集群。
4、如何设置索引的分片
思考:
分片对应的存储实体是什么? 底层本质就是一个 Lucene索引。
分片是不是越多越好? 不是
分片多了有什么影响?存储空间浪费,占用资源,影响性能。
分片越多,每个分片的数据量就少,查询性能反而受影响 会下降
规则:
最大的ES内存推荐,32G。
1、200GB = 7/8个分片,索引大概200G数据量,7个分片就可以了
2、按照节点数量来分配;如 总共有3个节点, 3 * (1.5~3倍左右),那么分片数就是5个到9个, 尽量不要超过 9 个,以此类推计算。
5、如何设置副本数
默认一个副本就好,不超过2个副本。
作用:高可用,保证数据不丢失,高并发的时候可以参与查询。
搭建集群
核心就是修改elasticsearch.yml这个配置文件
下面模拟一个服务器上开启3个ES实例(节点)搭建ES集群,一个服务器一个ES实例(节点)最好,那就不用修改端口了。
# linux服务器上,复制elasticsearch,分别以端口命名
[esuser@helloworld ~]$ cp -r elasticsearch-7.6.1 elasticsearch-9201
[esuser@helloworld ~]$ cp -r elasticsearch-7.6.1 elasticsearch-9202
[esuser@helloworld ~]$ ls
elasticsearch-7.6.1 elasticsearch-9201 elasticsearch-9202 kibana-7.6.1
# 删除复制过来的数据和日志
[esuser@helloworld elasticsearch-9201]$ rm -rf data logs
# 重新创建数据目录和日志目录
[esuser@helloworld elasticsearch-9201]$ mkdir data logs
# 修改配置文件
elasticsearch.yml配置集群参数,端口ip自行修改
# 1、集群名称,
cluster.name: coding-es
# 2、节点名和节点作用
node.name: node-3
# 是否有资格被选举成为master节点(是不是master候选节点)
node.master: true
# 是否存储索引的数据
node.data: true
# 存储路径
path.data: /usr/local/elasticsearch/esdata
path.logs: /usr/local/elasticsearch/eslogs
# 绑定可以访问的ip, 如果是云服务器,配置0.0.0.0,不限制访问的ip地址
network.host: 127.0.0.1
# http访问端口( 如 elasticsearch head, kibana就是通过该端口访问ES),默认是9200
http.port: 9202
# tcp访问端口(集群内节点通信端口,如主分片的数据复制到副本分片),默认是9300
transport.tcp.port: 9302
# discovery.zen.ping.multicast.enabled: false # 关闭多播发现机制(默认就是关闭的)
# 3、候选master节点列表
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
# es 7.x版本的参数配置可以使用这个
discovery.seed_hosts: ["127.0.0.1:9300", "127.0.0.1:9301","127.0.0.1:9302"]
# 初始主节点,为节点名
cluster.initial_master_nodes: ["node-1"]
# 配置最小候选master主节点
discovery.zen.minimum_master_nodes: 2
# 配置跨域
http.cors.enabled: true
http.cors.allow-origin: "*"
启动集群,
[esuser@helloworld ~]$ ./elasticsearch-7.6.1/bin/elasticsearch -d
[esuser@helloworld ~]$ ./elasticsearch-9201/bin/elasticsearch -d
[esuser@helloworld ~]$ ./elasticsearch-9202/bin/elasticsearch -d
# 检查是否都启动成功
[esuser@helloworld ~]$ ps -ef|grep elastic
# 云服务器上防火墙和安全组要开放端口 9200,9201,9202,才能外网访问
elasticsearch head 访问其中一个节点,星标记的节点为集群的master节点
添加一个节点,分片会自动调整到对应的节点上
删除两个节点试试,分片也会自动调整,
可以看到node-1 的主分片P3 会复制一份副本R3放到node-3节点上,副本分片R4会升级为P4,然后复制一份副本R4放到node-3节点上。node-3的副本分片R0、R1会升级为P0、P1,然后复制一份副本放到node-1节点上,主分片P2复制一份副本R2放到node-1节点上,这样就完成了整个索引的分片调整。
ES集群对与客户端来说就是一个整体的服务
核心原理
1、每个索引会被分片存储,分配到不同的集群节点中,默认是5个分片。
2、高可用,我们就需要给每个分片设置副本,副本分片可以分担查询请求的负载。
3、存放文档路由规则,hash规则
4、ES 的核心存储: 索引(分片存储)->文档
5、ES一旦使用集群,分片就归集群管理(完全是自动化的)
6、高可用例子:
3、面试题学习
1、2019年常见ElasticSearch面试题解析(上)
https://yq.aliyun.com/articles/740659?spm=a2c4e.11153940.0.0.165f198dH6erGJ
1、elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段 。
1.1、设计阶段调优 (1)根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引; (2)使用别名进行索引管理。 (3)每天凌晨定时对索引做 force_merge (归并)操作,以释放空间;
而是在负载较低的时间段,通过 forcemerge 接口,强制归并 segment。
# curl -XPOST http://127.0.0.1:9200/logstash-2015-06.10/_forceme rge?max_num_segments=1
(4)采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储; (6)仅针对需要分词的字段,合理的设置分词器; (7)Mapping 阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。……..
1.2、写入调优 (1)写入前副本数设置为 0; (2)写入前关闭 refresh_interval 设置为-1,禁用刷新机制; (3)写入过程中:采取 bulk 批量写入; (4)写入后恢复副本数和刷新间隔; (5)尽量使用自动生成的 id。自定义业务id
1.3、查询调优 (1)禁用 wildcard; (2)禁用批量 terms(成百上千的场景); (3)充分利用倒排索引机制,能 keyword 类型尽量 keyword; (4)数据量大时候,可以先基于时间敲定索引再检索; (5)设置合理的路由机制。
2、elasticsearch 的倒排索引是什么
像redis,使用key-value的方式查询,通过key找到value,这是正排索引。通过value找到包含这个value相似度极高的文档,这就是倒排索引,例如通过歌词找歌,这就需要全文搜索引擎。倒排索引从词出发,记载了这些词在哪些文档出现过,由两部分组成:词典和倒排表。倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。
3、elasticsearch 索引数据多了怎么办,如何调优,部署
索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户检索或者其他业务受到影响。 如何调优,正如问题 1 所说,这里细化一下: 3.1 动态索引层面 基于模板+时间+rollover api 滚动创建索引,举例:设计阶段定义:blog 索引的模板格式为:blog_index_时间戳的形式,每天递增数据。这样做的好处:不至于数据量激增导致单个索引数据量非常大,接近于上线 2 的32 次幂-1,索引存储达到了 TB+甚至更大。 一旦单个索引很大,存储等各种风险也随之而来,所以要提前考虑+及早避免。 3.2 存储层面 冷热数据分离存储,热数据(比如最近 3 天或者一周的数据),其余为冷数据。 对于冷数据不会再写入新数据,可以考虑定期 force_merge 加 shrink 压缩操作,节省存储空间和检索效率。 3.3 部署层面 一旦之前没有规划,这里就属于应急策略。 结合 ES 自身的支持动态扩展的特点,动态新增机器的方式可以缓解集群压力,注意:如果之前主节点等规划合理,不需要重启集群也能完成动态新增的。
4、elasticsearch 是如何实现 master 选举的
配置文件elasticsearch.yml,
(1)只有候选主节点(master:true)的节点才能成为主节点。
node.master: true 配置节点有机会成为master,
(2)最小主节点数(min_master_nodes)的目的是防止脑裂。
discovery.zen.minimum_master_nodes=(N/2)+1,N就是候选master节点数
选举流程大致描述如下: 第一步:确认候选主节点数达标,elasticsearch.yml 设置的值 discovery.zen.minimum_master_nodes; 第二步:比较:先判定是否具备 master 资格,具备候选主节点资格的优先返回; 若两节点都为候选主节点,则 id 小的值会优先成为主节点。注意这里的 id 为 string 类型。
5、详细描述一下 Elasticsearch 索引文档的过程
写原理
第一步:客户写集群某节点写入数据,发送请求。(如果没有指定路由/协调节点,请求的节点扮演路由节点的角色。) 第二步:节点 1 接受到请求后,使用文档_id 来确定文档属于分片 P2。请求会被转到另外的节点,假定节点 3。 第三步:节点 3 在主分片P2上执行写操作,如果成功,则将请求并行转发到副本分片R2上。所有的副本分片都报告成功,节点 3 将向协调节点(节点 1)报告成功,节点 1 向请求客户端报告写入成功。
读原理
第一步:客户端向集群某节点发起读请求,会先选择一个协调节点
第二步:协调节点根据数据的请求hash得知是放在哪个分片上的(路由算法)
由于数据在主副本分片上都有,并且数据一模一样,读取操作在主分片和副本分片上是采用轮询的方式(因此副本分片多了后会提升分片的负载能力)
第三部:数据查询完毕后返回给协调节点,协调节点返回客户端
1shard = hash(_routing) % (num_of_primary_shards)
6、详细描述一下 Elasticsearch 搜索的过程?
搜索拆解为“query then fetch” 两个阶段。 query 阶段的目的:定位到位置 步骤拆解如下: (1)假设一个索引数据有 5 主+1 副本 共 10 分片,一次请求会命中(主或者副本分片中)的一个(会命中5个分片)。 (2)每个分片在本地进行查询,结果返回到本地有序的优先队列中。 (3)第 2)步骤的结果(docid,score精确读)发送到协调节点,协调节点产生一个全局的排序列表。 fetch 阶段的目的:取数据,接着由协调节点根据
doc id
去各个节点上拉取实际的document
数据,最终返回给客户端。7、Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法
(1)关闭缓存 swap; (2)堆内存设置为:Min(节点内存/2, 32GB),config/jvm.options (3)设置最大文件句柄数;vi /etc/sysctl.conf,设置vm.max_map_count=262145 (4)线程池+队列大小根据业务需要做调整;vi /etc/security/limits.conf (5)磁盘存储 raid 方式——存储有条件使用 RAID10,增加单节点性能以及避免单节点存储故障。
8、lucence 内部结构是什么?
Lucene是一个Java语言开发的开源全文检索引擎工具包,有索引和搜索的两个过程,包含索引创建,索引,搜索三个要点。用Netty封装成服务,使用JSON访问就是ES了。
10、Elasticsearch 中的节点(比如共 20 个)选了一个 master,另外 10 个选了另一个 master,怎么办?
出现脑裂问题,ES无法正常提供服务,
(1)当集群 master 候选数量不小于 3 个时,可以通过设置最少投票通过数量(discovery.zen.minimum_master_nodes)超过所有候选节点一半以上来解决脑裂问题; (3)当候选数量为两个时,只能修改为唯一的一个 master 候选,其他作为 data节点,避免脑裂问题。
11、客户端在和集群连接时,如何选择特定的节点执行请求的?
轮询当协调节点,TransportClient 利用 transport 模块远程连接一个 elasticsearch 集群。它并不加入到集群中,只是简单的获得一个或者多个初始化的 transport 地址,并以 轮询 的方式与这些地址进行通信。
2、2019年常见Elasticsearch 面试题解析(下)
https://yq.aliyun.com/articles/740770
13、详细描述一下 Elasticsearch 更新和删除文档的过程。
- 删除和更新也都是写操作
,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;
磁盘上的每个段都有一个相应的.del 文件。当删除请求发送后,文档并没有真的被删除,而是在.del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时forcemerge,在.del 文件中被标记为删除的文档将不会被写入新段。
- 在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
14、详细描述一下 Elasticsearch 搜索的过程。
搜索拆解为“query then fetch” 两个阶段。 query 阶段的目的:定位到位置(分片) 步骤拆解如下: (1)假设一个索引数据有 5 主+1 副本 共 10 分片,一次请求会命中(主或者副本分片中)的一个(会命中5个分片)。 (2)每个分片在本地进行查询,结果返回到本地有序的优先队列中。 (3)第 2)步骤的结果发送到协调节点,协调节点产生一个全局的排序列表。 fetch 阶段的目的:取数据
协调节点从排序列表中,取出对应数量的数据返回客户端
15、在 Elasticsearch 中,是怎么根据一个词找到对应的倒排索引的?
当ES写入数据时,就会对可以搜索的字段(index:true)的内容根据指定的分词器拆分出词,并标记词与文档的关系,这就是倒排表,当用一个词搜索的时候,就会用该词与倒排表的词匹配找出文档。
17、对于 GC 方面,在使用 Elasticsearch 时要注意什么?
(1)倒排词典的索引需要常驻内存,无法 GC,需要监控 data node 上 segmentmemory 增长趋势。 (2)各类缓存,field cache, filter cache, indexing cache, bulk queue 等等,要设置合理的大小,并且要应该根据最坏的情况来看 heap 是否够用,也就是各类缓存全部占满的时候,还有 heap 空间可以分配给其他任务吗?避免采用 clear cache等“自欺欺人”的方式来释放内存。 (3)避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景,可以采用scan & scroll api 来实现。 (4)cluster stats 驻留内存并无法水平扩展,超大规模集群可以考虑分拆成多个集群通过 tribe node 连接。 (5)想知道 heap 够不够,必须结合实际应用场景,并对集群的 heap 使用情况做持续的监控。 (6)根据监控数据理解内存需求,合理配置各类circuit breaker,将内存溢出风险降低到最低
18、Elasticsearch 对于大数据量(上亿量级)的聚合如何实现?
19、在并发情况下,Elasticsearch 如果保证读写一致?
每个文档都有版本号,写入数据前先确认版本号是否一致。
- 对于写操作,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。
- 对于读操作,可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜索请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。
20、如何监控 Elasticsearch 集群状态?
过 Kibana 监控 Elasticsearch。你可以实时查看你的集群健康状态和性能,也可以分析过去的集群、索引和节点指标
21、介绍下你们电商搜索的整体技术架构。
22、介绍一下你们的个性化搜索方案?
基于word2vec和Elasticsearch实现个性化搜索 (1)基于word2vec、Elasticsearch和自定义的脚本插件,我们就实现了一个个性化的搜索服务,相对于原有的实现,新版的点击率和转化率都有大幅的提升; (2)基于word2vec的商品向量还有一个可用之处,就是可以用来实现相似商品的推荐; (3)使用word2vec来实现个性化搜索或个性化推荐是有一定局限性的,因为它只能处理用户点击历史这样的时序数据,而无法全面的去考虑用户偏好,这个还是有很大的改进和提升的空间;
23、是否了解字典树?
Trie 的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。它有 3 个基本性质: 1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。 2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。 3)每个节点的所有子节点包含的字符都不相同。
1)可以看到,trie 树每一层的节点数是 26^i 级别的。所以为了节省空间,我们还可以用动态链表,或者用数组来模拟动态。而空间的花费,不会超过单词数×单词长度。 (2)实现:对每个结点开一个字母集大小的数组,每个结点挂一个链表,使用左儿子右兄弟表示法记录这棵树; (3)对于中文的字典树,每个节点的子节点用一个哈希表存储,这样就不用浪费太大的空间,而且查询速度上可以保留哈希的复杂度 O(1)。
24、拼写纠错是如何实现的?
1、拼写纠错是基于编辑距离来实现;编辑距离是一种标准的方法,它用来表示经
过插入、删除和替换操作从一个字符串转换到另外一个字符串的最小操作步数;
2、编辑距离的计算过程:比如要计算 batyu 和 beauty 的编辑距离,先创建一个
7×8 的表(batyu 长度为 5,coffee 长度为 6,各加 2),接着,在如下位置填入
黑色数字。其他格的计算过程是取以下三个值的最小值:
如果最上方的字符等于最左方的字符,则为左上方的数字。否则为左上方的数字
+1。(对于 3,3 来说为 0)
左方数字+1(对于 3,3 格来说为 2)
上方数字+1(对于 3,3 格来说为 2)
最终取右下角的值即为编辑距离的值 3。
对于拼写纠错,我们考虑构造一个度量空间(Metric Space),该空间内任何关系满足以下三条基本条件: d(x,y) = 0 – 假如 x 与 y 的距离为 0,则 x=y d(x,y) = d(y,x) – x 到 y 的距离等同于 y 到 x 的距离 d(x,y) + d(y,z) >= d(x,z) – 三角不等式 (1)根据三角不等式,则满足与 query 距离在 n 范围内的另一个字符转 B,其与 A的距离最大为 d+n,最小为 d-n。 (2)BK 树的构造就过程如下:每个节点有任意个子节点,每条边有个值表示编辑距离。所有子节点到父节点的边上标注 n 表示编辑距离恰好为 n。比如,我们有棵树父节点是”book”和两个子节点”cake”和”books”,”book”到”books”的边标号 1,”book”到”cake”的边上标号 4。从字典里构造好树后,无论何时你想插入新单词时,计算该单词与根节点的编辑距离,并且查找数值为d(neweord, root)的边。递归得与各子节点进行比较,直到没有子节点,你就可以创建新的子节点并将新单词保存在那。比如,插入”boo”到刚才上述例子的树中,我们先检查根节点,查找 d(“book”, “boo”) = 1 的边,然后检查标号为1 的边的子节点,得到单词”books”。我们再计算距离 d(“books”, “boo”)=2,则将新单词插在”books”之后,边标号为 2。 (3)查询相似词如下:计算单词与根节点的编辑距离 d,然后递归查找每个子节点标号为 d-n 到 d+n(包含)的边。假如被检查的节点与搜索单词的距离 d 小于 n,则返回该节点并继续查询。比如输入 cape 且最大容忍距离为 1,则先计算和根的编辑距离 d(“book”, “cape”)=4,然后接着找和根节点之间编辑距离为 3 到5 的,这个就找到了 cake 这个节点,计算 d(“cake”, “cape”)=1,满足条件所以返回 cake,然后再找和 cake 节点编辑距离是 0 到 2 的,分别找到 cape 和cart 节点,这样就得到 cape 这个满足条件的结果。
3、4个最常见的es面试问题
https://blog.csdn.net/zl1zl2zl3/article/details/89035904
1、es 写数据过程
- 客户端选择一个 node 发送请求过去,这个 node 就是
coordinating node
(协调节点),(多个节点的情况下就是轮询)。coordinating node
对 document (根据文档id)进行路由,将请求转发给对应的 node(有 primary shard)。- 实际的 node 上的
primary shard
处理请求,然后将数据同步到replica node
。coordinating node
如果发现primary node
和所有replica node
都搞定之后,就返回响应结果给客户端。
2、es 读数据过程
可以通过
doc id
来查询,会根据doc id
进行 hash,判断出来当时把doc id
分配到了哪个 shard 上面去,从那个 shard 去查询。
- 客户端发送请求到任意一个 node,成为
coordinate node
。coordinate node
对doc id
进行哈希路由,将请求转发到对应的 node,此时会使用round-robin
随机轮询算法,在primary shard
以及其所有 replica 中随机选择一个,让读请求负载均衡。- 接收请求的 node 返回 document 给
coordinate node
。coordinate node
返回 document 给客户端。3、写数据底层原理
先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。
如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据
refresh
到一个新的segment file
中,但是此时数据不是直接进入segment file
磁盘文件,而是先进入os cache
。这个过程就是refresh
。每隔 1 秒钟,es 将 buffer 中的数据写入一个新的
segment file
,每秒钟会产生一个新的磁盘文件segment file
,这个segment file
中就存储最近 1 秒内 buffer 中写入的数据。但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。
操作系统里面,磁盘文件其实都有一个东西,叫做
os cache
,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入os cache
,先进入操作系统级别的一个内存缓存中去。只要buffer
中的数据被 refresh 操作刷入os cache
中,这个数据就可以被搜索到了。为什么叫 es 是准实时的?
NRT
,全称near real-time
。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的restful api
或者java api
,手动执行一次 refresh 操作,就是手动将 buffer 中的数据刷入os cache
中,让数据立马就可以被搜索到。只要数据被输入os cache
中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将
buffer
数据写入一个又一个新的segment file
中去,每次refresh
完 buffer 清空,translog 保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发commit
操作。commit 操作发生第一步,就是将 buffer 中现有数据
refresh
到os cache
中去,清空 buffer。然后,将一个commit point
写入磁盘文件,里面标识着这个commit point
对应的所有segment file
,同时强行将os cache
中目前所有的数据都fsync
到磁盘文件中去。最后清空 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。这个 commit 操作叫做
flush
。默认 30 分钟自动执行一次flush
,但如果 translog 过大,也会触发flush
。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件
translog
中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会丢失 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接
fsync
到磁盘,但是性能会差很多。实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的数据丢失。
总结一下,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。
数据写入 segment file 之后,同时就建立好了倒排索引。
4、删除/更新数据底层原理
如果是删除操作,commit 的时候会生成一个
.del
文件,里面将某个 doc 标识为deleted
状态,那么搜索的时候根据.del
文件就知道这个 doc 是否被删除了。如果是更新操作,就是将原来的 doc 标识为
deleted
状态,然后新写入一条数据。buffer 每 refresh 一次,就会产生一个
segment file
,所以默认情况下是 1 秒钟一个segment file
,这样下来segment file
会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个segment file
合并成一个,同时这里会将标识为deleted
的 doc 给物理删除掉,然后将新的segment file
写入磁盘,这里会写一个commit point
,标识所有新的segment file
,然后打开segment file
供搜索使用,同时删除旧的segment file
。5、底层lucene
简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。我们用 Java 开发的时候,引入 lucene jar,然后基于 lucene 的 api 去开发就可以了。
通过 lucene,我们可以将已有的数据建立索引,lucene 会在本地磁盘上面,给我们组织索引的数据结构。
6、倒排索引
在搜索引擎中,每个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。例如,文档 1 经过分词,提取了 20 个关键词,每个关键词都会记录它在文档中出现的次数和出现位置。
那么,倒排索引就是关键词到文档 ID 的映射,每个关键词都对应着一系列的文件,这些文件中都出现了关键词。
举个栗子。
有以下文档:
Dicid Doc 1 谷歌地图之父跳槽facebook 2 谷歌地图之父加盟facebook 3 谷歌地图创始人拉斯离开谷歌加盟Facebook 4 谷歌地图之父跳槽facebook与wave项目取消有关 对文档进行分词之后,得到以下倒排索引。
wordid word docids 1 谷歌 1,2,3,4 2 地图 1,2,3,4 3 之父 1,2,4 4 跳槽 1,4 5 1,2,3,4 6 加盟 2,3 另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。
那么,有了倒排索引,搜索引擎可以很方便地响应用户的查询。比如用户输入查询
要注意倒排索引的两个重要细节:
- 倒排索引中的所有词项对应一个或多个文档
- 倒排索引中的词项根据字典顺序升序排列