索引类似于MySQL数据库中的表结构定义,索引管理的 API 提供了单个索引的管理(创建和删除)、别名管理、索引设置、定义 Mapping、Reindex、索引模板、索引收缩等功能。
1.开发&测试中操作索引
这种方式仅仅适合于本地开发或者测试的时候使用,至于为什么,后面我们再详细分析。
1.1 创建索引
# 创建索引的最基本形式,没有指定 Mapping
PUT huidong_order
# 创建带 setting 和 Mapping 的索引
PUT huidong_order
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text"
}
}
},
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
创建索引的限制有以下几个:
- 只能是小写字母。
- 不能包含 \,/,*,?,",<,>,|,(空格),,,#等字符。
- 7.0 之后的版本不能再包含 : (冒号)字符了。
- 不能以 -,_,+ 开头。名字不能是 . 或者 ..。
- 不能长于 255 字节。需要注意的是某些字符是需要多个字节来表示的。
1.2 删除索引
索引删除比较简单,没啥说的。
# 删除一个索引
DELETE huidong_order
删除成功返回:{ "acknowledged" : true },如果索引不存在返回404。
1.3 判断索引是否存在
HEAD huidong_order
如果索引存在,那么返回的 HTTP 状态码为 200,不存在的话为 404。
2. 索引别名
假设我们想对索引huidong_order_v1的某一个字段类型进行变更,这个时候是不可以在索引上直接操作的。
- 我们需要去创建一个名字叫
huidong_order_v2的索引并指定预期的mapping。 - 然后将
huidong_order_v1的数据导入到huidong_order_v2。 - 删除
huidong_order_v1这个索引。 - 将
huidong_order_v2重新命名为huidong_order_v1。
这个操作是非原子性的,如果在这个过程中存在业务对索引数据的操作,会导致出现数据丢失数据错误的问题。
索引别名就是为了解决这个问题的。
假设huidong_order这个索引别名指向huidong_order_v1这个索引,那么此时我们仅仅需要这样操作:
- 创建一个名字叫
huidong_order_v2的索引并指定预期的mapping。 - 然后将
huidong_order_v1的数据导入到huidong_order_v2。 - 将
huidong_order这个索引别名指向huidong_order_v2这个索引。 - 删除
huidong_order_v1这个索引。
这样就可以做到新旧索引之间的无缝切换。
别名就是一个索引另外的名字,其就像一个软连接或者快捷方式。每个索引可以有多个别名,而不同的索引也可以使用相同的别名,这样使得不同的别名可以适用于不同的情景。
2.1 给索引指定别名
POST /_aliases
{
"actions" : [
{ "add" : { "index" : "huidong_order_v1", "alias" : "huidong_order" } }
]
}
2.2 别名删除
POST /_aliases
{
"actions" : [
{ "remove" : { "index" : "huidong_order_v1", "alias" : "huidong_order" } }
]
}
2.3 修改别名
POST /_aliases
{
"actions" : [
{ "remove" : { "index" : "huidong_order_v1", "alias" : "huidong_order" } },
{ "add" : { "index" : "huidong_order_v1", "alias" : "order" } }
]
}
别名重命名的操作是先将原先的别名删除了,然后再创建新的别名。Elasticsearch 中的索引别名修改操作是原子性的。当修改索引别名时,这个操作将会是原子的,要么完全成功,要么完全失败,不会出现中间状态。
这种原子性保证了在别名修改期间的并发访问情况下的一致性。当别名被修改时,对应的索引指向会立即生效,所有的读取和写入操作都会自动应用到新的别名指向的索引上。
这种原子性的别名修改机制使得在 Elasticsearch 中进行索引的切换、重命名、版本迁移等操作变得更加安全和方便。
2.4 为多个索引指定同一个别名
POST /_aliases
{
"actions" : [
{ "add" : { "indices" : ["huidong_order_v1", "huidong_order_v2"], "alias" : "huidong_order" } }
]
}
2.5 为一个索引指定多个别名
PUT /huidong_order_v1/_alias/huidong_order,order
3.索引设置
在创建索引的时候,可以在 "settings" 字段中指定索引的设置。number_of_shards 和 number_of_replicas 是索引非常重要的两个配置,设置它们值的示例如下:
PUT huidong_order_v1
{
"settings": {
"number_of_shards": 3, # 指定了 3 个主分片
"number_of_replicas": 1 # 指定了一个副本分片
}
}
另外我们还可以动态修改索引的配置。
PUT /hiudong_order_v1/_settings
{
"number_of_replicas": 2
}
注意:
number_of_shards设定后是无法改变的,要修改索引的分片数量可以通过Reindex API或者收缩索引的 API 做处理。
4. Mapping设置
在创建索引的时候,可以指定索引的Mapping。
PUT huidong_order_v1
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
}
}
}
}
后期需求有了新的变化,想要增加索引的设置:
PUT huidong_order_v1/_mapping
{
"properties": {
"name": {
"type": "keyword"
}
}
}
注意:在 Mapping 中已经定义好的字段是不能修改的。
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "mapper [name] cannot be changed from type [keyword] to [text]"
}
]
},
"status": 400
}
5.ReIndex
如果Mapping中的字段已经定义好了类型,但是由于狗产品现在想修改需求,或者我想改变索引的分片数量,这个时候我是需要新建一个正确的索引然后进行数据迁移的,ReIndex就是帮助我们解决将数据从一个索引迁移到另一个索引的API。
假设现在我想将数据从huidong_test_v1索引迁移到huidong_test_v2索引。
POST _reindex?wait_for_completion=false
{
"source":{
## 旧的索引
"index":"huidong_test_v1",
"size":5000
},
"dest":{
## 新的索引
"index":"huidong_test_v2",
"op_type":"index"
},
"conflicts":"proceed"
}
如果索引中的数据很多,reindex的时间会很长,这种情况下同步等待其实会导致http请求超时,所以在上面的bash中添加了?wait_for_completion=false参数,这表示该操作需要异步执行,此时会返回一个TaskId,我们可以根据这个TaskId查询任务的执行情况。
GET _tasks/uhWJdRy3SkOy9ueR_-fbMg:28112265
当然ReIndex API还额外的支持很多参数,上面的例子只不过是日常开发的时候比较常用的操作,如果有其他的需求建议查询官方文档,作为研发我们应该可以熟练查找字典,而不是把自己变成一本字典。
6.索引模板
可以使用 Index templates 按照一定的规则对新创建的索引进行 Mapping 设定和 Settings 设定。需要注意的是,索引模板只在索引被新创建时起作用。
6.1 创建一个模板
PUT _template/huidong_test
{
"order": 0,
"aliases": {
},
"index_patterns": [
# 以huidong_test开头的索引都引用这个模板
"huidong_test*"
],
# 指定优先级,数值越大,优先级越高,这个模板就越先被应用。
"priority":1,
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "keyword",
"fields": {
"text":{
"type":"text"
}
}
},
"age":{
"type": "integer"
},
"occupation":{
"type": "text"
}
}
}
}
我们创建了一个索引的模板,当以后有 huidong_test 开头的索引创建时都引用这个模板。
模板中的 "priority" 指定了模板的优先级,其数值越大优先级越高,这个模板就越先被应用。
6.2 创建索引的时候指定模板
创建索引的时候在body里面指定aliases可以直接将索引与模板关联。
PUT huidong_test_v1
{
"aliases": {
"huidong_test": {}
}
}
7.启用&禁用索引
当我们需要执行某些操作的时候,需要禁用索引或者启用索引,可以使用 _close API 和 _open API 来启用或者禁用索引。
禁用索引的操作开销很小,并且会阻塞读写操作,禁用后的索引不再允许执行启用状态时的所有操作。禁用和启用一个索引的示例如下:
# 禁用索引
POST /test_index/_close
# 启用索引
POST /test_index/_open
8.收缩索引
如果我们一开始创建的索引分片设置大了,可以使用收缩索引的 API 将索引收缩为具有较少主分片的新索引。
收缩后的新索引的主分片数量必须为源索引主分片数量的一个因子,例如,源索引的主分片分配了 12 个,那么收缩后的新索引的主分片数只能为
1、2、3、4、6。
在进行索引收缩前需要进行以下操作:
- 源索引必须只读。
- 源索引所有的副本(主分片也行,副分片也行)必须在同一个节点上,也就是在这个节点上必须有这个索引的所有数据,不管分片数据是主分片的还是副分片的。
- 源索引的状态必须为健康状态(green)。 下面我们将拥有 12 个主分片、2 个副本分片的索引(huidong_order)收缩为拥有 3 个主分片和 1 个副分片的索引(huidong_order_new)。
先创建索引 huidong_order,设置 number_of_shards = 12,number_of_replicas = 2。
# 创建索引
PUT huidong_order
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"settings": {
"number_of_shards": 12,
"number_of_replicas": 2
}
}
下面将索引 huidong_order 所有的主分片转移到节点 ndoe_1 上,并且设置索引的副本分片数量为0、设置这个索引为只读状态。
PUT /huidong_order/_settings
{
# 分片分配到 node_1 节点
"index.routing.allocation.require._name": "my_node_1",
"index.number_of_replicas": 0,
"index.blocks.write": true
}
这个时候 huidong_order 满足了收缩索引的 3 个条件了,下面开始进行收缩。
POST /huidong_order/_shrink/huidong_order_new
{
"settings": {
"index.number_of_replicas": 1,
"index.number_of_shards": 3,
"index.routing.allocation.require._name": null, # 系统随机分配分片
"index.blocks.write": null # 不阻塞写操作
}
}
在Elasticsearch中,_shrink 操作本身是原子的,它会以原子方式将一个索引缩小为一个较小的目标索引。这意味着在 _shrink 操作执行期间,它会创建一个新的目标索引,并在完成后替换原始索引。这确保了在 _shrink 操作完成之前,不会出现部分缩小或中间状态的情况。
原子性确保了 _shrink 操作的可靠性和一致性。一旦 _shrink 操作成功完成,应用程序可以立即使用缩小后的目标索引,并可以确信所有的文档已经正确地迁移到目标索引中。
_shrink 操作的原子性仅适用于 _shrink 操作本身,而不包括与 _shrink 操作相关的其他操作。例如,如果在 _shrink 操作期间进行索引的读取、写入或其他修改操作,这些操作不受 _shrink 操作的原子性保护。
建议在执行 _shrink 操作时,尽量避免对目标索引进行其他操作,以确保数据的完整性和一致性。
9.开发中ReIndex的常用操作
9.1 修改模板
PUT _template/huidong_test
{
"order": 0,
"aliases": {
},
"index_patterns": [
"huidong_test*"
],
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "keyword",
"fields": {
"text":{
"type":"text"
}
}
},
"age":{
"type": "integer"
},
"occupation":{
"type": "text"
},
"hobby":{
"type": "text"
}
}
}
}
9.2 创建新的索引
PUT huidong_test_v2
{
}
9.3 数据迁移ReIndex
POST _reindex?wait_for_completion=false
{
"source":{
"index":"huidong_test_v1",
"size":5000
},
"dest":{
"index":"huidong_test_v2",
"op_type":"index"
},
"conflicts":"proceed"
}
9.4 将新索引与模板关联并将旧索引移除
POST /_aliases
{
"actions":[
{
"add":{
"index":"huidong_test_v2",
"alias":"huidong_test"
}
},
{
"remove_index":{
"index":"huidong_test_v1"
}
}
]
}
此时如果不删除旧的索引,旧的索引就不好删除了。
10. Reindex官方文档阅读
10.1 参数
1)URL参数
| 参数 | 注释 |
|---|---|
| refresh | 默认是false,目标索引是否立即刷新,也就是让数据立马可以搜索到。 |
| timeout | 默认为1m(一分钟)。这保证了Elasticsearch在失败前至少等待超时。实际等待时间可能会更长,尤其是在发生多次等待的情况下。 |
| wait_for_active_shards | 在继续操作之前必须处于活动状态的分区副本的数量。设置为all或任意正整数,最大值为索引中的分片总数(副本数+1)。默认值:1,主分片。 |
| Scroll | 滚动查询时快照的保留时间 |
| slicing | reindex并行任务切片 |
| Max_docs | 单次最大数据量,条数 |
| requests_per_second | 单次执行的重建文档数据量 |
2) 请求体参数
| 参数 | 注释 |
|---|---|
| confilicts | 索引数据冲突如何解决,直接覆盖还是中断,默认值:abort。 |
| source | 源索引配置信息 |
| dest | 目标索引的配置信息 |
| script | 脚本处理,修改源索引信息后再写入新索引 |
10.2 reindex高级设置
1)每秒数据量阈值控制
requests_per_second字段控制每秒迁移数据量的大小,默认不设置是1000,设置-1则表示没有限制;生产环境重建时,建议控制在500-1000这个范围内,控制重建速度,主要是为了防止集群瞬间IO过大。
target_time = 1000 / 500 per second = 2 seconds
wait_time = target_time - write_time = 2 seconds - .5 seconds = 1.5 seconds
2)手动切片
- 手动指定切片数量,并行任务。
- 用于降低索引reindex的速度。
POST _reindex
{
"source":{
"index":"old-index",
"slice":{
//执行下标,从0开始,即0切第一批数据做迁移,1切第二批数据做迁移
"id":0,
//切分分片数
"max":2
}
},
"dest":{
"index":"new-index"
}
}
3)自动切片
仅仅需要指定自动切片的大小即可,后续的调度交给ES异步处理。
POST _reindex?slices=5&refresh
{
"source":{
"index":"old-index"
},
"dest":{
"index":"new-index"
}
}
4)限制reindex重建数据的范围
① query
基于DEL语言规则编写,可以任意复杂,限制数据范围。
POST _reindex
{
"source": {
"index": "my-index-000001",
"query": {
"term": {
"user.id": "zhangsan"
}
}
},
"dest": {
"index": "my-new-index-000001"
}
}
② max docs
限制重建数据总条数,默认全部。
POST _reindex
{
"max_docs": 1,
"source": {
"index": "my-index-000001"
},
"dest": {
"index": "my-new-index-000001"
}
}
5) 多索引重建
Es支持将多个索引的数据合并到一个索引里面,如果出现Id冲突的情况,则会互相覆盖。
POST_reindex
{
"source": {
"index": ["my-index-000001", "my-index-000002"]
},
"dest": {
"index": "my-new-index-000002"
}
}
6)限制重建索引的字段
使用 _source 进行过滤。
POST_reindex
{
"source": {
"index": "my-index-000001",
"_source": ["user.id", "_doc"]
},
"dest": {
"index": "my-new-index-000001"
}
}
7)重建并修改字段名
基于Es脚本机制实现。
ES字段名称原始是不允许修改的,但通过脚本可以操作。
POST _reindex
{
"source": {
"index": "my-index-000001"
},
"dest": {
"index": "my-new-index-000001"
},
"script": {
// reindex && change field name from flag -> tag
"source": "ctx._source.tag = ctx._source.remove("flag")"
}
}
8) 重建并修改原始文档数据
同样是基于Es脚本实现。
POST _reindex
{
"source": {
"index": "metricbeat-*"
},
"dest": {
"index": "metricbeat"
},
"script": {
"lang": "painless",
"source": "ctx._index = 'metricbeat-' + (ctx._index.substring('metricbeat-'.length(), ctx._index.length())) + '-1'"
}
}
10.3 Reindex Routing
默认情况下,如果_reindex扫描到一个带路由的文档,除非用脚本更改,否则路由将会被保留。可以通过在dest参数上设置路由来改变这种情况。
keep:如果文档带有路由,就继续保留使用原来的路由。discard:即使有路由也将路由设置成null。=<some text>:等于号后面是什么内容就设置每一个文档的路由是什么。
For Example:将所有公司名称为cat的文档reindex到新的路由,并设置路由为cat。
POST _reindex
{
"source": {
"index": "source",
"query": {
"match": {
"company": "cat"
}
}
},
"dest": {
"index": "dest",
"routing": "=cat"
}
}
注意:路由设置的不好会导致集群中的数据发生倾斜,请谨慎设置。
默认_reindex使用的游标查询分页大小为1000,可以通过source里面的size字段来更改这个值。
POST _reindex
{
"source": {
"index": "source",
"size": 100
},
"dest": {
"index": "dest",
"routing": "=cat"
}
}
10.4 跨集群索引重建
- 基于集群通信,类同远程机制。
- 需要设置跨集群白名单,配置在dist集群。
POST _reindex
{
"source": {
"remote": {
"host": "http://otherhost:9200",
"username": "user",
"password": "pass"
},
"index": "my-index-000001",
"query": {
"match": {
"test": "data"
}
}
},
"dest": {
"index": "my-new-index-000001"
}
}
host参数必须包含scheme,host,port可以包含可选路径。username & password参数是可选的,当他们存在时,_reindex将使用安全连接到远程Es节点。使用基本身份验证时,请确保使用https请求,否则密码将以明文发送。- 必须使用
reindex.remote.whitelist属性在elasticsearch.yml中明确允许远程主机。他可以设置为允许的远程主机和端口组合的逗号分隔列表,scheme被忽略,仅仅使用IP和端口。
reindex.remote.whitelist: "otherhost:9200, another:9200, 127.0.10.*:9200, localhost:*"
必须在每一个协调节点上配置白名单。
Elasticsearch不支持跨主要版本的向前兼容性。例如,不能从7.x集群重新索引到6.x集群。
为了将请求成功发送到旧版本的机器,将不会对请求参数进行校验或修改。
跨集群的
reindex不支持manual or automatic slicing。
跨集群的reindex默认最大使用100MB的堆外内存,如果reindex的index中含有很大的文档,需要将每一批的size调小。以下示例是将批大小修改成10.
POST _reindex
{
"source": {
"remote": {
"host": "http://otherhost:9200"
},
"index": "source",
"size": 10,
"query": {
"match": {
"test": "data"
}
}
},
"dest": {
"index": "dest"
}
}
可以通过socket_timeout & connect_timeout参数设置跨集群的connect超时时间和socket缓冲区的超时时间,二者默认都是30s。下面的示例是将socket读超时时间设置为1分钟并且将连接超时时间设置为10s。
POST _reindex
{
"source": {
"remote": {
"host": "http://otherhost:9200",
"socket_timeout": "1m",
"connect_timeout": "10s"
},
"index": "source",
"query": {
"match": {
"test": "data"
}
}
},
"dest": {
"index": "dest"
}
}
10.5 Reindex原理分析
Reindex 操作其实就是将数据从一个索引复制到另一个索引,本质其实就是数据的读写。
1)Reindex读操作
数据的读取和复制其实最快的操作就是将数据文件从一个索引复制到另一个索引,但是这要建立在目标索引和源索引主分片数一致的前提。Reindex操作默认需要读取全量的数据,而具体的数据读取操作其实是使用的Scroll查询。
Reindex的源码在一个单独的module下,通过TransportReindexAction的doExecute方法将task转换为BulkByScrollTask类型,然后将转换后的task交给Reindexer的execute方法,而execute方法实际上执行的是executeSlicedAction方法,可以将任务切分,并行获取数据。在每一个并行化的操作中,启动一个异步的searchAction进行数据获取。
2)Reindex写操作
数据写入的时候会先路由到对应的处理节点,然后将数据写入到Index Buffer,再写入相关记录到 事务日志,并且返回成功。默认情况下,Refresh操作每秒执行一次,将Index Buffer中的数据写入到文件系统中,并生成Segment文件。当达到了触发条件,程序还会进行Flush和Merge操作。
Reindexer在读取到数据以后,会通过静态内部类AbstractAsyncBulkByScrollAction构建bulk请求然后发送请求到对应的节点中进行处理。
10.6 Reindex性能优化
实际开发中如果使用Reindex API 重建数据量较大的索引,其实默认性能很拉胯,即使是同集群的Reindex,速度传输也仅仅只有M/S级别。
通过上一小节我们可以知道Reindex读操作底层其实就是可以并行的Scroll查询,写操作底层其实就是异步bulk请求。有了这些信息,我们就可以对Reindex进行优化,提高执行效率,其实调优的关键就是对于读写的控制。
1)大胆假设
Reindex API 为什么会慢呢?
- 批量写的参数设置不合理,比如bulk一批数据量太大,比如触发写策略没有调整好。
- 并行Scroll读操作在不同数据量下面没有控制好并发度。
- 硬件配置 | 网络带宽 垃圾。
抛开硬件|网络带宽,我们考虑从读写两个方面优化。
2)读操作优化
通过并行的Scroll操作读取数据。并行读的方式有两种:手动和自动;上面已经介绍过了。
虽然使用 Slicing 可以提高 Reindex 的效率,但如果使用不当,效果可能会适得其反。下面是几个 Slicing 设置的注意事项:
- slices 除了可以设定为数字外,slices 也可以设置为
auto,设置为 auto 表示:如果源索引是单索引,则 slices = 源索引的主分片数量值;如果源索引是多索引,则 slices = 各个源索引中最小的主分片数量值。 - slices 的值并不是越大越好的,过大的 slices 会影响性能。slices 的值等于源索引主分片数量值的时候效率会最高,当 slices 大于源索引主分片数量值时,不会提高效率,反而会增加开销,
没有特定需求的情况下,slices 设置为 auto 即可。
3)写操作优化
- 设置合适的bulk大小。
- 设置目标索引的副本数为0。
减少副本数量可以提高写入的效率,在数据 Reindex 完成后,再设置需要的副本数,这样系统会自动创建出需要的副本数。
- 调整
index.refresh_interval。
减少
Index Refresh的次数可以减少生成 Segment 的数量,也减少了 Merge 的频率。默认的情况下,ES Refresh操作会每秒进行一次,可以通过调整index.refresh_interval的值来调整 Refresh 的时间间隔。可以将
refresh_interval设置为 -1 来关闭 Refresh,当然在Index Buffer写满时还是会进行 Refresh的。在 Reindex 完成后,需要把这个设置回原来的值。
- 加大 Translog Flush 的间隔。
为了防止数据丢失,保证数据的可靠性,默认的情况下是每个请求
Translog都刷盘。如果我们是在导入数据的应用场景,那么为了提高写入的性能,可以不每个请求都对Translog进行刷盘。
如何改变Translog的设置?
PUT /myindex/_settings
{
"index.translog.durability":"async",
"index.translog.sync_interval": "240s",
"index.translog.flush_threshold_size": "512m"
}
async 是指异步刷盘,每隔 index.translog.sync_interval 进行刷盘。当然,当 Translog 的量达到 flush_threshold_size 的时候也会触发刷盘。