ES高级篇
集群部署
集群的意思:就是将多个节点归为一体罢了,这个整体就有一个指定的名字了
window中部署集群 - 了解
把下载好的window版的ES中的data文件夹、logs文件夹下的所有的文件删掉,然后拷贝成三份,对文件重命名
1、修改node-1001节点的config/elasticsearch.yml配置文件。这个配置文件里面有原生的配置信息,感兴趣的可以查看,因为现在要做的配置信息都在原生的配置信息里,只是被注释掉了而已,当然:没兴趣的,直接全选删掉,然后做如下配置
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
# 集群名称 注意:是把多个节点归为一个整体,所以这个集群名字就是各节点归为一体之后的名字
# 因此:各个节点中这个集群名字也就要一样了
cluster.name: es-colony
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
# 节点名称 在一个集群中,这个名字要全局唯一
node.name: node-1001
# 是否有资格成为主机节点
node.master: true
# 是否是数据节点
node.data: true
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 当前节点的ip地址
network.host: 127.0.0.1
#
# Set a custom port for HTTP:
# 当前节点的端口号
http.port: 1001
# 当前节点的通讯端口( 监听端口 )
transport.tcp.port: 9301
# 跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
2、修改node-1002节点的config/elasticsearch.yml配置文件
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
# 集群名称 注意:是把多个节点归为一个整体,所以这个集群名字就是各节点归为一体之后的名字
# 因此:各个节点中这个集群名字也就要一样了
cluster.name: es-colony
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
# 节点名称 在一个集群中,这个名字要全局唯一
node.name: node-1002
# 是否是主机节点
node.master: true
# 是否是数据节点
node.data: true
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 当前节点的ip地址
network.host: 127.0.0.1
#
# Set a custom port for HTTP:
# 当前节点的端口号
http.port: 1002
# 当前节点的通讯端口( 监听端口 )
transport.tcp.port: 9302
# 当前节点不知道集群中另外节点是哪些,所以配置,让当前节点能够找到其他节点
discovery.seed_hosts: ["127.0.0.1:9301"]
# ping请求调用超时时间,但同时也是选主节点的delay time 延迟时间
discovery.zen.fd.ping_timeout: 1m
# 重试次数,防止GC[ 垃圾回收 ]节点不响应被剔除
discovery.zen.fd.ping_retries: 5
# 跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
3、修改node-1003节点的config/elasticsearch.yml配置文件
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
# 集群名称 注意:是把多个节点归为一个整体,所以这个集群名字就是各节点归为一体之后的名字
# 因此:各个节点中这个集群名字也就要一样了
cluster.name: es-colony
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
# 节点名称 在一个集群中,这个名字要全局唯一
node.name: node-1003
# 是否是主机节点
node.master: true
# 是否是数据节点
node.data: true
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 当前节点的ip地址
network.host: 127.0.0.1
#
# Set a custom port for HTTP:
# 当前节点的端口号
http.port: 1003
# 当前节点的通讯端口( 监听端口 )
transport.tcp.port: 9303
# 当前节点不知道集群中另外节点是哪些,所以配置,让当前节点能够找到其他节点
discovery.seed_hosts: ["127.0.0.1:9301","127.0.0.1:9302"]
# ping请求调用超时时间,但同时也是选主节点的delay time
discovery.zen.fd.ping_timeout: 1m
# 重试次数,防止GC[ 垃圾回收 ]节点不响应被剔除
discovery.zen.fd.ping_retries: 5
# 跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
依次启动1、2、3节点的bin/elasticsearch.bat即可启动集群
用postman测试集群
GET http://localhost:1001/_cluster/health
// 响应内容
{
"cluster_name": "es-colony",
"status": "green", // 重点查看位置 状态颜色
"timed_out": false,
"number_of_nodes": 3, // 重点查看位置 集群中的节点数量
"number_of_data_nodes": 3, // 重点查看位置 集群中的数据节点数量
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100.0
}
status字段颜色表示:当前集群在总体上是否工作正常。它的三种颜色含义如下:
- green: 所有的主分片和副本分片都正常运行
- yellow: 所有的主分片都正常运行,但不是所有的副本分片都正常运行
- red: 有主分片没能正常运行
附加内容:一些配置说明,下面的一些配置目前有些人可能并没有遇到,但是在这里留个印象吧,知道个大概和怎么去找就行了
官网地址: https://www.elastic.co/guide/en/elasticsearch/reference/current/modules.html
主节点 [ host区域 ] 和 数据节点 [ stale区域 ]:
cluster.name: elastics # 定义集群名称所有节点统一配置
node.name: es-0 # 节点名称自定义
node.master: true # 主节点,数据节点设置为 false
node.data: false # 数据节点设置为true
path.data: /home/es/data # 存储目录,可配置多个磁盘
path.logs: /home/es/logs # 日志文件路径
bootstrap.mlockall: true # 启动时锁定内存
network.publish_host: es-0 # 绑定网卡
network.bind_host: es-0 # 绑定网卡
http.port: 9200 # http端口
discovery.zen.ping.multicast.enabled: false # 禁用多播,跨网段不能用多播
discovery.zen.ping_timeout: 120s
discovery.zen.minimum_master_nodes: 2 # 至少要发现集群可做master的节点数,
client.transport.ping_timeout: 60s
discovery.zen.ping.unicast.hosts: ["es-0","es-1", "es-2","es-7","es-8","es-4","es-5","es-6"] # 集群自动发现
# fd 是 fault detection
# discovery.zen.ping_timeout 仅在加入或者选举 master 主节点的时候才起作用;
# discovery.zen.fd.ping_timeout 在稳定运行的集群中,master检测所有节点,以及节点检测 master是否畅通时长期有用
discovery.zen.fd.ping_timeout: 120s # 超时时间(根据实际情况调整)
discovery.zen.fd.ping_retries: 6 # 重试次数,防止GC[垃圾回收]节点不响应被剔除
discovery.zen.fd.ping_interval: 30s # 运行间隔
# 控制磁盘使用的低水位。默认为85%,意味着如果节点磁盘使用超过85%,则ES不允许在分配新的分片。当配置具体的大小如100MB时,表示如果磁盘空间小于100MB不允许分配分片
cluster.routing.allocation.disk.watermark.low: 100GB # 磁盘限额
# 控制磁盘使用的高水位。默认为90%,意味着如果磁盘空间使用高于90%时,ES将尝试分配分片到其他节点。上述两个配置可以使用API动态更新,ES每隔30s获取一次磁盘的使用信息,该值可以通过cluster.info.update.interval来设置
cluster.routing.allocation.disk.watermark.high: 50GB # 磁盘最低限额
node.zone: hot # 磁盘区域,分为hot和stale,做冷热分离
script.inline: true # 支持脚本
script.indexed: true
cluster.routing.allocation.same_shard.host: true # 一台机器部署多个节点时防止一个分配到一台机器上,宕机导致丢失数据
# 以下6行为设置thread_pool
threadpool.bulk.type: fixed
threadpool.bulk.size: 32
threadpool.bulk.queue_size: 100
threadpool.search.type: fixed
threadpool.search.size: 49
threadpool.search.queue_size: 10000
script.engine.groovy.inline.aggs: on
# 以下为配置慢查询和慢索引的时间
index.search.slowlog.threshold.query.warn: 20s
index.search.slowlog.threshold.query.info: 10s
index.search.slowlog.threshold.query.debug: 4s
index.search.slowlog.threshold.query.trace: 1s
index.search.slowlog.threshold.fetch.warn: 2s
index.search.slowlog.threshold.fetch.info: 1600ms
index.search.slowlog.threshold.fetch.debug: 500ms
index.search.slowlog.threshold.fetch.trace: 200ms
index.indexing.slowlog.threshold.index.warn: 20s
index.indexing.slowlog.threshold.index.info: 10s
index.indexing.slowlog.threshold.index.debug: 4s
index.indexing.slowlog.threshold.index.trace: 1s
# 索引库设置
indices.fielddata.cache.size: 20% # 索引库缓存时占用大小
indices.fielddata.cache.expire: "48h" # 索引库缓存的有效期
indices.cache.filter.size: 10% # 索引库缓存过滤占用大小
index.search.slowlog.level: WARN # 索引库搜索慢日志级别
Linux中部署ES
部署单机ES
1、准备工作
- 下载linux版的ES,自行百度进行下载,老规矩,我的版本是:7.8.0
- 将下载好的linux版ES放到自己的服务器中去
2、解压文件:
# 命令
tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz
3、对文件重命名:
# 命令
mv elasticsearch-7.8.0 es
4、创建用户:因为安全问题, Elasticsearch 不允许 root 用户直接运行,所以要创建新用户,在 root 用户中创建新用户
useradd es # 新增 es 用户
passwd es # 为 es 用户设置密码,输入此命令后,输入自己想设置的ES密码即可
userdel -r es # 如果错了,可以把用户删除了再重新加
chown -R es:es /opt/install/es # 文件授权 注意:/opt/install/es 改成自己的ES存放路径即可
5、修改 config/elasticsearch.yml 配置文件
# 在elasticsearch.yml文件末尾加入如下配置
cluster.name: elasticsearch
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
cluster.initial_master_nodes: ["node-1"]
6、修改 /etc/security/limits.conf 文件
# 命令
vim /etc/security/limits.conf
# 在文件末尾中增加下面内容 这个配置是:每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
7、修改 /etc/security/limits.d/20-nproc.conf 文件
# 命令
vim /etc/security/limits.d/20-nproc.conf
# 在文件末尾中增加下面内容
# 每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
# 操作系统级别对每个用户创建的进程数的限制
* hard nproc 4096
# 注: * 表示 Linux 所有用户名称
8、修改 /etc/sysctl.conf 文件
# 在文件中增加下面内容
# 一个进程可以拥有的 VMA(虚拟内存区域)的数量,默认值为 65536
vm.max_map_count=655360
9、重新加载文件
# 命令
sysctl -p
10、启动程序 : 准备进入坑中
cd /opt/install/es/
# 启动
bin/elasticsearch
# 后台启动
bin/elasticsearch -d
这个错误告知:不能用root用户,所以:切换到刚刚创建的es用户
# 命令
su es
然后再次启动程序,进入下一个坑
这个错误是因为:启动程序的时候会动态生成一些文件,这和ES没得关系,所以:需要切回到root用户,然后把文件权限再次刷新一下
# 切换到root用户
su root
# 切换到root用户之后,执行此命令即可
chown -R es:es /opt/install/es
# 再切换到es用户
su es
11、再次启动程序
吃鸡,这样linux中单机ES就部署成功了
不过啊,前面这种方式都是low的方式,有更简单的方式,就是使用docker容器来进行配置,简直不要太简单,虽然:使用docker容器来启动程序有弊端,如:MySQL就不建议放在docker容器中,因为:MySQL是不断地进行io操作,放到docker容器中,就会让io操作效率降低,而ES放到docker中也是同样的道理,但是:可以玩,因为:有些公司其实并没有在意docker的弊端,管他三七二十一扔到docker中
如果想要用docker容器进行ES配置,编写如下的docker-compose.yml文件
version: "3.1"
services:
elasticsearch:
# 注:此网站版本不全,可以直接用官网 elasticsearch:7.8.0
image: daocloud.io/library/elasticsearch:7.9.0
restart: always
container_name: elasticsearch
ports:
- 9200:9200
environment:
- Java_OPTS=--Xms256m -Xmx1024m
然后启动容器即可
注:使用docker安装需要保证自己的linux中安装了docker和docker-compose, 没有安装的话,教程链接:centos7安装docker和docker-compose
注:有些人可能还会被防火墙整一下,贫道的防火墙是关了的
# 暂时关闭防火墙
systemctl stop firewalld
# 永久关闭防火墙
systemctl enable firewalld.service # 打开防火墙永久性生效,重启后不会复原
systemctl disable firewalld.service # 关闭防火墙,永久性生效,重启后不会复原
12、测试是否成功
// 在浏览器或postman中输入以下指令均可
GET http://ip:9200/
浏览器访问不了,看看自己服务器开放9200端口没有,别搞这种扯犊子事啊
部署集群ES
可以选择和windows版的集群搭建一样
- 解压ES、重命名
- 复制几份ES文件夹
- 修改对应配置
更简单的方式
- 解压linux版的ES,重命名
- 分发节点
# 分发节点的步骤
xsync es-cluster # es-cluster为解压之后的es名字
# 注:xsync需要单独配置,配置过程如下:
# 1、安装rsync
yum -y install rsync
# 配置hosts 节点服务配置 此配置是在/etc/hosts中进行的
ip name # 如:192.168.0.100 hadoop
# 2、编写脚本 看自己的/usr/local/bin中是否有xsync文件 都看这里了,那就是第一次弄,肯定没有,所以
# 在/usr/local/bin中新建一个xsync文件 touch xsync
# 编辑内容如下:
#!/bin/sh
# 获取输入参数个数,如果没有参数,直接退出
pcount=$#
if((pcount==0)); then
echo no args...;
exit;
fi
# 获取文件名称
p1=$1
fname=`basename $p1`
echo fname=$fname
# 获取上级目录的绝对路径
pdir=`cd -P $(dirname $p1); pwd`
echo pdir=$pdir
# 获取当前用户名称
user=`whoami`
# 循环
for((host=1; host<=2; host++)); do
echo $pdir/$fname $user@slave$host:$pdir
echo ==================slave$host==================
rsync -rvl $pdir/$fname $user@slave$host:$pdir
done
# Note:这里的slave对应自己主机名,需要做相应修改。另外,for循环中的host的边界值由自己的主机编号决定
# 3、给新建的xsync文件授权
chmod a+x xsync
# 这样就配置成功了
1、同样的,root用户不能直接运行,所以创建用户
useradd es # 新增 es 用户
passwd es # 为 es 用户设置密码
userdel -r es # 如果错了,可以删除再加
chown -R es:es /opt/module/es # 给文件夹授权
2、编辑 ES文件夹的config/elasticsearch.yml文件,实现集群配置
# 加入如下配置,这些配置在网上都有,看不懂的网上比我更详细
# 集群名称
cluster.name: cluster-es
# 节点名称, 每个节点的名称不能重复
node.name: node-1
# ip 地址, 每个节点的地址不能重复
network.host: zixieqing-linux1 # 这个主机名字是在前面使用xsync异步分发时在hosts中配置的名字
# 是不是有资格主节点
node.master: true
node.data: true
http.port: 9200
# head 插件需要这打开这两个配置
http.cors.allow-origin: "*"
http.cors.enabled: true
http.max_content_length: 200mb
# es7.x 之后新增的配置,初始化一个新的集群时需要此配置来选举 master
cluster.initial_master_nodes: ["node-1"]
# es7.x 之后新增的配置,节点发现
discovery.seed_hosts: ["zixieqing-linux1:9300","zixieqing-linux2:9300","zixieqing-linux3:9300"]
gateway.recover_after_nodes: 2
network.tcp.keep_alive: true
network.tcp.no_delay: true
transport.tcp.compress: true
# 集群内同时启动的数据任务个数,默认是 2 个
cluster.routing.allocation.cluster_concurrent_rebalance: 16
# 添加或删除节点及负载均衡时并发恢复的线程个数,默认 4 个
cluster.routing.allocation.node_concurrent_recoveries: 16
# 初始化数据恢复时,并发恢复线程的个数,默认 4 个
cluster.routing.allocation.node_initial_primaries_recoveries: 16
3、修改 etc/security/limits.conf
# 在文件末尾中增加下面内容
es soft nofile 65536
es hard nofile 65536
4、修改 /etc/security/limits.d/20-nproc.conf
# 在文件末尾中增加下面内容
es soft nofile 65536
es hard nofile 65536
* hard nproc 4096
# 注: * 表示 Linux 所有用户名称
5、修改/etc/sysctl.conf
# 在文件中增加下面内容
vm.max_map_count=655360
6、加载文件
sysctl -p
7、启动软件
cd /opt/module/es-cluster
# 启动
bin/elasticsearch
# 后台启动
bin/elasticsearch -d
防火墙的问题前面已经提到了
8、集群验证
分片、副本、分配
分片 shards - 重要
这玩意儿就类似于关系型中的分表
在关系型中如果一个表的数据太大了,查询效率很低、响应很慢,所以就会采用大表拆小表,如:用户表,不可能和用户相关的啥子东西都放在一张表吧,这不是找事吗?因此:需要分表
相应的在ES中,也需要像上面这么干,如:存储100亿文档数据的索引,在单节点中没办法存储这么多的文档数据,所以需要进行切割,就是将这整个100亿文档数据切几刀,然后每一刀切分出来的每份数据就是一个分片 ( 索引 ),然后在切开的每份数据单独放在一个节点中,这样切开的所有文档数据合在一起就是一份完整的100亿数据,因此:这个的作用也是为了提高效率
创建一个索引的时候,可以指定想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上
分片有两方面的原因:
- 允许水平分割 / 扩展内容容量,水平扩充,负载均衡嘛
- 允许在分片之上进行分布式的、并行的操作,进而提高性能 / 吞吐量
注意: 当 Elasticsearch 在索引中搜索的时候, 它发送查询到每一个属于索引的分片,然后合并每个分片的结果到一个全局的结果集中
副本 Replicas - 重要
这不是游戏中的刷副本的那个副本啊。是指:分片的复制品
失败是常有的事嘛,所以:在ES中也会失败呀,可能因为网络、也可能因此其他鬼原因就导致失败了,此时不就需要一种故障转移机制吗,也就是 创建分片的一份或多份拷贝,这些拷贝就叫做复制分片( 副本 )
副本( 复制分片 )之所以重要,有两个原因:
- 在分片 / 节点失败的情况下,提供了高可用性。因为这个原因,复制分片不与原 / 主要( original / primary )分片置于同一节点上是非常重要的
- 扩展搜索量 / 吞吐量,因为搜索可以在所有的副本上并行运行
多说一嘴啊,分片和副本这两个不就是配套了吗,分片是切割数据,放在不同的节点中( 服务中 );副本是以防服务宕掉了,从而丢失数据,进而把分片拷贝了任意份。这个像什么?不就是主备吗( 我说的是主备,不是主从啊 ,这两个有区别的,主从是主机具有写操作,从机具有读操作;而主备是主机具有读写操作,而备机只有读操作 ,不一样的啊 )
有个细节需要注意,在ES中,分片和副本不是在同一台服务器中,是分开的,如:分片P1在节点1中,那么副本R1就不能在节点1中,而是其他服务中,不然服务宕掉了,那数据不就全丢了吗
分配 Allocation
前面讲到了分片和副本,对照Redis中的主备来看了,那么对照Redis的主从来看呢?主机宕掉了怎么重新选一个主机?Redis中是加了一个哨兵模式,从而达到的。那么在ES中哪个是主节点、哪个是从节点、分片怎么去分的?就是利用了分配
所谓的分配是指: 将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。注意:这个过程是由 master 节点完成的,和Redis还是有点不一样的啊
既然都说了这么多,那就再来一个ES的系统架构吧
其中,P表示分片、R表示副本
默认情况下,分片和副本都是1,根据需要可以改变
单节点集群
这里为了方便就使用window版做演示,就不再linux中演示了
1、打开前面玩的window版集群的1节点
2、创建索引 把这个索引切成3份( 切片 )、每份拷贝1份副本
PUT http://127.0.0.1:1001/users
// 请求体内容
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
3、开始安装head插件,这就是一个可视化界面而已,后续还会用Kibana
还有一种es的集群监控的方式是使用cerebro,官网地址:https://github.com/lmenezes/cerebro 下载解压,运行 bin/cerebro.bat 即可
自行到官网下载elasticsearch-head-master,这是用Vue写的。启动效果如下:
访问上图中的地址即可,但是:这个端口是9100,而我们的ES是9200端口,所以9100访问9200是跨越的,因此:需要对ES设置跨越问题,而这个问题在第一次玩ES集群时就配置了的
head打开之后就是下图中的样子
head链接ES之后就是下图的样子
三种颜色再巩固一下:
- green:所有的主分片和副本分片都正常运行
- yellow:所有的主分片都正常运行,但不是所有的副本分片都正常运行
- red:有主分片没能正常运行
但是:上述的单节点集群有问题,就是将分片和副本都放在一个节点( node-1001 )中了,这样会导致前面说的服务宕掉,数据就没了,做的副本就是无用功。要解决就要引入接下来的内容了
故障转移
所谓的故障转移指的就是:
- 若新开节点,那么ES就会将原有数据重新分配到所有节点上
- 若是节点挂了,那么ES就会将挂了的节点的数据进行拷贝到另外好的节点中。要是挂的正好是master主节点,那么还有多一个选主过程,然后再分配数据————这种情况也可以称之为“应对故障”
1、新开节点的情况: 启动node-1002节点
可能由于玩windows版时的一些数据导致node-1002节点启动不了,所以删掉data文件夹和logs文件夹下的东西即可
刷新head可视化页面:
恢复正常
水平扩容 / 负载均衡
1、启动node-1003节点
刷新head页面
对照前面单节点集群来看,数据就被很好的分开了,这样性能不就提上来了吗
但是:如果相应继续扩容呢?即:超过6份数据( 6个节点,前面讲到过索引切分之后,每一份又是单独的索引、副本也算节点 ),那怎么办?
- 首先知道一个点:主分片的数目在索引创建时就已经确定下来了的,这个我们没法改变,这个数目定义了这个索引能够存储的最大数据量( 实际大小取决于你的数据、硬件和使用场景 )
- 但是,读操作——搜索和返回数据——可以同时被主分片 或 副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量
- 因此:增加副本分片的数量即可
put http://127.0.0.1:1001/users/_settings
// 请求体内容
{
"number_of_replicas": 2
}
刷新head页面
应对故障
应对的是什么故障?前面一直在说:服务宕掉了嘛
1、关掉node-1001节点( 主节点 )
2、刷新head页面
但是注意啊:yellow虽然不正常,但是不影响操作啊,就像你看了yellow之后,影响你正常发挥吗?只是可能有点虚脱而已,所以对于ES来说也是可以正常查询数据的,只是:效率降低了而已嘛( 主节点和3个分片都在的嘛 )
3、解决这种问题: 开启新节点(把node-1001节点启动。此时它就不是主节点了 ,当成新节点了)
这就会报错: unless existing master is discovered 找不到主节点( 对于启动的集群来说,它现在是新节点],因此:需要做一下配置修改( node-1001的 config/ElasticSearch.yml )
discovery.seed_hosts: ["127.0.0.1:9302","127.0.0.1:9303"]
保存开启node-1001节点即可
4、刷新head页面
故障恢复了,所以:这也告知一个问题,配置集群时,最好在每个节点的配置文件中都加上上述的配置,从而节点宕掉之后,重启节点即可( 不然每次改不得烦死 ),注意:ES版本不一样,这个配置方法不一样的,6.x的版本是用cluster.initial_master_nodes: 来进行配置的
路由计算和分片控制理论
路由计算
路由、路由,这个东西太熟悉了,在Vue中就见过路由router了( 用来转发和重定向的嘛 )
那在ES中的路由计算又是怎么回事?这个主要针对的是ES集群中的存数据,试想:你知道你存的数据是在哪个节点 / 哪个主分片中吗( 副本是拷贝的主分片,所以主分片才是核心 )?
- 当然知道啊,就是那几个节点中的任意一个嘛。娘希匹~这样的骚回答好吗?其实这是由一个公式来决定的
shard = hash( routing ) % number_of_primary_shards
routing 是一个任意值,默认是文档的_id,也可以自定义
number_of_primary_shards 表示主分片的数量,如前面切分为了3份
hash() 是一个hash函数
这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么之前所有路由的值都会无效,文档也再也找不到了
分片控制
既然有了存数据的问题,那当然就有取数据的问题了。
请问:在ES集群中,取数据时,ES怎么知道去哪个节点中取数据( 假如在3节点中,你去1节点中,可以取到吗?),因此:来了分片控制
负载均衡,轮询嘛。所以这里有个小知识点,就是:协调节点 coordinating node
,我们可以发送请求到集群中的任一节点,每个节点都有能力处理任意请求,每个节点都知道集群中任一文档位置,这就是分片控制,而我们发送请求的那个节点就是:协调节点,它会去帮我们找到我们要的数据在哪里
综合前面的知识就可以得到:
-
所谓的分片就是:将索引切分成任意份嘛,然后得到的每一份数据都是一个单独的索引
-
分片完成后,我们存数据时,存到哪个节点上,就是通过 shard = hash( routing ) % number_of_primary_shards 得到的
-
而我们查询数据时,ES怎么知道我们要找的数据在哪个节点上,就是通过协调节点做到的,它会去找到和数据相关的“所有节点”,从而轮询,然后进行数据整合,通过协调节点返回给客户端。因此最后的结果可能是从主分片上得到的,也可能是从副本上得到的,就看最后轮询到的是哪个节点罢了
集群下的数据写流程
新建、删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片
整个流程也很简单
- 客户端请求任意节点(协调节点)
- 通过路由计算,协调节点把请求转向指定的节点
- 转向的节点的主分片保存数据
- 主节点再将数据转发给副本保存
- 副本给主节点反馈保存结果
- 主节点给客户端反馈保存结果
- 客户端收到反馈结果
但是:从图中就可以看出来,这套流程完了,才可以做其他事( 如:才可以去查询数据 ),那我为什么不可以异步呢?就是我只要保证到了哪一个步骤之后,就可以进行数据查询,所以:这里有两个小东西需要了解
在进行写数据时,我们做个小小的配置,这就是接下来的两个小节内容
一致性 consistency
这玩意就是为了和读数据搭配起来嘛,写入和读取保证数据的一致性呗
这玩意儿可以设定的值如下:
- one :只要主分片状态 ok 就允许执行读操作,这种写入速度快,但不能保证读到最新的更改
- all:这是强一致性,必须要主分片和所有副本分片的状态没问题才允许执行写操作
- quorum:这是ES的默认值。即大多数的分片副本状态没问题就允许执行写操作。这是折中的方法,write的时候,W>N/2,即参与写入操作的节点数W,必须超过副本节点数N的一半,在这个默认情况下,ES是怎么判定你的分片数量的,就一个公式:
int((primary + number_of_replicas) / 2) + 1
primary 指的是创建的索引数量
number_of_replicas 是指的在索引设置中设定的副本分片数
如果你的索引设置中指定了当前索引拥有3个副本分片
那规定数量的计算结果为:int(1 primary + 3 replicas) / 2) + 1 = 3,
如果此时你只启动两个节点,那么处于活跃状态的分片副本数量就达不到规定数量,
也因此你将无法索引和删除任何文档
- realtime request:就是从translog里头读,可以保证是最新的。但是注意:get是最新的,但是检索等其他方法不是( 如果需要搜索出来也是最新的,需要refresh,这个会刷新该shard但不是整个index,因此如果read请求分发到repliac shard,那么可能读到的不是最新的数据,这个时候就需要指定preference=_primar y)
超时 timeout
如果没有足够的副本分片会发生什么?Elasticsearch 会等待,希望更多的分片出现。默认情况下,它最多等待 1 分钟。 如果你需要,你可以使用timeout参数使它更早终止,单位是毫秒,如:100就是100毫秒
新索引默认有1个副本分片,这意味着为满足规定数量应该需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当number_of_replicas 大于1的时候,规定数量才会执行
上面的理论不理解、或者感觉枯燥也没事儿,后面慢慢的就理解了,这里只是打个预防针、了解理论罢了
集群下的数据读流程
有写流程,那肯定也要说一下读流程嘛,其实和写流程很像,只是变了那么一丢丢而已
流程如下:
- 客户端发送请求到任意节点( 协调节点 )
- 这里不同,此时协调节点会做两件事:1、通过路由计算得到分片位置,2、还会把当前查询的数据所在的另外节点也找到( 如:副本 )
- 为了负载均衡( 可能某个节点中的访问量很大嘛,减少一下压力咯 ),所以就会对查出来的所有节点做轮询操作,从而找到想要的数据( 因此:你想要的数据在主节点中有、副本中也有,但是:给你的数据可能是主节点中的,也可能是副本中的 ———— 看轮询到的是哪个节点中的 )
- 节点反馈结果
- 客户端收到反馈结果
这里有个注意点: 在文档( 数据 )被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的
集群下的更新操作流程
更新操作流程
- 客户端向node 1发送更新请求
- 它将请求转发到主分片所在的node 3
- node 3从主分片检索文档,修改_source字段中的JSON,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改,它会重试步骤3 ,超过retry_on_conflict次后放弃
- 如果 node 3成功地更新文档,它将新版本的文档并行转发到node 1和 node 2上的副本分片,重新建立索引。一旦所有副本分片都返回成功,node 3向协调节点也返回成功,协调节点向客户端返回成功
当然:上面有个漏洞,就是万一在另一个进程修改之后,当前修改进程又去修改了,那要是把原有的数据修改了呢?这不就成关系型数据库中的“不可重复读”了吗?
- 不会的。因为当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。注意点:这些更改将会“异步转发”到副本分片,并且不能保证它们以相同的顺序到达。 如果 ES 仅转发更改请求,则可能以错误的顺序应用更改,导致得到的是损坏的文档
批量更新操作流程
这个其实更容易理解,单文档更新懂了,那多文档更新就懂了嘛,多文档就请求拆分呗
所谓的多文档更新就是:将整个多文档请求分解成每个分片的文档请求,并且将这些请求并行转发到每个参与节点。协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端
原理图的话:我就在网上偷一张了
其实mget 和 bulk API的模式就类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中
用单个 mget 请求取回多个文档所需的步骤顺序:
- 客户端向 Node 1 发送 mget 请求
- Node 1为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复,Node 1 构建响应并将其返回给客户端。可以对docs数组中每个文档设置routing参数
- bulk API, 允许在单个批量请求中执行多个创建、索引、删除和更新请求
bulk API 按如下步骤顺序执行:
- 客户端向Node 1 发送 bulk请求
- Node 1为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机
- 主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端
文档搜索
不可变的倒排索引
以前的全文检索是将整个文档集合弄成一个倒排索引,然后存入磁盘中,当要建立新的索引时,只要新的索引准备就绪之后,旧的索引就会被替换掉,这样最近的文档数据变化就可以被检索到
而索引一旦被存入到磁盘就是不可变的( 永远都可以修改 ),而这样做有如下的好处:
- 只要索引被读入到内存中了,由于其不变性,所以就会一直留在内存中( 只要空间足够 ),从而当我们做“读操作”时,请求就会进入内存中去,而不会去磁盘中,这样就减小开销,提高效率了
- 索引放到内存中之后,是可以进行压缩的,这样做之后,也就可以节约空间了
- 放到内存中后,是不需要锁的,如果自己的索引是长期不用更新的,那么就不用怕多进程同时修改它的情况了
当然:这种不可变的倒排索引有好处,那就肯定有坏处了
- 不可变,不可修改嘛,这就是最大的坏处,当要重定一个索引能够被检索时,就需要重新把整个索引构建一下,这样的话,就会导致索引的数据量很大( 数据量大小有限制了 ),同时要更新索引,那么这频率就会降低了
- 这就好比是什么呢?关系型中的表,一张大表检索数据、更新数据效率高不高?肯定不高,所以延伸出了:可变索引
可变的倒排索引
又想保留不可变性,又想能够实现倒排索引的更新,咋办?
- 就搞出了
补充索引
,所谓的补充索引:有点类似于日志这个玩意儿,就是重建一个索引,然后用来记录最近指定一段时间内的索引中文档数据的更新。这样更新的索引数据就记录在补充索引中了,然后检索数据时,直接找补充索引即可,这样检索时不再重写整个倒排索引了,这有点类似于关系型中的拆表,大表拆小表嘛,但是啊:每一份补充索引都是一份单独的索引啊,这又和分片很像,可是:查询时是对这些补充索引进行轮询,然后再对结果进行合并,从而得到最终的结果,这和前面说过的读流程中说明的协调节点挂上钩了
这里还需要了解一个配套的按段搜索
,玩过 Lucene 的可能听过。按段,每段也就可以理解为:补充索引,它的流程其实也很简单:
- 新文档被收集到内存索引缓存
- 不时地提交缓存
- 一个新的段,一个追加的倒排索引,被写入磁盘
- 一个新的包含新段名字的提交点被写入磁盘
- 磁盘进行同步,所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件
- 内存缓存被清空,等待接收新的文档
- 新的段被开启,让它包含的文档可见,以被搜索
一样的,段在查询的时候,也是轮询的啊,然后把查询结果合并从而得到的最终结果
另外就是涉及到删除的事情,段本身也是不可变的, 既不能把文档从旧的段中移除,也不能修改旧的段来进行文档的更新,而删除是因为:是段在每个提交点时有一个.del文件,这个文件就是一个删除的标志文件,要删除哪些数据,就对该数据做了一个标记,从而下一次查询的时候就过滤掉被标记的这些段,从而就无法查到了,这叫逻辑删除( 当然:这就会导致倒排索引越积越多,再查询时。轮询来查数据也会影响效率 ),所以也有物理删除,它是把段进行合并,这样就舍弃掉被删除标记的段了,从而最后刷新到磁盘中去的就是最新的数据( 就是去掉删除之后的 ,别忘了前面整的段的流程啊,不是白写的 )
近实时搜索、文档刷新、文档刷写、文档合并
ES的最大好处就是实时数据全文检索
但是:ES这个玩意儿并不是真的实时的,而是近实时 / 准实时
原因就是:ES的数据搜索是分段搜索,最新的数据在最新的段中(每一个段又是一个倒排索引),只有最新的段刷新到磁盘中之后,ES才可以进行数据检索,这样的话,磁盘的IO性能就会极大的影响ES的查询效率,而ES的目的就是为了:快速的、准确的获取到我们想要的数据,因此:降低数据查询处理的延迟就very 重要了,而ES对这方面做了什么操作?
- 就是搞的一主多副的方式(一个主分片,多个副本分片),这虽然就是一句话概括了,但是:里面的门道却不是那么简单的
首先来看一下主副操作
但是:这种去找寻节点的过程想都想得到会造成延时,而延时 = 主分片延时 + 主分片拷贝数据给副本的延时
而且并不是这样就算完了,前面提到了N多次的分段、刷新到磁盘还没上堂呢,所以接着看
但是:在flush到磁盘中的时候,万一断电了呢?或者其他原因导致出问题了,那最后数据不就没有flush到磁盘吗
因此:其实还有一步操作,把数据保存到另外一个文件中去
数据放到磁盘中之后,translog中的数据就会清空
同时更新到磁盘之后,用户就可以进行搜索数据了
注意:这里要区分一下,数据库中是先更新到log中,然后再更新到内存中,而ES是反着的,是先更新到Segment( 可以直接认为是内存,因它本身就在内存中 ),再更新到log中
可是啊,还是有问题,flush刷写到磁盘是很耗性能的,假如:不断进行更新呢?这样不断进行IO操作,性能好吗?也不行,因此:继续改造(没有什么是加一层解决不了的,一层不够,那就再来一层)
加入了缓存之后,这缓存里面的数据是可以直接用来搜索的,这样就不用等到flush到磁盘之后,才可以搜索了,这大大的提高了性能,而flush到磁盘,只要时间到了,让它自个儿慢慢flush就可以了,上面这个流程也叫:持久化 / 持久化变更
写入和打开一个新段的轻量的过程叫做refresh。默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 ES是近实时搜索:文档的变化并不是立即对搜索可见,但会在一秒之内变为可见
刷新是1s以内完成的,这是有时间间隙的,所以会造成:搜索一个文档时,可能并没有搜索到,因此:解决办法就是使用refresh API刷新一下即可
但是这样也伴随一个问题:虽然这种从内存刷新到缓存中看起来不错,但是还是有性能开销的。并不是所有的情况都需要refresh的, 假如:是在索引日志文件呢?去refresh干嘛,浪费性能而已,所以此时:你要的是查询速度,而不是近实时搜索,因此:可以通过一个配置来进行改动,从而降低每个索引的刷新频率
http://ip:port/index_name/_settings // 请求方式:put
// 请求体内容
{
"settings": {
"refresh_interval": "60s"
}
}
refresh_interval 可以在既存索引上进行动态更新。在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来。虽然有点麻烦,但是按照ES这个玩意儿来说,确实需要这么做比较好
// 关闭自动刷新
http://ip:port/users/_settings // 请求方式:put
// 请求体内容
{
"refresh_interval": -1
}
// 每一秒刷新
http://ip:port/users/_settings // 请求方式:put
// 请求体内容
{
"refresh_interval": "1s"
}
另外:不断进行更新就会导致很多的段出现(在内存刷写到磁盘那里,会造成很多的磁盘文件),因此:在哪里利用了文档合并的功能(也就是段的能力,合并文档,从而让刷写到磁盘中的文档变成一份)
文档分析
试想:我们在浏览器中,输入一条信息,如:搜索“博客园紫邪情”,为什么连“博客园也搜索出来了?我要的是不是这个结果涩”
这就是全文检索,就是ES干的事情( 过滤数据、检索嘛 ),但是:它做了哪些操作呢?
在ES中有一个文档分析的过程,文档分析的过程也很简单:
- 将文本拆成适合于倒排索引的独立的词条,然后把这些词条统一变为一个标准格式,从而使文本具有“可搜索性”。 而这个文档分析的过程在ES是由一个叫做“分析器 analyzer”的东西来做的,这个分析器里面做了三个步骤
- 字符过滤器:就是用来处理一些字符的嘛,像什么将 & 变为 and 啊、去掉HTML元素啊之类的。它是文本字符串在经过分词之前的一个步骤,文本字符串是按文本顺序经过每个字符串过滤器从而处理字符串
- 分词器:见名知意,就是用来分词的,也就是将字符串拆分成词条( 字 / 词组 ),这一步和Java中String的split()一样的,通过指定的要求,把内容进行拆分,如:空格、标点符号
- Token过滤器:这个玩意儿的作用就是 词条经过每个Token过滤器,从而对数据再次进行筛选,如:字母大写变小写、去掉一些不重要的词条内容、添加一些词条( 如:同义词 )
上述的内容不理解没事,待会儿会用IK中文分词器来演示,从而能够更直观的看到效果
在ES中,有提供好的内置分析器、我们也可以自定义、当然还有就是前面说的IK分词器也可以做到。而这里重点需要了解的就是IK中文分词器
在演示在前,先玩kibana吧,原本打算放在后面的,但是越早熟悉越好嘛,所以先把kibana说明了
kibana
1、去Elastic 官网 下载kibana。 但是需要注意:kibana的版本必须和ES的版本一致
下载好了kibana之后,解压到自己想要的目录( 注:加压会有点久,因为是用Vue写的,里面有模块module 要是加压快的话,可能还下错了 ),然后点击bin/kibana.bat即可启动kibana、第一次进去会有一个选择页面,add … / explore,选择explore就可以了,进去之后就是如下界面:
这是英文版,要是没玩过大数据的话,那么里面的一些专业名词根据英文来看根本不知道
所以:汉化吧。 kibana本身就提供得有汉化的功能,只需要改动一个配置即可。就是一个i1bn配置而已
进入config/kibana.yml,刷到最底部
加上上面的信息,然后重启kibana就可以了
但是:个人建议,先汉化一段时间,等熟悉哪些名词了,然后再转成英文 ,总之最后建议用英文,一是增加英文词汇量,二是熟悉英文专业词
kibana遵循的是rest风格( get、put、delete、post..... ),具体用法接下来玩分析器和后面都会慢慢熟悉
内置分析器
标准分析器 standard
这是根据Unicode定义的单词边界来划分文本,将字母转成小写,去掉大部分的标点符号,从而得到的各种语言的最常用文本选择,另外:这是ES的默认分析器。 接下来演示一下
1、启动ES和kibana,打开控制台
2、编写指令
GET _analyze
{
"analyzer": "standard", // analyzer 分析器 standard 标准分析器
"text": "my name is ZiXieQing" // text 文本标识 my name is ZiXieQing 自定义的文本内容
}
// 响应内容
{
"tokens" : [
{
"token" : "my", // 分词之后的词条
"start_offset" : 0,
"end_offset" : 2, // start和end叫偏移量
"type" : "<ALPHANUM>",
"position" : 0 // 当前词条在整个文本中所处的位置
},
{
"token" : "name",
"start_offset" : 3,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "is",
"start_offset" : 8,
"end_offset" : 10,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "zixieqing",
"start_offset" : 11,
"end_offset" : 20,
"type" : "<ALPHANUM>",
"position" : 3
}
]
}
从上图可以看出:所谓标准分析器是将文本通过标点符号来分词的( 空格、逗号... ,不信可以自行利用这些标点测试一下,观察右边分词的结果 ),同时大写转小写
简单分析器 simple
简单分析器是“按非字母的字符分词,例如:数字、标点符号、特殊字符等,会去掉非字母的词,大写字母统一转换成小写”
空格分析器 whitespace
是简单按照空格进行分词,相当于按照空格split了一下,大写字母不会转换成小写
去词分析器 stop
会去掉无意义的词(此无意义是指语气助词等修饰性词,补语文:语气词是疑问语气、祈使语气、感叹语气、肯定语气和停顿语气),例如:the、a、an 、this等,大写字母统一转换成小写
不拆分分析器 keyword
就是将整个文本当作一个词
IK中文分词器
来个实验:
它把我的名字进行拆分了,这不是我想要的,我想要的“紫邪情”应该是一个完整的词,同样道理:想要特定的词汇,如:ID号、用户名....,这些不应该拆分,而ES内置分析器并不能做到,所以需要IK中文分词器(专门用来处理中文的 )
1、下载IK分词器: https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.8.0
注意:版本对应关系, 还是和ES版本对应,https://github.com/medcl/elasticsearch-analysis-ik 这个链接进去之后有详细的版本对应
2、把IK解压到ES/plugins中去。 如我的:
3、重启ES即可。 kibana开着的话,也要关了重启 ,注意观察:重启时会有一个IK加载过程
经过如上的操作之后,IK中文分词器就配置成功了,接下来就来体验一下( 启动ES和kibana ),主要是为了了解IK中的另外两种分词方式:ik_max_word 和 ik_smart
-
ik_max_word 是细粒度的分词,就是:穷尽词汇的各种组成。 4个字是一个词,继续看3个字是不是一个词,再看2个字又是不是一个词,以此穷尽..........
-
ik_smart 是粗粒度的分词。 如:那个叼毛也是一个程序员,就先看整句话是不是一个词(length = 11),不是的话,就看length-1是不是一个词.....,如果某个长度是一个词了,那么这长度内的内容就不看了,继续看其他的是不是一个词,如“那个"是一个词,那就看后面的内容,继续length、length-1、length-2........
回到前面的问题,“紫邪情”是名字,我不想让它分词,怎么做?上面哪些分词都是在一个“词典”中,所以我们自己搞一个词典即可
1、创建一个.dic文件 dic就是dictionary词典的简写
2、在创建的dic文件中添加不分词的词组,保存
3、把自定义的词典放到ik中去,保存
4、重启ES和kibana
5、测试
可见,现在就把“紫邪情”组成词组不拆分了,前面玩的kibana汉化是怎么做的?和这个的原理差不多
多玩几次kibana
在第一篇高级篇中我边说过:kibana重要,只是经过前面这些介绍了使用之后,并不算熟悉,因此:多玩几次吧
另外:就是前面说的kibana遵循rest风格,在ES中是怎么玩的?总结下来其实就下面这些,要上手简单得很,但理论却是一直弄到现在
现在用kibana来演示几个,其他在postman中怎么弄,换一下即可( 其实不建议用postman测试,专业的人做专业的事,kibana才是我们后端玩的 )
1、创建索引
2、查看索引
3、创建文档( 随机id值,想要自定义id值,在后面加上即可 )
4、删除索引
5、创建文档( 自定义id )
6、查看文档( 通过id查询 )
7、修改文档( 局部修改 )
验证一下:
8、建字段类型
其他的也是差不多的玩法,在基础篇中怎么玩,稍微变一下就是kibana的玩法了
拼音分词器/自定义分析器
官网:https://github.com/medcl/elasticsearch-analysis-pinyin
安装和IK分词器一样
- 下载
- 上传解压
- 重启es
测试拼音分词器
由上可知,伴随2个问题:
- 只进行了拼音分词,汉字分词不见了
- 只采用拼音分词会出现一种情况:同音字,如“狮子”,“虱子”,这样的话明明想搜索的是“狮子”,结果“虱子”也出来了,所以这种搜索效果不好
因此:需要定制,让汉字分词出现,同时搜索时使用的汉字是什么就是什么,别弄同音字
要完成上面的需求,就需要结合前面的文档分析的过程
在ES中有一个文档分析的过程,文档分析的过程也很简单:
- 将文本拆成适合于倒排索引的独立的词条,然后把这些词条统一变为一个标准格式,从而使文本具有“可搜索性”。 而这个文档分析的过程在ES是由一个叫做“分析器 analyzer”的东西来做的,这个分析器里面做了三个步骤
- 字符过滤器(character filters):就是用来处理一些字符的嘛,像什么将 & 变为 and 啊、去掉HTML元素啊之类的。它是文本字符串在经过分词之前的一个步骤,文本字符串是按文本顺序经过每个字符串过滤器从而处理字符串
- 分词器(tokenizer):见名知意,就是用来分词的,也就是将字符串拆分成词条( 字 / 词组 ),这一步和Java中String的split()一样的,通过指定的要求,把内容进行拆分,如:空格、标点符号
- Token过滤器(tokenizer filter):这个玩意儿的作用就是 词条经过每个Token过滤器,从而对数据再次进行筛选,如:字母大写变小写、去掉一些不重要的词条内容、添加一些词条( 如:同义词 )
举例理解:character filters、tokenizer、tokenizer filter)
因此现在自定义分词器就变成如下的样子:
注: 是建立索引时自定义分词器,即自定义的分词器只对当前索引库有效
PUT /test
{
"settings": {
"analysis": {
"analyzer": { // 自定义分词器
"my_analyzer": { // 分词器名称
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": { // 自定义tokenizer filter
"py": { // 过滤器名称
"type": "pinyin", // 过滤器类型,这里是pinyin,这些参数都在 拼音分词器官网有
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer", // 指明在索引时使用的分词器
"search_analyzer": "ik_smart" // 指明搜索时使用的分词器
}
}
}
}
使用自定义分词器:
文档控制-了解
所谓的文档控制就是:不断更新的情况,试想:多进程不断去更新文档,会造成什么情况?会把其他人更新过的文档进行覆盖更新了,而ES是怎么解决这个问题的?
就是弄了一个锁来实现的,和Redis一样,也是用的乐观锁来实现的,这个其实没什么好说的,只需要看一下就知道了
上图中·的三个字段就和锁挂钩的,version,版本号嘛,每次更新都会有一个版本号,这样就解决了多进程修改从而造成的文档冲突了( 必须等到一个进程更新完了,另一个进程才可以更新 ),当然:需要注意旧版本的ES在请求中加上version即可,但是新版本的ES需要使用 if"_seq_no" 和"if_primary_term" 来达到version的效果
ES的优化
ES的所有索引和文档数据都是存储在本地的磁盘中的,所以:磁盘能处理的吞吐量越大,节点就越稳定
要修改的话,是在config/elasticsearch.yml中改动
硬件方面
1、选用固态硬盘( 即:SSD ),它比机械硬盘的好是因为:机械硬盘是通过旋转马达的驱动来进行的,所以这就会造成发热、磨损,就会影响ES的效率,而SSD是使用芯片式的闪存来存储数据的,性能比机械硬盘好得多
2、使用RAID 0 ( 独立磁盘冗余阵列 ),它是把连续的数据分散到多个磁盘上存取,这样,系统有数据请求就可以被多个磁盘并行的执行,每个磁盘执行属于它自己的那部分数据请求。这种数据上的并行操作可以充分利用总线的带宽,显著提高磁盘整体存取性能
3、由上面的RAID 0可以联想到另外一个解决方式:使用多块硬盘,也就可以达到同样的效果了( 有钱就行 ),是通过path data目录配置把数据条分配到这些磁盘上面
4、不要把ES挂载到远程上去存储
分片策略
分片和副本不是乱分配的!分片处在不同节点还可以( 前提是节点中存的数据多 ),这样就类似于关系型中分表,确实可以算得到优化,但是:如果一个节点中有多个分片了,那么就会分片之间的资源竞争,这就会导致性能降低
所以分片和副本遵循下面的原则就可以了
- 每个分片占用的磁盘容量不得超过ES的JVM的堆空间设置( 一般最大为32G ),假如:索引容量为1024G,那么节点数量为:1024 / 32 = 32左右
- 分片数不超过节点数的3倍,就是为了预防一个节点上有多个分片的情况,万一当前节点死了,那么就算做了副本,也很容易导致集群丢失数据
- 节点数 <= 主节点数 * ( 副本数 + 1 )
- 推迟分片分配。有可能一个节点宕掉了,但是后面它又恢复了,而这个节点原有数据是还在的,所以:推迟分片分配,从而减少ES的开销,具体做法如下:
PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}
可以全局修改,也可以在建索引时修改
带路由查询
前面说过:路由计算公式 shard = hash( routing ) % number_of_primary_shards
而routing默认值就是文档id,所以查询时把文档id带上,如:前面玩kibana做的操作
不带路由就会把分片和副本都查出来,然后进行轮询,这效率想都想得到会慢一点嘛
内存优化
修改es的config/jvm.options
把上面的数字改了
Xms 表示堆的初始大小, Xmx 表示可分配的最大内存,ES默认是1G,这个数字在现实中是远远不够了,改它的目的是:为了能够在 Java 垃圾回收机制清理完堆内存后不需要重新分隔计算堆内存的大小而浪费资源,可以减轻伸缩堆大小带来的压力,但是也需要注意:改这两个数值,需要确保 Xmx 和 Xms 的大小是相同的,另外就是:这两个数值别操作32G啊,前面已经讲过了
附上一些配置说明
参数名 | 参数值 | 说明 |
---|---|---|
cluster.name | elasticsearch | 配置 ES 的集群名称,默认值是 ES,建议改成与所存数据相关的名称, ES 会自动发现在同一网段下的 集群名称相同的节点 |
node.name | node-1001 | 集群中的节点名,在同一个集群中不能重复。节点 的名称一旦设置,就不能再改变了。当然,也可以 设 置 成 服 务 器 的 主 机 名 称 , 例 如 node.name: $ |
node.master | true | 指定该节点是否有资格被选举成为 Master 节点,默 认是 True,如果被设置为 True,则只是有资格成为 Master 节点,具体能否成为 Master 节点,需要通过选举产生 |
node.data | true | 指定该节点是否存储索引数据,默认为 True。数据的增、删、改、查都是在 Data 节点完成的 |
index.number_of_shards | 1 | 设置索引分片个数,默认是 1 片。也可以在创建索引时设置该值,具体设置为多大值要根据数据量的大小来定。如果数据量不大,则设置成 1 时效率最高 |
index.number_of_replicas | 1 | 设置默认的索引副本个数,默认为 1 个。副本数越多,集群的可用性越好,但是写索引时需要同步的数据越多 |
transport.tcp.compress | true | 设置在节点间传输数据时是否压缩,默认为 False |
discovery.zen.minimum_master_nodes | 1 | 设置在选举 Master 节点时需要参与的最少的候选主节点数,默认为 1。如果使用默认值,则当网络不稳定时有可能会出现脑裂。 合理的 数 值 为 ( master_eligible_nodes / 2 )+1 , 其 中 master_eligible_nodes 表示集群中的候选主节点数 |
discovery.zen.ping.timeout | 3s | 设置在集群中自动发现其他节点时 Ping 连接的超时时间,同时也是选主节点的延迟时间,默认为 3 秒。 在较差的网络环境下需要设置得大一点,防止因误判该节点的存活状态而导致分片的转移 |
说一些另外的理论吧
ES的master主节点选举流程
- 首先选主是由ZenDiscovery来完成的。ZenDiscovery做了两件事:
- 一个是Ping过程,发现节点嘛
- 二是Unicast过程,控制哪些节点需要Ping通
- 对所有可以成为master的节点(文件中设置的node.master: true)根据nodeId字典排序,每次“选举节点(即:参与投票选举主节点的那个节点)”都把自己知道的节点排一次序,就是把排好序的第一个节点(第0位)认为是主节点(投一票)
- 当某个节点的投票数达到一个值时,此值为 (可以成为master节点数n / 2 ) + 1,而该节点也投自己,那么这个节点就是master节点,否则重新开始,直到选出master
另外注意: master节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data节点可以关闭http功能
ES中的节点职责如下:
节点类型 | 配置参数 | 默认值 | 节点职责 |
---|---|---|---|
master eligible | node.master | true | 备选主节点:主节点可以管理和记录集群状态、决定分片在哪个节点、处理创建和删除索引库的请求 |
data | node.data | true | 数据节点:存储数据、搜索、聚合、CRUD |
ingest | node.ingest | true | 数据存储之前的预处理 但:若是已经使用Java代码进行了预处理,那么此配置就无效了 |
coordinating | 上面3个参数都为false则为coordinating节点 | 无 | 协调节点,路由请求到其它节点合并其它节点处理的结果,返回给用户 |
默认情况下,集群中的任何一个节点都同时具备上述四种角色
但是真实的集群一定要将集群职责分离:
- master节点:对CPU要求高,但是内存要求第
- data节点:对CPU和内存要求都高
- coordinating节点:对网络带宽、CPU要求高
职责分离可以让我们根据不同节点的需求分配不同的硬件去部署。而且避免业务之间的互相干扰
ES的集群脑裂问题
所谓的脑裂一句话来概括就是老大(master节点)“没了”,然后小弟(有资格成为主节点的节点)重新选举出老大,结果最后旧老大回来了,从而造成新旧老大整合的数据不一样,最后就摊上事儿了
导致的原因:
- 网络问题:集群间的网络延迟导致一些候选主节点(文件中设置的node.master: true,即:可以成为主节点的节点)访问不到master, 认为master 挂掉了从而选举出新的master,并对master上的分片和副本标红,分配新的主分片
- 节点负载:主节点的角色既为master又为data,访问量较大时可能会导致ES停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点
- 内存回收:data 节点上的ES进程占用的内存较大,引发JVM的大规模内存回收,造成ES进程失去响应
脑裂问题解决方案:
-
减少误判:discovery.zen ping_ timeout 节点状态的响应时间,默认为3s。可以适当调大,如果master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数( 如6s,discovery.zen.ping_timeout:6 ),可适当减少误判
-
选举触发:discovery.zen.minimum_master_nodes:1,该参數是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个數大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n / 2) +1,n为有资格成为主节点的节点个数)
- 多提一嘴:为了避免脑裂,要求选票超过 (n+1) / 2 才能当选为主,n为有资格成为主节点的节点个数(即:候选主节点个数)。因此n候选主节点数最好是奇数,对应配置就是上面的 discovery.zen.minimum_master_nodes 。当然,在es7.0以后,已经成为默认配置,es会自动去计算候选主节点的数量,从而进行配置,所以一般不会发生脑裂
-
角色分离:即master节点与data节点分离,限制角色
-
主节点配置为:node master: true,node data: false
-
从节点置为:node master: false,node data: true
-