1. Index(写)调优
一般数据首先都是进入MySQL集群的,我们从MySQL的原始表里面抽取并存储到 ES 的Index,而MySQL的原始数据也是经常在变化的,所以快速写入Elasticsearch、以保持 Elasticsearch 和 MySQL 的数据及时同步也是很重要的。总结下面几个方面优化来提高写入的速度:
1.1 副本数置0
如果是集群首次灌入数据,可以将副本数设置为0,写入完毕再调整回去,这样副本分片只需要拷 贝,节省了索引过程。
PUT /my_temp_index/_settings {
"number_of_replicas": 0 }
1.2 自动生成doc ID
通过Elasticsearch写入流程可以看出,写入doc时如果外部指定了id,则Elasticsearch会先尝试 读取原来doc的版本号,以判断是否需要更新。这会涉及一次读取磁盘的操作,通过自动生成doc ID可 以避免这个环节。
1.3 合理设置mappings
将不需要建立索引的字段index属性设置为not_analyzed或no。对字段不分词,或者不索引,可以 减少很多运算操作,降低CPU占用。 尤其是binary类型,默认情况下占用CPU非常高,而这种类 型进行分词通常没有什么意义。 减少字段内容长度,如果原始数据的大段内容无须全部建立 索引,则可以尽量减少不必要的内 容。 使用不同的分析器(analyzer),不同的分析器在索引过程中 运算复杂度也有较大的差异。
1.4 调整_source字段
source doc includes excludes source禁用,一般用于索引和数据分离,这样可以降低 I/O 的压力,不过实际场景中大多不
会禁用_source。
1.5 对analyzed的字段禁用norms Norms用于在搜索时计算doc的评分,如果不需要评分,则可以将其禁用:
"title": {
"type": "string",
"norms": {
"enabled": false
}
1.6 调整索引的刷新间隔
该参数缺省是1s,强制ES每秒创建一个新segment,从而保证新写入的数据近实时的可见、可被搜索 到。比如该参数被调整为30s,降低了刷新的次数,把刷新操作消耗的系统资源释放出来给index操作使 用。 PUT /my_index/_settings
{
"index" : {
"refresh_interval": "30s" }
}
这种方案以牺牲可见性的方式,提高了index操作的性能。
1.7 批处理
批处理把多个index操作请求合并到一个batch中去处理,和mysql的jdbc的bacth有类似之处。如图: 将者或,滤 过过通以可,段字的储存要需不分部于对,据数始原储存于用段字
比如每批1000个documents是一个性能比较好的size。每批中多少document条数合适,受很多因素 影响而不同,如单个document的大小等。ES官网建议通过在单个node、单个shard做性能基准测试来 确定这个参数的最优值。
1.8 Document的路由处理 当对一批中的documents进行index操作时,该批index操作所需的线程的个数由要写入的目的shard的个数决定。看下图:
上图中,有2批documents写入ES, 每批都需要写入4个shard,所以总共需要8个线程。如果能减少 shard的个数,那么耗费的线程个数也会减少。例如下图,两批中每批的shard个数都只有2个,总共线 程消耗个数4个,减少一半。
默认的routing就是id routing value put /index/doc/id?routing=user_id
说如比, 个一定指动手,候时的求请送发在以可也,
值得注意的是线程数虽然降低了,但是单批的处理耗时可能增加了。和提高刷新间隔方法类似,这有可 能会延长数据不见的时间。
2. Search(读)调优
在存储的Document条数超过10亿条后,我们如何进行搜索调优。
2.1 数据分组
很多人拿ES用来存储日志,日志的索引管理方式一般基于日期的,基于天、周、月、年建索引。如下 图,基于天建索引:
当搜索单天的数据,只需要查询一个索引的shards就可以。当需要查询多天的数据时,需要查询多个索 引的shards。这种方案其实和数据库的分表、分库、分区查询方案相比,思路类似,小数据范围查询而 不是大海捞针。
开始的方案是建一个index,当数据量增大的时候,就扩容增加index的shard的个数。当shards增大 时,要搜索的shards个数也随之显著上升。基于数据分组的思路,可以基于client进行数据分组,每一 个client只需依赖自己的index的数据shards进行搜索,而不是所有的数据shards,大大提高了搜索的性 能,如下图:
2.2 使用Filter替代Query
在搜索时候使用Query,需要为Document的相关度打分。使用Filter,没有打分环节处理,做的事情更少,而且filter理论上更快一些。 如果搜索不需要打分,可以直接使用filter查询。如果部分搜索需要打分,建议使用'bool'查询。这种方 式可以把打分的查询和不打分的查询组合在一起使用,如: GET /_search
{
"query": {
"bool": {
"must": {
"term": {
"user": "kimchy"
}
},
"filter": {
"term": {
"tag": "tech"
}
}
}
}
}
2.3 ID字段定义为keyword
一般情况,如果ID字段不会被用作Range 类型搜索字段,都可以定义成keyword类型。这是因为 keyword会被优化,以便进行terms查询。Integers等数字类的mapping类型,会被优化来进行range类型搜索。将integers改成keyword类型之后,搜索性能大约能提升30%。
2.4 别让用户的无约束的输入拖累了ES集群的性能
注意用户查询输入的条件中夹带了很多'OR'语句以及通配符“*”开头的字符串,可通过对Slow Logs分析发现
为了不让用户无约束的查询语句拖累ES集群的查询性能,可以限制用户用来查询的keywords。对于可以用来查询的keyworkds,也可以写成文档来帮助用户更正确的使用。
3 ES 关联关系处理
目前ES主要有以下4种常用的方法来处理数据实体间的关联关系:
3.1 Application-side joins
索引之间完全独立(利于对数据进行标准化处理),由应用端的多次查询来实现近似关联关 系查询。这种方法适用于关联的实体只有少量的文档记录的情况(使用ES的terms查询具有上限,默认 1024,具体可在elasticsearch.yml中修改),并且最好它们很少改变。这将允许应用程序对结果进行缓 存,并避免经常运行第一次查询。
PUT /user/_doc/1
{
"name": "John Smith",
"email": "john@smith.com",
"dob": "1970/10/24"
}
PUT /blogpost/_doc/2
{
"title": "Relationships",
"body": "It's complicated...",
"user": 1
}
GET /user/_search
{
"query": {
"match": {
"name": "John"
}
}
}
GET /blogpost/_search
{
"query": {
"terms": { "user": [1] }
}
}
3.2 Data denormalization(数据的非规范化)
通俗点就是通过字段冗余,以一张大宽表来实现粗粒度的index,这样可以充分发挥扁平化 的优势。但是这是以牺牲索引性能及灵活度为代价的。使用的前提:冗余的字段应该是很少改变的,比 较适合与一对少量关系的处理。当业务数据库并非采用非规范化设计时,这时要将数据同步到作为二级 索引库的ES中,就需要进行定制化开发,基于特定业务进行应用开发来处理join关联和实体拼接。 说明:宽表处理在处理一对多、多对多关系时,会有字段冗余问题,适合“一对少量”且这个“一”更新不 频繁的应用场景。
PUT /user/_doc/1
{
"name": "John Smith",
"email": "john@smith.com",
"dob": "1970/10/24"
}
PUT /blogpost/_doc/2
{
"title": "Relationships",
"body": "It's complicated...",
"user": {
"id": 1,
"name": "John Smith"
}
}
GET /blogpost/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title":
{ "match": { "user.name": "John" }
}
]
}
}
}
3.3 Nested objects(嵌套文档)
索引性能和查询性能二者不可兼得,必须进行取舍。嵌套文档将实体关系嵌套组合在单文档内部,这种 方式牺牲建立索引性能(文档内任一属性变化都需要重新索引该文档)来换取查询性能,比较适合于一 对少量的关系处理。 } } } GET /blogpost/_search { "query": { "terms": { "user": [1] } } } 当使用嵌套文档时,使用通用的查询方式是无法访问到的,必须使用合适的查询方式(nested query、 nested filter、nested facet等),很多场景下,使用嵌套文档的复杂度在于索引阶段对关联关系的组 织拼装。
PUT /drivers
{
"mappings":{
"properties":{
"driver":{
"type":"nested",
"properties":{
"last_name":{
"type":"text"
},
"vehicle":{
"type":"nested",
"properties":{
"make":{
"type":"text"
},
"model":{
"type":"text"
}
}
}
}
}
}
}
}
PUT /drivers/_doc/1
{
"driver" : {
"last_name" : "McQueen",
"vehicle" : [
{
"make" : "Powell Motors",
"model" : "Canyonero"
},
{
"make" : "Miller-Meteor",
"model" : "Ecto-1"
}
]
}
}
PUT /drivers/_doc/2?refresh
{
"driver" : {
"last_name" : "Hudson",
"vehicle" : [
{
"make" : "Mifune",
"model" : "Mach Five"
},
{
"make" : "Miller-Meteor"
"model" : "Ecto-1"
}
]
}
}
GET /drivers/_search
{
"query":{
"nested":{
"path":"driver",
"query":{
"nested":{
"path":"driver.vehicle",
"query":{
"bool":{
"must":[
{
"match":{
"driver.vehicle.make":"Powell Motors"
}
},
{
"match":{
"driver.vehicle.model":"Canyonero"
}
}
]
}
}
}
}
}
}
}
3.4 Parent/child relationships(父子文档)
父子文档牺牲了一定的查询性能来换取索引性能,适用于写多读少的场景。父子文档相比嵌套文档较灵 活,适用于“一对大量”且这个“一”不是海量的应用场景,该方式比较耗内存和CPU,这种方式查询比嵌 套方式慢5~10倍,且需要使用特定的has_parent和has_child过滤器查询语法,查询结果不能同时返回 父子文档(一次join查询只能返回一种类型的文档)。受限于父子文档必须在同一分片上(可以通过 routing指定父文档id即可)操作子文档时需要指定routing。
PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
# 插入父文档
PUT /my_index/_doc/1?refresh
{
"text": "This is a question",
"my_join_field": {
"name": "question"
}
}
PUT /my_index/_doc/2?refresh
{
"text": "This is a question2",
"my_join_field": "question"
}
# 插入子文档
PUT /my_index/_doc/3?routing=1 {
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}
查询那个文档有子文档
POST my_index/_search
{
"query": {
"has_child": {
"type": "answer",
"query" : {
"match": {
"text" : "this"
}
}
}
}
}
根据父文档id查询子文档
GET my_index/_search
{
"query": {
"parent_id": {
"type": "answer",
"id": "1"
}
}
}
本文由 chaoohuua 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2025/05/29 15:51