LeeQingShui's Blog

  • 标签

  • 分类

  • 归档

  • 关于

ElasticSearch 优化篇

发表于 2018-12-22 | 更新于 2024-02-23
本文字数: 5.9k | 阅读时长 ≈ 8 分钟

索引的 Maping 优化

  • 对于不需要建立倒排索引的字段,可以将该值设置为index属性设置为false,提高 ES 性能,比如说用户图片的地址就不需要进行搜索,可以这么设置
  • 数值型的类型字段尽量选择范围小的类型,提高搜索效率
  • 对于浮点型的类型尽量用比例因子,使用比例因子的好处是整型比浮点型更易压缩,节省磁盘空间。比如一个价格字段,单位为元,将比例因子设置为 100,在 ES 中会按分存储。由于比例因子为 100, 如果我们输入的价格是 23.45,则 ES 中会将 23.45 乘以 100 存储在 ES 中。如果输入的价格是 23.456, ES会将 23.456 乘以 100 再取一个接近原始值的数,得出 2346。此时字段映射如下:
1
2
3
4
5
6
{
"price": {
"type": "scaled_float",
"sca1ing_factor": 100
}
}

疑问

  • 索引分片怎么设置?
  • 索引如何重建?
  • 索引别名如何查询的?

磁盘

  磁盘在现代服务器.上通常都是瓶颈。Elasticsearch重度使用磁盘,你的磁盘能处理的吞吐量越大,你的节点就越稳定。这里有一些优化磁盘I/0 的技巧:

  • 使用 SSD,这比机械磁盘优秀多了
  • 使用 RAID 0。条带化 RAID 会提高磁盘 /O,代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验RAID因为副本已经提供了这个功能
  • 使用多块硬盘,并允许 Elasticsearch 通过多个path.data 目录配置把数据条带化分配到它们上面
  • 不要使用远程挂载的存储,比如 NFS。这个引入的延迟对性能来说完全是背道而驰的

分片策略

合理分片

  分片和副本的设计为 ES 提供了支持分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的。而且由于索引主分片的路由机制,一旦主分片完成分配后,无法重新修改主分片的数量。
  可能有人会说,我不知道这个索引将来会变得多大,并且过后我也不能更改索引的大小,所以为了保险起见,还是给它设为 100 个分片吧。这样并不合理,因为配置分片时并不是没有代价的:

  • 一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转
  • 每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好,但如果多个分片都需要在同一个节点上竞争使用相同的资源,那么会降低一部分性能
  • 用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度

  一个业务索引具体需要分配多少分片可能需要架构师和技术人员对业务的增长有个预先的判断,横向扩展应当分阶段进行, 为下一阶段准备好足够的资源。 只有当你进入到下一个阶段,你才有时间思考需要作出哪些改变来达到这个阶段。一般来说, 我们遵循一些原则

  • 控制每个分片占用的硬盘容量不超过 ES 的最大 JVM 的堆空间设置(一般设置不超过 32G,参考下文的 JVM 设置原则),因此,如果索引的总容量在 500G 左右,那分片大小在 16 个左右即可;当然,最好同时考虑原则

  • 考虑一下node数量,一般一个节点有时候就是一台物理机, 如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了1个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以,一般都设置分片数不超过节点数的 3 倍

  • 主分片,副本和节点最大数之间数量,我们分配的时候可以参考以下关系:节点数<=主分片数*(副本数+1)

推迟分片分配

  对于节点瞬时中断的问题,默认情况, 集群会等待一分钟来查看节点是否会重新加入,如果节点在此期间重新加入, 重新加入的节点会保持其现有的分片数据,不会触发新的分片分配。这样就可以减少 ES 在自动再平衡可用分片时所带来的极大开销。

  通过修改参数 delayed timeout,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改:

1
2
3
4
5
6
PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}

选择性路由

  当我们查询文档的时候,Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?它其实是通过下面这个公式来计算出来:

1
shard = hash(routing) % number_of primary_shards

routing 默认值是文档的 id, 也可以采用自定义值,比如用户 id。

不带 routing 查询

  在查询的时候因为不知道要查询的数据具体在哪个分片上, 所以整个过程分为 2 个步骤:

  • 分发:请求到达协调节点后, 协调节点将查询请求分发到每个分片上
  • 聚合:协调节点搜集到每个分片上查询结果, 在将查询的结果进行排序,之后给用户返回结果

带 routing 查询(更快)

  查询的时候,可以直接根据 routing 信息定位到某个分配查询, 不需要查询所有的分配,经过协调节点排序。
  向上面自定义的用户查询,如果 routing 设置为 userid 的话, 就可以直接查询出数据来,效率提升很多。

写入速度优化

  ES 的默认配置,是综合了数据可靠性、写入速度、搜索实时性等因素。

  实际使用时, 我们需要根据公司要求,进行偏向性的优化。

  针对于搜索性能要求不高,但是对写入要求较高的场景,我们需要尽可能的选择恰当写优化策略。

  综合来说,可以考虑以下几个方面来提升写索引的性能:

  • 加大 Translog Flush,目的是降低 Iops、 Writeblock
  • 增加 Index Refresh 间隔,目的是减少 Segment Merge 的次数
  • 调整 Bulk 线程池和队列
  • 优化节点间的任务分布
  • 优化 Lucene 层的索引建立,目的是降低 CPU 及 IO

批量数据提交

  ES 提供了 Bulk API 工支持批量操作,当我们有大量的写任务时,可以使用 Bulk 来进行批量写入。
  通用的策略如下:Bulk 默认设置批量提交的数据量不能超过 100M。数据条数一般是根据文档的大小和服务器性能而定的,但是单次批处理的数据大小应从 5MB~15MB 逐渐增加,当性能没有提升时,把这个数据量作为最大值。

优化存储设备

  ES 是一种密集使用磁盘的应用,在段合并的时候会频繁操作磁盘,所以对磁盘要求较高,当磁盘速度提升之后,集群的整体性能会大幅度提高。

合理使用合并

  Lucene以段的形式存储数据。当有新的数据写入索引时,Lucene 就会自动创建一个新的段。
  随着数据量的变化,段的数量会越来越多,消耗的多文件句柄数及CPU就越多,查询效率就会下降。
  由于Lucene 段合并的计算量庞大,会消耗大量的I/O, 所以ES默认采用较保守的策略,让后台定期进行段合并

减少Refresh的次数

  Lucene在新增数据时,用了延迟写入的策略,默认情况下索引的refiesh interval 为1秒。
  Lucene将待写入的数据先写到内存中,超过1秒(默认)时就会触发一次 Refesh, 然后Refresh会把内存中的的数据刷新到操作系统的文件缓存系统中。
  如果我们对搜索的实效性要求不高,可以将Refresh 周期延长,例如30秒。
这样还可以有效地减少段刷新次数,提高写的效率,但这同时意味着需要消耗更多的 Heap 内存。

加大 Flush 设置

  Flush的主要目的是把文件缓存系统中的段持久化到硬盘,当Translog 的数据量达到512MB或者30分钟时,会触发一次Flush.
  index.translog. flush threshold size参数的默认值是 512MB,我们进行修改。
  增加参数值意味着文件缓存系统中可能需要存储更多的数据,所以我们需要为操作系统的文件缓存系统留下足够的空间。

减少副本的数量

  ES 为了保证集群的可用性,提供了Replica(副本)支持,由于每个副本也会执行分析、索引及可能的合并过程,所以 Replica 的数量会影响写索引的效率。

  当写索引时,除了把数据写入主分片节点中,还会并行将数据写入到所有副本分片节点,副本节点越多,写索引的效率就越慢。

  因此,如果我们需要大批量进行写入操作,可以先禁止 Replica 复制,设置index.number.of_replicas: 0关闭副本;在写入完成后,再将 Replica 修改回正常的状态。

内存设置

  ES 默认安装后设置的内存是基于服务器总内存设置的,如果安装 ES 的机器还存在其他应用,那么会影响到它们。

  对于解压安装的 ES,则其中包含一个jvm.option配置文件,可通过以下参数设置 ES 堆大小:

1
2
3
# Xms 表示堆的初始大小,Xmx 表示可分配的最大内存
-Xms4g
-Xmx4g

  确保 Xmx 和 Xms 的大小是相同的,其目的是为了能够在 Java 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源,可以减轻伸缩堆大小带来的压力。

  假设你有一个 64G 内存的机器,按照正常思维思考,你可能会认为把 64G 内存都给ES比较好,但现实是这样吗,越大越好?

  虽然内存对 ES 来说至关重要,但是答案是否定的!因为 ES 堆内存的分配需要满足以下两个原则:

  • 不要超过物理内存的 50%:Lucene 的设计目的是把底层 OS 里的数据缓存到内存中。Lucene 的段是分别存储到单个文件中的,这些文件都是不会变化的,所以很利于缓存,同时操作系统也会把这些段文件缓存起来,以便更快的访问。因此如果设置的堆内存过大,Lucene 可用的内存将会减少,就会严重影响降低 Lucene 的全文本查询性能。
  • 堆内存的大小最好不要超过 32GB: 在 Java 中,所有对象都分配在堆上,然后有一个 Class Pointer指针指向它的类元数据。这个指针在 64 位的操作系统上为 64 位,64 位的操作系统可以使用更多的内存(2^64)。在 32 位的系统上为 32 位,32 位的操作系统的最大寻址空间为 4GB(2^32)。但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如 LLC,L1 等)之间移动数据的时候,会占用更多的带宽。

  最终我们都会采用 31G 设置

1
2
-Xms 31g
-Xmx 31g

更新速度优化

ES版本5.6,数据量在3000万左右,数据更新频率比较频繁,总共的更新速度大概是1w/s-5w/s。
最新的数据先进kafka,再由flink消费写入ES。
目前发现在默认的ES配置下,bulk update或者upsert的速度始终上不去,所有节点的cpu使用率才25%左右,速度最高只能到1w/s左右。
如果改成这些数据全部是插入,不做更新操作,那么cpu可以跑满,而且kafka的消费绝对没有积压。
试过增加节点,效果很小,速度看起来有一点点的增加。试过增加分片数,有效果,但仍然不是很明显。

请问是什么原因导致这个现象呢,增加分片对写入速度有提升又是为什么呢?
像这种场景,有没有办法直接的提升update/upsert速率,或者间接解决,比如全部用插入的方式写入,查询时同一个id的记录取时间最近的数据。那么查询方式还有删除旧数据这块怎么设计比较好

1.update是先get再insert然后再delete(标记删除)旧的文档,和insert相比,肯定update耗时多
2.由于一次操作完成时长多,线程池数量有限,导致cpu只有25%(猜测…)
3.适当增加ES分片,对写入是有一点提高,因为相当于多出来了lucene进程,可以接收的请求多了,付出的代价就是分片多了之后同步数据是需要消耗性能的,然后查询更是会性能降低
4.客户端使用层面:可以全部使用insert提高性能,然后定时去delete,定时(低峰期)合并segment,优化数据结构
5.集群本身层面:可以控制refresh的频率,translog设置,副本可以先干掉(写完再补回来),线程池参数修改——-这些都是危险操作,评估后再进行实践

在上面基础上补充:

  1. 使用ES自动的id,不要指定id
  2. 提高操作系统 filesystem cache
  3. 关注下 index_buffer_size 这个参数

在不改业务逻辑的情况下,只能指定id来做更新。
改线程池大小、设置translog异步写、加大refresh interval、index buffer size这些以前测试过都没有明显提高,或者说几乎没有提高。

改业务逻辑的话,不知道怎么样保持像不改业务逻辑那样的效果。例如有10条数据,9条更新频繁,1条几天都不更新一次。那么如果我每次都是新写入,查询时取时间最近的记录,这个好办,但删旧数据时,怎么办呢,如果根据时间删掉当天之前的全部数据,那么原来的10条数据可能只剩下9条了。

集群规模及索引规划

集群规模评估

集群规模的评估主要评估以下三个方面:

  • 计算资源评估:计算资源的评估主要是评估单节点的CPU和内存。ES的计算资源一般消耗在写入和查询过程,经过总结大量ES集群的运维经验,2C8G 的配置大概能支持 5k doc/s 的写入,32C64G 的配置大概能支撑 5w doc/s的写入能力;
  • 存储资源评估:存储资源的评估主要是评估磁盘的类型及容量大小。例如ES集群使用什么类型的磁盘,SSD或者高性能云盘,以及每块盘的容量大小,是选择单盘多容量,还是多盘少容量。而对于冷热分离的集群,则默认使用SSD作为热节点,高性能云盘作为温节点。另外腾讯云ES支持单节点挂载多块云硬盘,且经过性能压测,3块盘相比于1块盘,吞吐量大约有2.8倍的提升。因此如果对写入速度及IO性能要求较高,可选择挂载多块 SSD 磁盘;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PUT /user_gift_202401
{
"aliases" : {
"user_gift" : {}
}
}
PUT /user_gift_202402
{
"aliases" : {
"user_gift" : {}
}
}
......
POST /user_gift/_rollover
{
"conditions": {
"max_age": "7d",
"max_docs": 30000000,
"max_size": "40gb"
}
}

参考

ES 7.17——优化说明

Mybatis 学习笔记

发表于 2018-12-22 | 更新于 2024-10-07 | 分类于 Java Web 框架
本文字数: 22k | 阅读时长 ≈ 32 分钟

序言

  通过传统的 JDBC 去连接数据源存在很多问题:

  • ① 每次都需要输入数据库的基本信息,可否抽离一次固定以复用?
  • ② Connection 是什么?每次都需要新建,大量创建与关闭会不会比较消耗资源,可否优化?
  • ③ 如何保证数据的读写正确性?即事务是如何管理控制的?
  • ④ SQL 语句定义、参数设置、结果集处理存在硬编码,不同的 SQL 零散地分布在各处,能否做成动态参数定义方式,并统一管理?
  • ⑤ 每次都需要手动映射获取的数据到 POJO 对象,好麻烦呀,能不能自动映射?

  为了解决这些问题,ORM 框架应运而生,本文介绍的 Mybatis 就是这么一个 ORM 框架。

阅读全文 »

(九)数据结构之哈希表

发表于 2018-12-18 | 更新于 2023-09-01 | 分类于 数据结构与算法
本文字数: 2.7k | 阅读时长 ≈ 4 分钟

简介

  哈希表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做哈希函数,存放记录的数组称做哈希表。

  一个通俗的例子是,为了查找电话簿中某人的号码,可以创建一个按照人名首字母顺序排列的表(即建立人名 x 到首字母 F(x) 的一个函数关系),在首字母为 W 的表中查找“王”姓的电话号码,显然比直接查找就要快得多。这里使用人名作为关键字,“取首字母”是这个例子中哈希函数的函数法则 F(x) ,存放首字母的表对应哈希表。关键字和函数法则理论上可以任意确定。

阅读全文 »

(八)数据结构之映射

发表于 2018-12-10 | 更新于 2023-08-20 | 分类于 数据结构与算法
本文字数: 558 | 阅读时长 ≈ 1 分钟
  此文待重构

简介

  映射,或者射影,在数学及相关的领域经常等同于函数。基于此,部分映射就相当于部分函数,而完全映射相当于完全函数。

阅读全文 »

(七)数据结构之集合

发表于 2018-12-09 | 更新于 2023-08-20 | 分类于 数据结构与算法
本文字数: 625 | 阅读时长 ≈ 1 分钟
  此文待重构

简介

  在计算机科学中,集合是一组可变数量的数据项(也可能是0个)的组合,这些数据项可能共享某些特征,需要以某种操作方式一起进行操作。一般来讲,这些数据项的类型是相同的,或基类相同(若使用的语言支持继承 “继承 (计算机科学)”))。列表(或数组)通常不被认为是集合,因为其大小固定,但事实上它常常在实现中作为某些形式的集合使用。

阅读全文 »

(六)数据结构之堆

发表于 2018-12-08 | 更新于 2023-08-20 | 分类于 数据结构与算法
本文字数: 1.7k | 阅读时长 ≈ 2 分钟
  此文待重构

简介

  堆(英语:Heap)是计算机科学中的一种特别的树状数据结构。若是满足以下特性,即可称为堆:“给定堆中任意节点 P 和 C,若 P 是 C 的母节点,那么 P 的值会小于等于(或大于等于) C 的值”。若母节点的值恒小于等于子节点的值,此堆称为最小堆(英语:min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(英语:max heap)。在堆中最顶端的那一个节点,称作根节点(英语:root node),根节点本身没有母节点(英语:parent node)。

阅读全文 »

(五)数据结构之二叉搜索树

发表于 2018-12-07 | 更新于 2023-09-04 | 分类于 数据结构与算法
本文字数: 2.2k | 阅读时长 ≈ 3 分钟

简介

  在计算机科学中,二叉树(英语:Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。

阅读全文 »

(四)数据结构之链表

发表于 2018-12-04 | 更新于 2023-08-31 | 分类于 数据结构与算法
本文字数: 3.2k | 阅读时长 ≈ 5 分钟

简介

  链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针)(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到 O(1) 的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 O(n) 的时间,而顺序表相应的时间复杂度分别是 O(logn) 和 O(1)。

阅读全文 »

(三)数据结构之队列

发表于 2018-12-03 | 更新于 2023-08-20 | 分类于 数据结构与算法
本文字数: 1.1k | 阅读时长 ≈ 2 分钟
  此文待重构

简介

  队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。

阅读全文 »

(二)数据结构之栈

发表于 2018-12-02 | 更新于 2023-08-20 | 分类于 数据结构与算法
本文字数: 2k | 阅读时长 ≈ 3 分钟

简介

  堆栈(英语:stack)又称为栈或堆叠,是计算机科学中一种特殊的串列形式的抽象数据类型,其特殊之处在于只能允许在链表或数组的一端(称为堆栈顶端指针,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。另外堆栈也可以用一维数组或链表的形式来完成。堆栈的另外一个相对的操作方式称为队列。

阅读全文 »
1…12131415
LeeQingShui

LeeQingShui

149 日志
16 分类
69 标签
RSS
© 2018 – 2026 LeeQingShui | 站点总字数: 899k
赣 ICP 备 2022002212 号
本站已运行
本站总访问量 次 | 本站访客 人次
0%