网站建设的主要功能及定位,珍岛网站建设,全市网站建设情况摸底调查,购买网址ES 7.6 - API高阶操作篇 分片和副本索引别名添加别名查询所有别名删除别名使用别名代替索引操作代替插入代替查询 场景实操 滚动索引索引模板创建索引模板查看模板删除模板 场景实操一把索引的生命周期数据迁移APIGEO(地理)API索引准备矩形查询圆形查询多边形查询 自定义分词器… ES 7.6 - API高阶操作篇 分片和副本索引别名添加别名查询所有别名删除别名使用别名代替索引操作代替插入代替查询 场景实操 滚动索引索引模板创建索引模板查看模板删除模板 场景实操一把索引的生命周期数据迁移APIGEO(地理)API索引准备矩形查询圆形查询多边形查询 自定义分词器总结 分片和副本
只会CURD的boy可能对es的分片和副本概念都很模糊更别提要怎么对一个索引设置一个合适的分片和副本大小了
分片你可以认为是一个存储数据库有几个分片就有几个库本质上是将数据分片存储达到更好的性能和容灾效果
副本你可以认为是分片的从库用来同步主分片的数据平时不接受写的请求但可以接受读的请求
怎么设置这两者的数量呢假设ES集群有三个节点那么分片数设置为3副本设置为2
为什么这么设置 首先ES是一个内存怪兽性能全靠内存一个分片的数据能全放内存里面这是性能最高的所以一个节点最好只放一个分片为什么要副本分片因为节点有可能宕机如果没有副本一旦宕机就失去了该主分片的数据读写能力了有了副本主分片挂了副本还能升级为主对外提供服务像下面这个图一样无论哪个节点宕机都不会造成太大的影响至于数据丢失等问题不在本文讨论范围内 如下图所示 怎么设置呢在创建索引的时候就可以设置了
PUT http://{{es_ip}}:{{es_port}}/xxxx(索引名称)
{settings:{index: {number_of_shards: 1, // 主分片数number_of_replicas: 1 // 每个分片的副本数}}
}
索引别名
别名是干嘛的顾名思义就是可以替代索引的名称做一些操作举个例子 索引的设置和mapping一旦创建好后是不能被修改的但是后期扩容、字段类型变更怎么办只能重新创建一个索引然后把旧的索引数据迁移过来吧这要是停机迁移那用户不得裂开 这时候别名的好处就体现出来了别名就等于是索引的一层代理像上面那个场景我只需要改一下别名的指向就搞定了多说无用直接实操 注意一个索引可以用多个别名一个别名也可以赋给多个索引 添加别名
三个添加方式唯一需要注意的就是is_write_index这是干嘛的 想想别名可以同时赋予多个索引条件查询的时候好说但插入的时候呢我要是用别名用来插入我咋知道要写入哪个索引呢这个用处就是这个标识写入哪个要是别名下只有一个索引的话则不需要指定默认写入就好比一个没有负载均衡的代理 # 创建索引时直接添加别名 如下我为alias_test2 添加了一个alias_test别名
PUT http://{{es_ip}}:{{es_port}}/alias_test2(索引名称)
aliases: {alias_test: {}
}# 创建索引后为索引添加别名
// 1. 先创建索引
PUT http://{{es_ip}}:{{es_port}}/alias_test1 // 先创建索引
// 2.后为这个索引添加一个别名
PUT http://{{es_ip}}:{{es_port}}/alias_test1(索引名称)/_alias(别名命令)/alias_test(别名名称)# 使用别名命令 批量添加
POST http://{{es_ip}}:{{es_port}}/_aliases(别名命令)
{actions: [{add: {index: alias_test2, // 索引alias: data_alias, // 别名is_write_index:true, // 可写代表用data_alias别名写入的时候写入这个alias_test2索引filter:{ // 可以控制只访问这个索引的部分数据比如这里就是只能访问id10的数据range:{id:{gte:10 }}}}}]
}
查询所有别名
GET http://{{es_ip}}:{{es_port}}/_alias 删除别名
# 根据索引删除别名
DELETE http://{{es_ip}}:{{es_port}}/alias_test2(索引名称)/_alias/alias_test(别名名称)# 用别名命令删除
POST http://{{es_ip}}:{{es_port}}/_aliases
{
actions: [{remove: {index: alias_test2,alias: alias_test}}]
}
使用别名代替索引操作
代替插入
现在我们alias_test别名下只有alias_test2索引我们用alias_test2别名来插入个文档方式和用索引插入一个文档是一样的此时可以插入 我们再给alias_test1索引添加alias_test别名再插入试试就会报错要怎么解决呢
第一种插入不用别名而是用对应索引名称第二种那就是is_write_index 第一种毋庸置疑咱们试试第二种给test2加上 然后就可以正常插入了 代替查询
查询可以说一点影响没有直接查就好了现在alias_test别名下有两个索引所以用这个别名查询的时候能同时查询两个索引的数据所以这也是别名的好处之一 场景实操
怎么无缝迁移切换索引 首先前提条件有一个索引(old_index)有一个别名(proxy_index)代码中插入和查询的操作都是针对这个别名操作的因为这别名下只有这一个索引 好现在要迁移了把这个索引数据迁移到新的并无缝切换
创建一个新的索引new_index为新的索引设置别名并指定写入此时写入的数据会写到新索引查询会查询两个索引不影响
POST http://{{es_ip}}:{{es_port}}/_aliases
{
actions: [{add: {index: new_index,alias: proxy_index,is_write_index:true}}]
}然后数据迁移把(old_index)数据迁移到new_index最后删除(old_index)
以上看似好像很合理对吧但是有个致命的问题指定新的别名写入后那根据ID修改、根据ID删除咋办
我的建议是使用_delete_by_query和_update_by_query命令来代替尽量不要用指定ID的处理
如果实在不行可以看看下面的讨论 这个不能走别名呀因为别名下新索引的数据还在迁移过程中是找不到数据的所以也有人的方案是把上述流程的2、3步换一下即先转移数据转移后再切这样的话所有操作都针对别名就行了但是这个我个人觉得还是有个问题假设迁移数据完成后设置别名前刚好又有数据写入了或者刚迁移完的数据又马上被更新了这就有几率导致新老索引数据不一致了这还需要一个数据check任务去校验我觉得也很蛋疼那要怎么解决呢 我觉得可以这样流程上述不变根据ID修改、根据ID删除一样走别名在没迁移的过程中这样是没问题的在迁移的时候就有可能出现文档找不到的错误我们在代码层面捕捉这个错误然后再用索引去执行一次就OK了等于就是用别名找不到的情况下用索引去找索引切换后老的索引名会失效索引名称可以做成动态配置的 找不到时报错信息如下 好了别名的妙用我就写到这了咱们开启下一趴
滚动索引
哈这玩意又是干嘛的… 咱们试想一个场景虽然现在有了分片了但是单个分片数据量还是很很很大可能包含了几年的数据但是我们平时搜索一般都是最近一年的数据这就意味着这些老数据会一直影响我们的查询性能而且这么大的数据量也导致我们维护啊、迁移啊诸多一遍能不能让单个分片数据量再缩小一点呢要是把分片比作分库那能不能做一个类似分表的操作呢比如 xxx_table_1、xxx_table_2这样 ε(´ο*)))唉聪明的大兄弟可能想到了切索引不就好了吗…只要索引结构一致那不就是分表嘛但是什么时候切呢以什么为标准切呢谁去切呢这时候就得用上滚动索引API了这个的本质呢就是可以设置一些阈值然后在执行这个API的时候呢会判断是否达到了这个阈值如果达到了就自动帮你创建一个新的索引后续的写入就会写到这个新的索引里面基于别名
网上找了个图 具体要怎么做呢就以下几步
创建一个新的索引并设置好别名和mapping
PUT http://{{es_ip}}:{{es_port}}/logs-1
{aliases: {rollover_test: {}},mappings:{properties: {id:{type:long},name:{type:text,analyzer:ik_max_word},remark:{type:text,analyzer:ik_max_word}}}
}
插入几条数据先
POST http://{{es_ip}}:{{es_port}}/rollover_test/_bulk
{index: {_id: 1}}
{id:1,name:滚动测试1,remark:asdfasdgas爱}
{index: {_id: 2}}
{id:2,name:滚动测试2,remark:asdfasdgas爱}
{index: {_id: 3}}
{id:3,name:滚动测试3,remark:asdfasdgas爱}
{index: {_id: 4}}
{id:4,name:滚动测试4,remark:asdfasdgas爱}
执行滚动API
# 试运行实际不会执行可以查看执行后的结果
POST http://{{es_ip}}:{{es_port}}/rollover_test(上面索引的别名)/_rollover?dry_run# 直接执行
POST http://{{es_ip}}:{{es_port}}/rollover_test(上面索引的别名)/_rollover
{conditions: {max_age: 7d, // 天数超过7天滚动一次(创建新索引)max_docs: 2, // 文档数超过2个文档就滚动一次max_size: 5gb // 索引大小超过5G滚动一次}
}
返回结果
{acknowledged: true,shards_acknowledged: true,old_index: logs-1,new_index: logs-000002, // 这个就是新的索引名称新的索引是没有数据的它并不会转移数据rolled_over: true,dry_run: false,conditions: {[max_size: 5gb]: false,[max_docs: 2]: true, // 我们刚刚插入了4条满足了这个条件所以为true[max_age: 7d]: false}
} 查看效果 你会发现别名已经转移到了新的索引上面老的索引已经没有别名了 当我们查看新索引的结构时你会发现结构居然全是默认的和老的索引都不一样而且别名也都迁移过来了就代表用别名查询不到之前老索引的数据了呀这不得出大事要咋办呢不合理绝对不合理
要解决这个问题其实也很简单就是还需要一个索引同时在执行滚动API的时候同时给新索引添加上去包括结构啥的就像
{conditions: {max_age: 7d, // 天数超过7天滚动一次(创建新索引)max_docs: 2, // 文档数超过2个文档就滚动一次max_size: 5gb // 索引大小超过5G滚动一次},........ // 这里添加新索引的配置
}但是这样也不是非常的合适能不能自动的就给新增的索引加上配置呢每次这样多麻烦啊索引结构一一变这里也得变。
所以呀这时候就需要聊聊索引模板这个东西了 **注意**这种方式几乎全在操作别名需要注意用ID操作的问题 索引模板
创建索引模板
PUT http://{{es_ip}}:{{es_port}}/_template(模板命令)/template_1(模板名称)
JSON传参
{// 索引名称匹配这里代表匹配所有logs-开头的索引// 意味了创建这样名称的索引的时候会自动加上下面的配置index_patterns:[ logs-*],settings:{ // 索引的设置number_of_shards:1},aliases : { // 别名log_all : {}},mappings: { // 映射结构_source: {enabled: false},properties: {id:{type:long},name:{type:text,analyzer:ik_max_word},remark:{type:text,analyzer:ik_max_word}}},// 优先级假设一个索引同时匹配了多个模板则会按照这个顺序依次加载// 越大优先级越高高的配置会覆盖低的order:0
}
咱们新增这个一个模板再滚动一次上面的看看结果还是先插入4条数据,然后滚动
# 插入数据
POST http://{{es_ip}}:{{es_port}}/rollover_test/_bulk
{index: {_id: 1}}
{id:1,name:滚动测试1,remark:asdfasdgas爱}
{index: {_id: 2}}
{id:2,name:滚动测试2,remark:asdfasdgas爱}
{index: {_id: 3}}
{id:3,name:滚动测试3,remark:asdfasdgas爱}
{index: {_id: 4}}
{id:4,name:滚动测试4,remark:asdfasdgas爱}# 滚动 (刚插入数据会有一段时间才会刷新这个立马执行这个滚动不一定成功)
POST http://{{es_ip}}:{{es_port}}/rollover_test/_rollover这时候咱们再来看看这个新的索引logs-000003结构,可以看到结构啊别名啊都有了以后每次滚动log_all这个索引都会给新的索引这样咱们用这个别名查询就可以查询所有的索引啦 查看模板
# 一次性查询所有templat开头的模板
GET http://{{es_ip}}:{{es_port}}/_template(模板命令)/templat*(模板名称匹配)# 只查询一个模板
GET http://{{es_ip}}:{{es_port}}/_template(模板命令)/template_1(模板名称)
删除模板
# 一次性删除所有templat开头的模板
DELETE http://{{es_ip}}:{{es_port}}/_template(模板命令)/templat*(模板名称匹配)# 只删除一个模板
DELETE http://{{es_ip}}:{{es_port}}/_template(模板命令)/template_1(模板名称)
场景实操一把
大家可能经常看到一些索引为日期命名每天更新就类似以下这种
xxx-2023-01-01-000001
xxx-2023-01-01-000002
xxx-2023-01-02-000003
流程如下
创建日志索引索引名格式有点区别了这里用这个logs-{now/d}-000001需要编码一次可以用这个网站编码网站
PUT http://{{es_ip}}:{{es_port}}/%3Clogs-%7Bnow%2Fd%7D-000001%3E(索引名称需要编码)
JSON传参
{aliases: {logs_rollover: {}, // 这个是用来滚动的别名logs_query:{} // 这个是用来给所有滚动的日志索引添加的别名便于搜索所有},mappings:{properties: {id:{type:long},name:{type:text,analyzer:ik_max_word},remark:{type:text,analyzer:ik_max_word}}}
}添加一个模板
PUT http://{{es_ip}}:{{es_port}}/_template/template_1
JSON传参
{index_patterns:[ // 匹配所有日志索引logs-*],settings:{number_of_shards:1},aliases : {logs_query : {} // 日志全局索引别名},mappings: {_source: {enabled: false},properties: {id:{type:long},name:{type:text,analyzer:ik_max_word},remark:{type:text,analyzer:ik_max_word}}},order:0
}添加几条数据
POST http://{{es_ip}}:{{es_port}}/logs_rollover/_bulk
{index: {_id: 1}}
{id:1,name:滚动测试1,remark:asdfasdgas爱}
{index: {_id: 2}}
{id:2,name:滚动测试2,remark:asdfasdgas爱}
{index: {_id: 3}}
{id:3,name:滚动测试3,remark:asdfasdgas爱}
{index: {_id: 4}}
{id:4,name:滚动测试4,remark:asdfasdgas爱}
执行滚动
POST http://{{es_ip}}:{{es_port}}/logs_rollover/_rollover
{conditions: {max_age: 7d, // 天数超过7天滚动一次(创建新索引)max_docs: 2, // 文档数超过2个文档就滚动一次max_size: 5gb // 索引大小超过5G滚动一次}
}查看新索引结构 查看别名 这就搞定了你可以发现所有日志的索引名称都是非常标准、统一的格式但这样需要注意的是插入只能用别名logs_rollover查询只能用别名logs_query不要用带文档ID的操作 看起来很简单是吧但你以为这样就完了吗。。。。。。。。。想想这样还有什么缺点
滚动需要人为操作目前别名查询的是所有数据但完全可以根据时间建很多个别名如7天、一个月、季度、年这样是不是会更高效时间已经很久的冷数据怎么办…等等
场景需要灵活运用…下面提一下索引的生命周期
索引的生命周期
上述说的场景在以前可能都是定时脚本解决的但是现在索引有了生命周期LLM管理可以自动的帮我们做很多的事这种偏运维、也不太好实操就放个链接给大家简单了解一下吧
ES ILM实践
数据迁移API
如果要迁移索引的数据我是建议用这个_reindex命令简单示例,并提供几个重要操作
# 默认同步迁移、单任务执行
POST http://{{es_ip}}:{{es_port}}/_reindex# slices并行数(最好和主分片数一致) wait_for_completion异步执行
POST http://{{es_ip}}:{{es_port}}/_reindex?slicesxwait_for_completionfalse
{source: {index: old_index_name, // 旧的索引名称size: 5000, // 每次迁移的文档数量这里就是一次批量转移5000个query: { // 条件迁移只迁移条件匹配的数据term: {user: kimchy}}},dest: {index: new_index_name, // 新的索引名称version_type: internal // 版本类型 }
}
查看任务进度
# 如果是异步执行的会返回一个任务名称可以根据这个名称查询任务信息
GET http://{{es_ip}}:{{es_port}}/_tasks/(任务名称)GEO(地理)API
这个就是地理经纬度相关API比如附近的人某个地址附近的店最近距离等等很多人可能用Redis的GEO来实现但Redis对于数据的存储量来讲可远远比不上ES
索引准备
操作和普通的其实都差不多GEO无非就是一个特殊的字段类型我们先创建一个索引
PUT http://{{es_ip}}:{{es_port}}/geo_test
{mappings:{properties: {name:{type:text,analyzer:ik_max_word},location: {type: geo_point // GEO数据类型}}}
}随意准备一点数据想要更可观可以自己去地图上捞一些点我这里就随意了哈
http://{{es_ip}}:{{es_port}}/geo_test/_bulk
JSON 传参:
{index: {_id: 1}}
{name:唐聪健1, location : { lat : 40.12, lon : -71.34 }}
{index: {_id: 2}}
{name:唐聪健2, location : { lat : 50.12, lon : -60.34 }}
{index: {_id: 3}}
{name:唐聪健3, location : { lat : 60.12, lon : -50.34 }}
{index: {_id: 4}}
{name:唐聪健4, location : { lat : 70.12, lon : -40.34 }}
{index: {_id:5}}
{name:唐聪健5, location : { lat : 80.12, lon : -30.34 }}
{index: {_id: 6}}
{name:唐聪健6, location : { lat : 85.12, lon : -20.34 }}
{index: {_id: 7}}
{name:唐聪健7, location : { lat : 35.12, lon : -67.34 }}
{index: {_id: 8}}
{name:唐聪健8, location : { lat : 55.12, lon : -55.34 }}
矩形查询
就是查询在一个矩形的框框内有哪些点
GET http://{{es_ip}}:{{es_port}}/geo_test/_search
{query: {bool : {must : {match_all : {}},filter : {geo_bounding_box : { // 矩形的命令location : { // 要查询的字段一定要是GEO类型top_left : { // 矩形左上角的点经纬度lat : 80.73,lon : -30.1},bottom_right : { // 矩形右下角的点经纬度lat : 40.01,lon : -30.12}}}}}}
}
圆形查询
就是查询以一个点为中心半径多少的一个圆形内有多少个点
GET http://{{es_ip}}:{{es_port}}/geo_test/_search
{query: {bool : {must : {match_all : {}},filter : {geo_distance : {distance : 1000km, // 半径 单位km、m、cm、mm、nmi、mi、yd、ft、indistance_type: arc, // arc默认的方式这种方式计算比较精确但是比较慢 plane这种方式计算比较快但是可能不怎么准越靠近赤道越准location : { // 圆心点lat : 40,lon : -70}}}}},sort: [ {_geo_distance: { // 根据与下面点的的距离排序location: {lat : 40,lon : -70},order: desc,unit: m, // 单位米distance_type: arc}}]
}多边形查询
GET http://{{es_ip}}:{{es_port}}/geo_test/_search
{query: {bool : {must : {match_all : {}},filter : {geo_polygon : { // 多边形命令location : {points : [ // 点集合{lat : 40, lon : -70}, // 多边形点位经纬度{lat : 60, lon : -60},{lat : 20, lon : -20}]}}}}}
}自定义分词器
之前咱们环境搭建的时候搞了一个IK分词器是吧但是你会发现百度输入个拼音就出来东西了想达到这个效果咱们还得去搞个拼音分词器可以GitHub上面下一个地址传送门 像之前安装ik一样搞进去压缩重启es就ok了看看效果
GET http://{{es_ip}}:{{es_port}}/_analyze{
text: 分词测试,
analyzer: pinyin
} 好像还不错确实用拼音分词了但是感觉差点意思啊这都变成一个一个的拼音啊我们应该是要分词的拼音然后中文呢难不成为了拼音舍弃中文分词又或者要搞两个字段一个中文分词一个拼音分词
肯定不合理所以我们要自定义分词集两者为一体
想要自定义分词首先就得了解分词器的一丢丢原理了有三个重要的部分
部分含义Character Filter在分词之前对原始文本进行处理例如去除 HTML 标签或替换特定字符。Tokenizer定义如何将文本切分为词条或 token。例如使用空格或标点符号将文本切分为单词Token Filter对 Tokenizer 输出的词条进行进一步的处理例如转为小写、去除停用词或添加同义词。
为了更好的理解这里贴上一张网图 看了这个图是不是就很清晰了要达到我们的效果只需要Tokenizer部分用IK分词器Token Filter部分用拼音分词器是不是就搞定了下面咱们实操一把
# 创建自定义分词的索引
PUT http://{{es_ip}}:{{es_port}}/my_analyzer_test
{settings: {analysis: {analyzer: {my_analyzer: { // 自定义的分词名称tokenizer: ik_smart, // 这个就是 Tokenizerfilter: [py_filter // 过滤器]}},filter: {py_filter: {type: pinyin,keep_full_pinyin: false, // 拼音默认是一个字一个字的分词拼音所以要关了keep_joined_full_pinyin: true, // 按照词语拼音remove_duplicated_term: true, // 删除重复的拼音keep_original:true // 保留原始的输入也就是保留汉字的分词}}}},mappings: {properties: {name: {type: text,analyzer: my_analyzer // mapping这里的分词就要选择我们自定义的分词名称}}}
}分词测试:
# 注意自定义分词是只属于索引的索引这里分词命令前面要加上索引的名称
GET http://{{es_ip}}:{{es_port}}/my_analyzer_test/_analyze{
text: 分词测试,
analyzer: my_analyzer // 自定义分词的名称
}
大功告成总结 本文讲了很多关于ES的进阶用法让你不再局限于CURD但是灵活度也就更高了实际中什么场景用什么样的方案这就得你自己来把控了本来还想写一些关于这些高阶API的Java应用层面的使用最后想想还是算了客户端得自己去摸索摸索才会更深刻 好了到了这我相信你比CURD boy应该更上一层楼了但是你以为这就完了才开始呢少年
这些东西都是ES整体中的冰山一角更多的东西需要你自己去摸索、去看文档了相信有了这些作为基础文档你也基本能搞懂了下面贴一些文档地址
官方文档我推荐是看这个下面参考用ES客户端文档ES API中文文档这个我不知道是什么版本的ES参考就好一个ES 中文教程网站我同样不知道什么版本的参考就好
后续会分享一些关于ES的原理以及必须要知道的知识点理论加上实践你才能得到升华