Elasticsearch全文搜索引擎,从0到0.6
一,ES简介Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)
目录
一,ES简介
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。——摘自百度百科
- 基于Lucene
- 全文搜索引擎
- 基于RESTful web接口
- 近实时搜索
- java语言开发
1,es实现原理
Elasticsearch 的实现原理主要分为以下几个步骤:
- 首先用户将数据提交到Elasticsearch 数据库中
- 再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据
- 当用户搜索数据时候,再根据权重将结果排名,打分
- 再将返回结果呈现给用户
Elasticsearch是分布式的,所以索引可以被分成分片,每个分片可以有0个或多个副本。每个节点托管一个或多个分片,并充当协调器将操作委托给正确的分片。再平衡和路由是自动完成的。相关数据通常存储在同一个索引中,该索引由一个或多个主分片和零个或多个复制分片组成。一旦创建了索引,就不能更改主分片的数量。
Elasticsearch 将信息存储已序列化为 JSON 文档的复杂数据结构。当在集群中有多个 Elasticsearch 节点时,存储的文档将分布在整个集群中,并且可以从任何节点立即访问。
Elasticsearch使用一种称为倒排索引的数据结构,支持非常快速的全文搜索。倒排索引列出任何文档中出现的每个唯一单词,并标识每个单词出现的所有文档。
2,基本结构
Elasticsearch是面向文档型数据库,一条数据就是一个文档
index(索引库)——类比关系型数据库
type(类型)——类比数据表
document(文档)——类比一行数据
field(字段)——类比一列数据
3,ELK
Elasticsearch 是 Elastic Stack 核心的分布式搜索和分析引擎;
Logstash 和 Beats 有助于收集、聚合和丰富数据,并将其存储在 Elasticsearch 中;
Kibana 使您能够以交互方式探索、可视化和共享对数据的见解,并管理和监控堆栈。
二、Linux搭建环境
1,jdk安装
安装ElasticSearch之前必须保证JDK1.8+安装完毕,并正确的配置好JDK环境变量,否则启动ElasticSearch失败。
#检查jdk版本
java -version #没有安装jdk或没有配置环境变量,输出找不到java命令
#检查jdk是否安装
rpm -qa | grep java
下载jdk1.8:
Java Downloads | Oraclehttps://www.oracle.com/java/technologies/downloads/#java8
上传至linux:
#解压
tar -zxvf jdk-8u311-linux-x64.tar.gz
设置环境变量:
#修改环境变量配置文件
vim /etc/profile
#加入
export JAVA_HOME=/usr/local/jdk1.8.0_311
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
#执行profile文件,使配置立即生效
source /etc/profile
安装配置成功 :
2,安装Elasticsearch
新建一个用户,出于安全考虑,elasticsearch默认不允许以root账号运行:
#新增用户
useradd es
#设置密码
passwd esuser
因为linux目录权限的问题,我们直接将Elasticsearch安装在es用户的家目录中:
#从root用户切换到es用户中
su es
#解压elasticsearch
tar -zxvf elasticsearch-7.16.0-linux-x86_64.tar.gz
Elasticsearch的目录结构说明:
配置文件说明:
修改配置文件elasticsearch.yml:
默认情况下,es仅能在本地访问,设置一个地址让其可以远程访问;
默认情况下,es的端口默认为9200,可以根据实际情况进行修改。
同时,linux服务器需要放行9200端口
#查看防火墙规则 firewall-cmd --list-all #查询端口是否开放 firewall-cmd --query-port=8080/tcp #关闭端口(--permanent参数表示设置为永久) firewall-cmd --permanent --remove-port=8080/tcp #1,开启端口 firewall-cmd --permanent --add-port=8080/tcp #2,重启防火墙 firewall-cmd --reload
修改jvm.options配置文件:
堆内存是由es根据系统中可用的内存和每个节点配置的角色自动配置的,如果需要指定堆,应该通过jvm.options.d中创建一个新文件加入 -Xms4g -Xmx4g两行,同时应该设置成相同的值。
- 设置为不超过总内存的 50%。Elasticsearch 需要内存用于 JVM 堆以外的目的。例如,Elasticsearch 使用堆外缓冲区来实现高效的网络通信,并依靠操作系统的文件系统缓存来高效访问文件。JVM本身也需要一些内存。Elasticsearch 使用的内存超过使用该设置配置的限制是正常的。
- Elasticsearch 可用的堆越多,它可用于内部缓存的内存就越多。这为操作系统留下了较少的内存用于文件系统缓存。较大的堆还可能导致更长的垃圾回收暂停时间。
jvm参数:
- -Xms:初始堆大小
- -Xmx:最大堆大小
3,启动es
进入bin目录,执行一下命令:
#es启动命令
./elasticsearch
4,启动es可能遇到的错误
启动es遇到的错误:
错误[1]:
修改/etc/security/limits.conf,追加
* soft nofile 65536
* hard nofile 65536
重新登录用户,文件修改才生效。
错误[2]:
因为 elasticserach启动的时候要求当前用户(我这里是es用户)最大线程数至少为 4096 个线程,而操作系统限制该用户最大线程数为 3795,只需要修改当前用户的最大线程数即可。
修改/etc/security/limits.conf,追加
es - nproc 4096
#查看当前用户被服务器的限制
ulimit -a
错误[3]:
修改 /etc/sysctl.conf,追加以下内容:
vm.max_map_count=262144
#查看vm.max_map_count的值
sysctl -a|grep vm.max_map_count
#重启sysctl使配置生效
sysctl -p
vm.max_map_count:限制一个进程可以拥有的VMA(虚拟内存区域)的数量
错误[4]:
不适合生产环境,需要配置主节点
修改elasticsearch.yml ,修改如下内容:
#使用一组初始的主节点来引导集群
cluster.initial_master_nodes: ["node-1"]
#设置本节点名称 防止es不断连接主节点占用资源
node.name: node-1
启动成功:
浏览器访问:
4,在windonws系统上安装es的图形化界面插件
(需要有nodejs环境)
启动:
npm i #或cnpm i
npm run start
启动成功:
浏览器访问:
访问不成功,发现存在跨域问题:
配置es的elasticsearch.yml 文件,解决跨域问题,文件中追加:
http.cors.enabled: true
http.cors.allow-origin: "*"
重启es服务器,再次连接:
6,安装Kibana
Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。
Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示Elasticsearch查询动态。
下载地址(需要和es版本对应):
Kibana 7.16.0 | Elastichttps://www.elastic.co/cn/downloads/past-releases/kibana-7-16-0
配置编辑config/kibana.yml,修改配置:
server.host: "192.168.72.129"
elasticsearch.url: "http://192.168.72.29:9200"
启动kibana,(必须以r运行root权限运行):
./kibana --allow-root
启动成功:
在windows中浏览器中访问:
汉化:
配置编辑config/kibana.yml,修改配置:
i18n.locale: "zh-CN"
7,安装IK分词器
下载地址(需要和es版本一样):
IK分词器下载地址· GitHubhttps://github.com/medcl/elasticsearch-analysis-ik/releases
只需要解压到es包中的plugins/ik/目录下
重启es
成功加载ik分词器:
三、REST简介
REST即表述性状态传递(英文:Representational State Transfer,简称REST),是一组架构约束条件和原则,满足这些约束条件和原则的应用程序或设计就是RESTful。
1,REST操作
- GET:获取对象的当前状态,
- PUT:改变对象的状态
- POST:创建对象
- DELETE:删除对象
- HEAD:获取头信息
格式 | http://www.baidu.com/res/ | http://www.baidu.com/res/123 |
含义 | 请求一组资源的URI,res:资源组名 | 请求单个资源的URI,123:资源名 |
GET | 列出URI | 获取指定的资源的详细信息 |
PUT | 使用给定的一组资源替换当前整组资源 | 替换或创建指定的资源,并放到相应的资源组中 |
POST | 在本组资源中创建或追加一个新的资源 | 把指定的资源当作一个资源组,在其下创建或追加一个新的元素 |
DELETE | 删除整组资源 | 删除指定的资源 |
2,ES内置的REST接口
/index/_serach | 搜索指定索引下的数据 |
/_aliases | 获取或操作索引的别名 |
/index/ | 查看指定索引的详细信息 |
/index/type/ | 创建或操作类型 |
/index/_mapping | 创建或操作mapping |
/index/_setting | 创建或操作设置 |
/index/_open | 打开指定被关闭的索引 |
/index/_close | 关闭指定索引 |
/index/_refresh | 刷新索引,使新内容对搜索可见,但不保证数据被写入磁盘 |
/index/flush | 刷新索引,会触发Lucene提交 |
四,es实际操作
1,索引操作
(1)创建索引
可以理解成,创建索引如同在关系型数据库中创建数据库
- PUT 索引名
PUT index_1
返回执行成功信息:
如果重复添加,会返回错误信息:
PUT、DELETE是幂等方法,POST不是
幂等:指不管进行多少次操作,结果都是一样的
- PUT 索引名/类型/id
PUT index_2/type1/1
{
"name": "zhangsan",
"age": "18"
}
#! [types removal] Specifying types in document index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id}).
类型信息已经被删除,建议使用无类型请求 (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id})
返回信息:
(2)查看所有索引
GET _cat/indices?v
_cat:表示查看,indices:表示索引
查看当前ES服务器的所有索引:
返回结果字段含义:
表头 | 含义 |
health | 当前服务器健康状态,green:集群完整,yellow:单点正常、集群不完整,red:单点不正常 |
status | 索引打开关闭的状态 |
index | 索引名称 |
uuid | 索引编号id |
pri | 主分片数量 |
rep | 副本数量 |
docs.count | 可用文档数量 |
docs.deleted | 文档逻辑删除状态 |
store.size | 主分片和副分片整体占空间大小 |
pri.store.size | 主分片占空间大小 |
(3)查询指定索引
- GET 索引名
GET index_1
返回结果:
(4)删除索引
- DELETE 索引名
DELETE index_1
返回结果:
2,文档操作
文档可以理解成关系型数据库中的表数据
(1)创建文档
- POST 索引名/_doc { 内容 }
POST index_1/_doc
{
"name": "lisi",
"gender": 0,
"age": "15"
}
返回结果:
在执行一次:
POST是非幂等,每次执行结果不一样
- POST 索引名/_doc/id { 内容 }
POST index_1/_doc/1
{
"name": "zhangsan",
"gender": 1,
"age": "18"
}
返回结果:
- PUT 索引名/_doc/id { 内容 }
PUT index_1/_doc/2
{
"name": "zhangsan",
"gender": 1,
"age": "18"
}
返回结果:
不指定id时,必须用POST:因为不指定id时,id会自动生成,每次执行的操作结果不同(非幂等),每次执行都为新增数据
指定id时,可以用POST和PUT:id为指定id,第一次执行为新增数据,再次执行即为修改操作
(2)查询文档
- GET 索引名/_doc/id
GET index_1/_doc/gnNFwX0By_6QR7EhtjkS
(3)修改文档
①全量修改(需要输入全部内容):
- POST 索引名/_doc/id { 内容 } ——若此id已存在,修改,否则为新增
POST index_1/_doc/1
{
"name": "zhangsansan",
"gender": 1,
"age": "19"
}
返回结果:
- POST 索引名/_doc/id { 内容 } ——若此id已存在,修改,否则为新增
PUT index_1/_doc/1
{
"name": "zhangsansan",
"gender": 1,
"age": "19"
}
返回结果:
②局部修改(只修改某些给定字段)
- POST 索引名/_update/id { "doc":{ 字段名 : 新值} }
POST index_1/_update/1
{
"doc": {
"age": "20"
}
}
返回结果:
(5)删除文档
删除一个文档不会立即从磁盘上移除,只是先标记成已删除(逻辑删除)
根据文档id进行删除
- DELETE 索引名/_doc/id
删除不存在的文档:
根据条件进行多条删除:
- POST 索引库/_delete_by_query { "query":{ "match": { 字段名: 值 } } }
#删除index_1索引库中age字段为15的所有数据
POST index_1/_delete_by_query
{
"query":{
"match":{
"age":"15"
}
}
}
返回结果:
3、映射操作
es索引库的映射,就相当于数据库中的表结构;需要设置这个索引库中有哪些字段,以及每个字段的类型、是否索引等约束信息,称映射
(1)创建映射
- PUT 索引名/_mapping
- {
- "properties": {
- "字段名" : {
- "type": "类型",
- ”index" : true/false
- },
- ............
- }
- }
字段名:需要创建的字段
type:给字段指定的类型,可以指定如下类型
- String类型:text,可分词;keyword,不可分词,在匹配时被作为完整字段
- 基本数据类型:long; integer; short; byte; double; float;
- Date:日期类型
- Array: 数组类型
- Object: 对象
index:指定是否可以索引,默认true,字段会被索引,可以用来进行搜索;false,字段不会被索引,不能用来搜索
analyzer:指定的分词器
store:是否将数据独立存储,默认false,原始的文本会存储_source里面,独立存储的字段比从_source中解析快,但会占用更多的空间
#创建映射
PUT index_2/_mapping
{
"properties":{
"name":{
"type":"text",
"index":true
},
"sex":{
"type":"keyword",
"index":false
},
"age":{
"type":"long",
"index":false
}
}
}
返回结果:
(2)查看映射
- GET 索引名/_mapping
GET index_2/_mapping
返回结果:
4、高级查询
(1)查询全部文档
- GET 索引名/_search
- "query" :{
- "match_all": { }
- }
"query":代表一个查询对象,里面填写不同的查询属性
"match_all":查询类型,表示查询所有
GET index_1/_search
{
"query": {
"match_all": {}
}
}
(2)匹配查询
- GET 索引名/_search
- {
- "query": {
- "match":{
- 字段名:待查询的值
- }
- }
- }
match匹配类型查询:先将查询条件进行分词,然后查询,多个分词之间是or的关系
GET index_1/_search
{
"query": {
"match": {
"name": "zhangsan"
}
}
}
(3)多字段查询
- GET 索引名/_search
- {
- "query": {
- "multi_match":{
- "query": "待查询的值",
- “fileds" : [ 要查询的字段名, ........ ]
- }
- }
- }
mulit_match:可以在多个字段同时查询指定的值
GET index_1/_search
{
"query": {
"multi_match": {
"query": "zhangsan",
"fields": ["name","age"]
}
}
}
(4)指定查询字段
- GET 索引名/_search
- {
- "_source": [需要展示的字段名, .....],
- ..........
- }
默认情况下,es会展示_source所有的字段
GET index_1/_search
{
"_source": ["name"],
"query": {
"match_all": {}
}
}
(5)过滤字段
- GET 索引名/_search
- {
- "_source":{
- ”includes" : [待显示的字段, .....]
- },
- ..........
- }
includes:指定需要显示的字段
excludes:指定不需要显示的字段
#只显示name和age字段
GET index_1/_search
{
"_source": {
"includes": ["name","age"]
},
"query": {
"match_all": {}
}
}
(6)组合查询
- GET 索引名/_search
- {
- "query": {
- "bool": {
- "must" :[
- {
- "match": {
- 字段名:待查询的值
- }
- }
- }
- }
- }
bool:相当于sql中的and,or那种条件查询
- must:必须满足指定的多个条件
- must_not:必须不满足指定的多个条件
- should:只要满足指定条件的一个即可
GET index_1/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"age": "15"
}
}
]
}
}
}
(7)范围查询
- GET 索引名/_search
- {
- "query": {
- "range": {
- "待查询的字段名" :{
- “gte" : 范围边界值
- }
- }
- }
- }
range:把指定区间的数字或时间等查询出来
允许的操作如下:
- gt:大于
- gte:大于等于
- lt:小于
- lte:小于等于
GET index_1/_search
{
"query": {
"range": {
"gender": {
"gte": 1,
"lte": 2
}
}
}
}
(8)单字段排序
- GET 索引名/_search
- {
- ...............,
- "sort": [
- { "待排序的字段名": {
- “order" : desc
- }
- },
- { "待排序的字段名2": {
- “order" : asc
- }
- }
- ]
- }
sort:按照不同的字段进行排序
order:指定排序的方式
- desc:降序
- asc:升序
GET index_1/_search
{
"query": {
"match_all": {}
},
"sort": [{
"gender": {
"order": "desc"
}
}]
}
(9)高亮查询
- GET 索引名/_search
- {
- ...... , #查询对象
- "highlight": {
- ”pre_tags": html标签
- "post_tags": html标签
- "fields": {
- "待高亮的字段名" :{ }
- }
- }
- }
highligth:高亮,在进行关键字搜索时,把搜索出来的内容的关键字加粗、标色等
pre_tags:设置关键字前html标签,默认是<em>
post_tags:设置关键字后的html标签,默认是</em>
fields:需要高亮显示的字段
GET index_1/_search
{
"query": {
"match": {
"name":"zhangsan"
}
},
"highlight": {
"fields": {
"name":{}
}
}
}
(10)分页查询
- GET 索引名/_search
- {
- "query": {
- },
- “from": 当前页的起始索引
- ”size": 每页显示的条数
- }
“from": 当前页的起始索引
”size": 每页显示的条数
GET index_1/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 2
}
(11)聚合查询
- GET 索引名/_search
- {
- "aggs": {
- “随意取个名字”:{
- “max": {"field": 进行聚合的字段名称}
- }
- }
通过聚合对文档进行统计分析,
- max:最大值
- min:最小值
- sum:求和
- avg:平均值
- count:计数
size:设置要展示的原始数据的个数
GET index_1/_search
{
"aggs": {
"gender_sum": {
"sum":{
"field": "gender"
}
}
},
"size": 0
}
五、ES集群简介
1,部署集群
只要节点同属于一个局域网同一网段,而且集群名称相同,es就会自动发现其他节点并加入集群。
2,ES概念
(1)索引
文档的集合。es对这整个集合中的文档建立索引。
(2)类型
在早期的es版本中,一个类型是索引的一个逻辑的分类,由用户自定义创建,在从7.0开始的版本已放弃自定义索引类型,默认_doc类型。
(3)文档
一个文档就是一条数据,es对每个文档建立索引。
(4)字段
相当于数据表的字段,对文档数据根据不同属性进行分类标识。
(5)映射
由用户创建的处理数据的方式和规则的一些限制,比如某个字段的数据类型、分析器、是否被索引。
(6)分片
相当于数据库的分库分表,一个索引可以存储超出单个节点硬件磁盘空间限制的数据,同时解决单个节点处理搜索请求响应慢的问题,es把一个索引的文档分成多份,每一份是一个分片。
创建索引可以指定分片的数量,每个分片本身是一个功能完善并且独立的索引,这个索引可以被放置到集群中任何节点。
Elasticsearch底层是Lucene,但二者的概念具有一定的不同:
Lucene的索引是Elasticsearch的分片,
Elasticsearch的索引是分片的集合,
当Elasticsearch在索引中搜索的时候,它查询每个该属性的分片(一般在不同的节点上),最后将每个分片的结果合并成结果集。
(7)副本
分片的备份,一般存在于不同于主分片的节点上,是一种容灾机制,防止某个节点突然宕机,同时可提高系统的吞吐量和效率,es中的搜索可以在所有副本进行。
每个索引的主分片的数量实在创建索引的时候指定的,之后不能改变分片的数量,但可以任意动态改变副本的数量。
(8)分配
master节点,将分片分配给某个节点的过程,包括分配主分片或副本,如果是副本还包含从主分片复制数据的过程。
3,系统架构
假如在创建索引时,设置该索引包含3个分片和1个备份
- ES根据配置的集群名称cluster.name自动将节点加入集群中
- es自动将数据平均分配到不同的节点中,在有节点加入或移除集群时自动操作
- 主节点负责管理集群,新增、删除节点等
- 用户的请求可以发送到集群中的任何节点(包括主节点),每个节点都可以计算出文档所处的位置,并将请求直接转发到存放要请求的文档的节点,将最终结果返回给用户
六、ES集群实战
1,建立一个主分片为3,副本为1的索引
PUT index_1
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1
}
}
2,单节点集群
.
当集群中只有一个节点时,所有的主分片都被分配到主节点;同时集群健康值为yellow,3个副本分片都是Unassigned。
yellow:表示当前集群的全部主分片都正常运行,但副本分派你没有全部处于正常状态;
Unassigned:表示没有被分配到任何节点(在同一节点上即保存原始数据又保存副本时没有意义的)
此时,集群正常运行,但在硬件故障时又丢失数据的风险。
3,多节点集群
对比单节点的好处:
优点1:增加节点进行容灾
当集群中只有一个节点时,可能出现单点故障问题;
但,当我们在启动一个节点,只要和第一个节点具有同样的cluster.name配置,该新增节点就会自动发现集群并加入到其中,(根据配置的可连接到的单播主机列表——不同的机器上的节点建立集群时候才需要,使用单播为了放置节点无意加入集群) ,此时主分片和副本都将被分配
所有新加入的文档都会保存在主分片上,然年被并行的复制到对应的副本分片上
优点2:增加副本和节点提高性能
为了分散负载,集群会将分片尽可能平均的分散到不同的节点上,使每个节点的硬件资源被更少的分片共享,进而提高每个分片的性能。
主分片的数量在创建索引时确定,且后期不能更改;但副本的数量可随时增加。
读操作,可以同时被主分片或副本分片处理,所以副本越多,吞吐量越高。(假设新增的副本的同时新增了节点)
假如,某个索引具有三个分片,则这个索引容量不能超过三个分片所在节点的物理容量。
优点3:应对故障
当某一个节点宕机,若该节点时主节点,集群首先选举一个新的节点作为主节点,然后将其他一个节点的副本升级为主分片替换丢失的主节点。
如果宕机节点恢复,集群会将分片重新分配,尝试重用之前的分片,并从主分片复制发生了修改的数据文件。
4,路由计算
当索引一个文档时,文档会被存储到一个主分片上,通过一定的计算获得所在位置:
计算所在主分片号:
shard = hash(routing) % number_of_primary_shards
routing:默认是文档的_id,可以自定义,通过自定义routing可以使某些文档在同一个分片中
number_of_primary_shards:主分片的数量
所以,主分片的数量在创建索引时确定,因为如果数量变化后,之前计算的路由都会失效,文档丢失;
5,分片控制
用户可以发送请求到任意节点,每个节点都有能力处理任意请求。
每个节点都知道集群中任何一个文档的位置,并可以直接将请求转发到需要的节点上。
协调节点:接收了用户请求的节点。
用户发送请求时,最好轮询集群中的所有节点,可以起到负载均衡的效果。
Ⅰ、新建、删除文档
①客户端随意向任意一个节点N(协调节点)发送新建文档请求R
②接收到该请求R的节点N通过【路由计算】确定文档属于分片F,如果分片F在节点N2上,节点N将请求R转发到节点N2
③节点N2在主分片F上执行请求,如果成功,节点N2将请求并行转发到其他节点N3、N4、....(存放了主分片F的副本)上
④节点N3、N4、...执行被转发的请求R,如果执行成功,节点N3、N4、....向节点N2报告成功的消息
⑤ 如果节点N2收到了所有的副本分片的成功消息,节点N2向协调节点N报告成功的消息
⑥如果协调节点N收到N2的成功消息,协调节点N向客户端报告成功的消息
综上,客户端收到成功的响应时,文档已经在主分片和所有副本分片执行完成。
该过程可自定义:
在发送请求时可配置consisitency参数,可以设定的值如下:
- quorum,默认,规定数量的分片副本执行成功,就像用户反馈成功
- one,只要主分片执行成功,就向用户返回成功
- all,所有副本分片都执行成功,才向用户反馈成功
timeout:设置超时等待时间
Ⅱ,读操作
①客户端随意向某个节点N(协调节点)发送获取请求R
②节点N通过文档的_id【路由计算】出该文档属于的分片(包括主分片和副本分片)
es中的主分片和副本分片均同时用于检索,不是那种主分片挂了才让副本分片顶上去
③节点N将请求R转发到一个包含该文档的节点上N2
④节点N2将文档返回给节点N
⑤节点N将文档返回给客户端
在处理读取请求时,协调节点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡;
文档可能还没有复制到副本分片上,但主分片能成功返回文档
Ⅲ,更新文档
①客户端随意向某个节点N(协调节点)发送更新请求R
②节点N将 请求R进行【路由计算】判断主分片所在节点N2
③节点N将请求转发至节点N2
④节点N2从主分片检索文档,修改_source字段中的JSON,并且尝试重新索引主分片的文档,如果文档已经被另一个进程修改,会重试步骤④,超过retry_on_conflict次后放弃
⑤如果节点N2成功更新了文档,节点N2将新版本的文档并行转发到存放该主分片的副本分片的节点N3、N4、...,重新建立索引
主分片向副本分片转发的是整个完整文档的新版本,而不是更新请求R
⑥所有副本分片返回成功后,节点N2向协调节点返回成功,协调节点N向客户端返回成功
6,分片原理
Ⅰ、倒排索引
Ⅱ、文档搜索
倒排索引写入磁盘后是不可改变的,永远不会修改
优点:
- 不需要加锁,不存在多线程同时修改数据的情况
- 索引被读入内核的文件系统缓存后会一直驻留,提升性能
- 其他缓存会在索引的生命周期内一直有效,数据不会变就不会重构
缺点:
- 增加一个新文档时就需要重构整个索引
Ⅲ、动态更新索引
因为倒排索引是不可变的,那怎么更新索引使新增、修改或删除的文档加入索引呢?
通过增加新的补充索引反映新近的修改,而不是直接重写整个倒排索引,每个新补充的索引称作一个段,每一个段本身都是一个倒排索引;es从最早的倒排索引开始轮流查询每一个倒排索引,再对所有的结果进行合并。
当然,es需要知道所有的段;es通过一个文件记录所有的段,称为提交点。
具体执行流程如下:
- 新文档在内存中缓存
- 一定时间,缓存被提交:
- 将追加的倒排索引写入磁盘,形成一个段
- 同时将包含新段信息的提交点写入磁盘;
- 将数据缓存写入到磁盘
- 新段诞生,其中包含的文档可以被搜索
- 清空内存缓存,使其接收新的文档
①查询文档操作:
当查询请求时,所有的已知的段按照时间先后顺序比查询,词项统计会对所有段的结果进行聚合。
②删除文档操作:
因为段时不可变的,所以当删除文档时,进行了逻辑删除;
每个提交点都包含一个.del文件,在该文件中列出了这些被删除的文档的信息;
删除文档时,只是在.del文件中进行标记;
查询操作时,现实在段中查询,然后通过.del文件将被标记删除的文档过滤;
③更新文档操作:
将旧文档标记删除,新文档被索引到一个新的段中。
Ⅳ、近实时搜索
由Ⅲ中执行流程可知,一个新段被保存到物理磁盘之后,其中的文档才可被搜索,磁盘的IO操作变成了速度的限制,同时每次索引一个文档都需要去执行一个该操作就会造成性能问题。
所以,在内存索引缓冲区中暂存文档的段,先写入文件系统的缓存中使其可以被读取,之后再刷新到磁盘(既是:缓存去中的段已经被写入一个可被搜索的段中,但还没有进行提交),就极大的提高的性能。
默认情况下,每个分片每秒自动刷新一次,文档的变化不是立即对搜索可见,但会在一秒之内变的可见;(近实时的由来)
这个使新段包含的文档在未进行一次完成提交时便对搜索可见的操作叫做refresh
可以通过执行/索引名称/_refresh手动刷新,同时通过设置refresh_interval参数设置自动刷新的频率
Ⅴ,持久化数据
es通过事务日志(translog)记录对es进行的每一次操作;
具体执行流程:
- 一个文档被索引之后,会被添加到内存缓存区,并追加到translog中
- refresh使分片每秒刷新一次,使内存缓存区中的文档被写入到一个新段(这个段可被搜索),同时内存缓冲区会被清空
- 每隔一段时间,将提交点写入磁盘,且存放在文件系统缓存中的段通过fsync被刷新(flush)到硬盘
- 清除translog
1,translog持久化所有没有被刷新到磁盘的操作记录。
当es启动时,es会从磁盘中使用最后 一个提交点恢复已知的段,并重做translog中所有最后一次提交后发生的变更操作。
2,translog也被用来提供实时的CRUD
当用户通过ID查询、更新、删除一个文档,es会在从相应的段中检索之前,会先检查translog最近的变更(这个变更不就是最新的)。
Ⅵ、段合并
因为自动刷新流程每秒会创建一个新段,所以将导致段的数量很多;
缺点:
- 每一段都会消耗文件句柄、内存和cpu
- 每次搜索请求都会轮流检查每个段,段越多搜索就越慢
所以:
es会后台合并段,小段合并到大段,大段合并到大大段
段合并时,将已删除的文档从文件系统中清除,该操作会在索引和搜索时自动进行。
具体执行流程:
- 当索引时,refresh操作会创建新的段放入文件系统缓存供搜索使用
- 合并进程会选择一小部分大小相似的段,并在后台将其合并成大的段(不会中断索引和搜索操作)
- 合并结束之后,删除老的段,新的提交点和新的段被flush到磁盘
合并操作会消耗IO和CPU资源,es默认限制合并操作的使用的资源,以保证搜索的正常进行。
6,文档分析
- 在新增(索引)文档时,通过分词器将文档分割成适合倒排索引的一个个的词条
- 用内置分析器或调用分析器将词条统一化成标准格式以使它们更适合搜索
分析器操作:
- 分词器:将字符串分割成单个的词条,比如
- 字符过滤器:整理字符串,比如过滤掉HTML、将&转换成and
- Token过滤器:改变词条,比如将字母转化成全小写;删除词条,比如删除a、an、the等无意义的词条;增加词条,比如增加同义词,比如西红柿和番茄、go和run
分析器类型:
- 内置分析器(es自带的)
- 标准分析器:默认使用,根据Unicode定义的单词边界划分文本,删除标点,词条小写
- 简单分析器:从不是字母的地方分割文档,词条小写
- 空格分析器:从空格的地方分割文档
- 语言分析器:根据指定语言的特点划分,比如英语分析器可删除一些a、an、the无意义的词条、并将单词转换为词根形式,如apples变成apple、sets变成set
- 指定分析器:手动指定一些字符串的映射,可将an apple tree不进行分词而作为整体
- Ik分词器:es默认的分词器只能将中文分割成一个一个汉字,不能分词,所以用IK
- 自定义分析器
- 在搜索文档时,将查询的字符串做相同的分析过程,保证搜索的词条格式与索引中的词条格式保持一致
7,多线程更新文档不安全问题处理
安全问题:两个线程同时读取一个数据进行修改,对于一个线程的读取数据和更新数据的间隔,可能造成更新丢失;
es是分布式的,当文档新增、更新或删除时,新版本的文档必须复制到集群中的其他节点;
es也是异步和并发的,复制的请求会被并行发送,并且到达目的地的顺序是乱的。
在数据库领域内,经常采用两种方法确保并发更新时变更不丢失:
- 悲观锁:假定有变更就会有线程不安全问题,所以在读取数据之前先加锁,确保只有加了锁的线程才能修改这个数据;性能较低
- 乐观锁:假定有变更不会发生不安全问题,而是操作均可进行,只是如果数据在读写之前被修改就会使更新失败;性能较高
解决方案:
es通过乐观锁的方式解决这个不安全的问题;es中每个文档都有一个_version版本号(当文档被修改时版本号+1)来确保变更以正确顺序得到执行,如果旧版本的文档在新版本之后到达,则旧版本的文档会被忽略;
用户可以通过指定想要修改的文档的_version号,保证安全性,如果该版本已不是当前版本,用户的请求会失败;
新版本的es已不支持使用_version,而是用if_seq_no和if_primary_term
外部系统版本控制:
比如一个常见的应用场景:使用其他数据库作为主要的数据存储,而es只做数据检索;
此时,主数据库的所有变更都需要被复制到es,如果多个进程同时做这个数据同步,就会造成不安全的问题;
如果主数据库有一个版本号或可以作为版本号的字段(比如,时间戳),就可以在es中增加versioin_type=external到查询字符串的方式重用这些相同的版本号;
_version:es是检查请求中指定的版本号和当前_version是否相同
外部版本号:检查当前_version是否小于请求中指定的版本号,如果请求成功,外部的版本号作为文档的新_version进行存储。
八、影响Elasticsearch的性能因素
1,磁盘
Elasticsearch的基础是Lucene,所有的索引和文档数据是存储在本地的磁盘中,具体的路径可在ES的配置文件elasticsearch.yml中配置path.data,path.logs
Es对磁盘的依赖比较大,磁盘的吞吐量影响节点的性能
- SSD,固态硬盘
- RAID0,条带化RAID会提高磁盘IO
- es可通过path.data目录配置把数据条带化分配到多块硬盘上
- 不使用远程挂载的存储,远程传递影响性能
2,分片策略
一个分片的底层是Lucene索引,会消耗一定的文件句柄、内存、cpu
每一个搜索请求都需要命中索引中的每一个分片,多个分片在同一个节点上会竞争节点的资源
计算相关度的词项统计信息是基于分片的,如果分片过多,每一个都只有很少的数据会导致很低的相关度
所以:合理设置分片数
控制每个分片占用的硬盘容量不超过ES的最大JVM的堆空间,一般设置不超过32G,如果索引的容量在500G,分片大小设置16个左右
一般设置分片数不超过节点数的3倍,如果分片数过多,很可能导致一个节点存在多个分片
节点数<=主分片数*(副本数+1)
节点瞬间中断,默认情况,集群会等待一分钟来查看节点是否会重新加入,如果这个节点在此期间重新加入,重新加入的节点会保持其现有的分片数据,不会触发新的分片分配,这样减少ES在自动再平衡可用分片时带来的开销
通过修改参数delayed_timeout,可以延长再均衡的时间
3,路由选择
不带routing查询时,因为不知道要查询的数据具体再哪个分片上,所以请求到达协调节点后,协调节点将查询请求分发到每个分片上,协调节点将每个分片查询结果进行排序返回
带routing查询时,直接根据routing信息定位到某个分配查询,不需要查询所有的分片
4,偏向性优化
ES默认配置是综合了数据可靠性、写入速度、搜索实时性等因素
实际应用根据具体场景进行偏向性优化
- 加大Translog Flush,可以降低写 磁盘次数
- 增大Index Refresh间隔,目的是减少段合并的次数(应用于搜索的实效性要求不高)
- 调整Bulk线程池和队列,使其批量写入更多,视文档大小和服务器性能稳定
- 优化节点间的任务分布
- 优化Lucene层的索引建立,降低CPU和IO
九,性能测试
Elasticsearch压测工具——esrallyhttps://esrally.readthedocs.io/en/latest/index.html
官方压测报告https://elasticsearch-benchmarks.elastic.co/index.html
Elasticsearch压测实战——esrally实战教程https://www.bilibili.com/video/BV1Gs411E7VE/?spm_id_from=333.788.recommend_more_video.1
十,要点问题
1,es选举master的流程
es通过ZenDiscovery模块负责的,包含Ping和Unicast两部分;
Ping:节点之间通过这个RPC来发现彼此
Unicast:单播模块包含一个主机列表以控制哪些节点需要ping
- 每个节点在选举是把自己知道的所有节点根据nodeId字段排序将可以成为master的节点(node.master:true)排序,选择第一个节点暂时认为它是master节点
- 如果某个节点的票数达到一定值,且该节点自己选举了自己,该节点为master;否则重新选举
master节点主要负责管理集群、节点和索引,不负责文档级别的管理。
2,es搜索的流程
es的搜索分查询和拉取两个阶段:Query Then Fetch
- (Query开始:)协调节点接收到查询请求
- 协调节点广播查询到索引中每一个分片(主分片或副本)
- 每个分片在本地执行搜索并创建一个大小为from+size的配位文档的优先队列,并返回给协调节点
- 协调节点收取每个分片的优先队列,(即是:协调节点获得文档的ID和排序值)
- 协调节点合并文档的ID和排序值到自己的优先队列产生一个全局排序的结果列表
- (Fetch开始:)协调节点确定哪些文档需要被取回,并向相关分片发送GET请求
- 相关分片收到GET请求,返回文档给协调节点
- 协调节点将所有文档返回给客户端
更多推荐
所有评论(0)