目录
七、ES SDK及增删改查Demo
一、背景
二、内容概要
-
elasticsearch版本选择对比,包括sdk的选择
-
elasticsearch多租户设计方案,逻辑隔离与物理隔
-
elasticsearch集群搭建,安全设置
-
elasticsearch 增删改查Demo及sdk的封装
-
kibana介绍与搭建
-
elasticsearch常见运维方法及操作命令
三、基础概念
PUT test001
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text",
"analyzer": "ik_max_word"
},
"age": {
"type": "integer"
}
}
}
}
四、ES版本选择
注:由于elasticsearch各版本差异较大,所以在调研及给出具体的方案前我们需要确定使用的es版本
通过大量资料搜索对比了对各个版本差异,最终选择7.10比较稳定的版本来使用。 es从5.x、6.x、7.x、8.x设计及sdk差异都比较大,5和6版本比较老了,与现在spring boot 2.x版本集成存在问题,8.x比较新才出一两年也有比较多的license限制,所以最终选择了7.10版本。
elasticsearch版本选择
-
结论 :优先选择 7.10.0
-
原因对比如下
类型\版本 | 6.x | 7.x | 8.x | 建议 |
Licence | Apache 2.0 |
7.0 ~ 7.10 Apache 2.0
7.11++ SSPL
|
SSPL
|
建议选择更友好的Apache2.0版本,sspl对于在云上想要让ES as a Service,将会面临es厂商的限制
|
云厂商支持程度
| 腾讯、阿里云均支持, 华为不支持 |
腾讯云最高版 7.10.x
阿里云7.10.x,7.16.x
华为云7.6.x, 7.10.x
|
均不支持
|
各云厂商也主要在推广7.x版本,稳定性及占用率更高,建议选择7.x中的7.10.0版本
|
发版时间
| 初版2016 |
2019年
|
2021年底
|
建议选择
7.x版本,经历将近4年,稳定性已经经过验证,6.x和8.x一个太老一个太新
|
特性差异
| / | 集群配置简化,master选举进行了优化, 能够避免集群脑裂问题; 索引创建已经去除了type,更加简化; 索引查询算法升级,查询性能有优化; 提供安全策略; Kibana更轻量化,更易用; |
ES API进行了升级方便后续升级使用;
更加安全,es默认开启了一些安全功能;
新的搜索API 特性,比如支持NLP等;
|
7.x基本也能满足目前需求,稳定性也更有保障
|
2.1 ~ 2.2版本对6.x支持 |
2.3 ~ 2.7版本对7.x支持
| / |
基于目前应用中使用的spring boot版本,只能选择
7.x版本
|
ES客户端SDK版本选择
-
结论 : 使用spring官方提供的spring-boot-es-starter
客户端 | 适用版本 | 优点 | 缺点 | 建议 |
TransportClient | 5.x 6.x | 启动速度快,轻量级,可创建极多连接,与应用程序解耦;推荐使用原生的,ES本身就很简单,灵活性很高 | 分发或查询数据速度较慢,不能获取指定节点数据,高版本已经废弃 | 不建议使用 |
JestClient | 5.x 6.x 7.x | 提供Restful API, 原生ES API不具备;若ES集群使用不同的ES版本,使用原生ES API会有问题,而Jest不会;更安全(可以在Http层添加安全处理);JestClient是ElasticSearch的Java HTTP Rest客户端; JestClient填补了 ElasticSearch缺少HttpRest接口客户端的空白; JestClient可以跨版本 | 18年已经停止更新,7.x、8.x版本兼容性存疑 | 不建议使用 |
RestClient low-level-rest-client | 5.0++ | 基于Http Client 进行的简单封装,RestClient可以跨版本,支持到目前8.x所有版本。 | HttpClient和Jsoup都不直接支持发送DELETE方法带参数的请求(官方高版本已经放弃使用)。使用成本较高 | 不推荐 |
high-level-rest-client | 7.2.0 - 7.16.x | 官方基于RestClient进行的封装,提供一系列API方便对ES的使用 | 在7.17版本后官方又废弃了 | 7部分版本推荐使用 |
New ElasticsearchClient | 7.17++ | 为最新官方提供的版本 | 较高版本es适用 | 8.x官方推荐使用 |
spring-boot-es-starter | 3.0++ | spring官方封装的ES api,使用起来相对简单,也spring兼容性也能保障,教程也比较多。 | 需要与使用的es版本进行匹配 | 推荐使用 |
五、Elasticsearch多租户
-
es本身没有租户的能力,对es来说都是一个一个独立索引,这些索引默认都存储在一个es集群上,考虑后面ES as a Service,即作为PaaS能力来提供服务时,就不得不加入租户设计,这也是云时代iaas、paas、saas所面临的共同问题。通过调研市面上并无较多可参考的设计及直接可用的es多租户plugin,基于此需要自行设计支持。
多租户下的架构
-
多租户架构往往我们要提供逻辑隔离和物理隔离两种能力,根据需求并可以灵活定制某一租户是独享集群还是共享集群,当独享集群则是物理隔离,当共享集群时就是逻辑隔离,无论哪种方式都要做到部署架构能够低成本支持,应用层对集群无感;
租户的识别和路由
-
逻辑隔离代表着所有索引在同一个ES集群上,此时可以通过在索引上加一个tenant字段,通过字段隔离业务数据
-
物理隔离则可以前置一个负载均衡,比如使用Nginx来完成往不同的ES集群分发请求,不同的集群有不同的地址,应用发起请求时根据请求中的参数进行路由
-
考虑方便路由及管理,团队内可以定制租户及索引名统一命名规范,比如租户cod: tenantxx,文章索引名为article,完整索引名为:tenantxx_article
-
六、ES 集群搭建
-
生产环境linux下Es 不能使用root启动,需要给es创建独立用户
集群部署架构
单集群示例
-
集群每个节点都可以设置为可参与master选举及是否是数据节点
多集群示例
集群访问规范
ES集群搭建配置
es官方下载地址,es和kibana尽量下载同一版本
-
elasticsearch各版本下载地址
https://www.elastic.co/cn/downloads/past-releases#elasticsearch
-
kibana (es的可视化管理工具)
https://www.elastic.co/cn/downloads/past-releases/#kibana
1、先搭建单个节点
先找任一个节点修改elasticsearch.yml,并添加以下配置(此时不要添加集群和证书配置)
path.data: /opt/data
path.logs: /opt/logs
#http访问端口,程序或kibana使用
http.port: 9200
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
2、启动ES
-
观察启动是否ok,若ok进行第三步
./bin/elasticsearch
3、设置安全账号信息(ES要启动状态)
-
执行以下命令,给各账号设置密码, 整个集群只需要设置一次即可
-
警告:设置账户密码切记要在单实例非集群模式时配置,不能添加任何集群的配置,否则会设置失败
bin/elasticsearch-setup-passwords interactive
4、添加配置集群配置信息
-
集群搭建需要开启安全,开启安全需要用到证书,证书生成参考【ES集群安全策略步骤进行生成】,生成后进行以下配置
-
elasticsearch.yml加入以下配置
#数据和日志存储路径
path.data: /opt/data
path.logs: /opt/logs
#数据备份和恢复使用,可以一到多个目录
path.repo: ["/opt/backup/es", "/opt/backup/es1"]
#http访问端口,程序或kibana使用
http.port: 9200
#集群名称
cluster.name: es001
#节点名,每个节点名不能重复
node.name: node1
#是否可以参与选举主节点
node.master: true
#是否是数据节点
node.data: true
#允许访问的ip,4个0的话则允许任何ip进行访问
network.host: 0.0.0.0
#es各节点通信端口
transport.tcp.port: 9300
#集群每个节点IP地址。
discovery.seed_hosts: ["xxx.xx.xx.xx:9300", "xx.xx.xx:9300", "xx.xx.xx:9300"]
#es7.x新增的配置,初始化一个新的集群时需要此配置来选举master
cluster.initial_master_nodes: ["node1", "node2", "node3"]
#配置是否压缩tcp传输时的数据,默认为false,不压缩
transport.tcp.compress: true
# 是否支持跨域,es-header插件使用
http.cors.enabled: true
# *表示支持所有域名跨域访问
http.cors.allow-origin: "*"
http.cors.allow-headers: Authorization,X-Requested-With,Content-Type,Content-Length
#集群模式开启安全 https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.license.self_generated.type: basic
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: certs/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: certs/elastic-certificates.p12
#默认为1s,指定了节点互相ping的时间间隔。
discovery.zen.fd.ping_interval: 1s
#默认为30s,指定了节点发送ping信息后等待响应的时间,超过此时间则认为对方节点无响应。
discovery.zen.fd.ping_timeout: 30s
#ping失败后重试次数,超过此次数则认为对方节点已停止工作。
discovery.zen.fd.ping_retries: 3
5 、jvm.options
-Xms6g
-Xmx6g
sudo vim /etc/sysctl.conf
#添加参数
vm.max_map_count = 262144
#重新加载配置
sysctl -p
6、配置集群其它节点
-
使用系统命令查看集群节点数量
curl -XGET -u elastic:elastic123 "http://127.0.0.1:9200/_cluster/health?pretty"
-
创建一个索引看集群中每个节点索引数据
-
1、可以在任间一个节点执行创建索引命令
curl -XPUT -u elastic:elastic123 "http://127.0.0.1:9200/test-index"
-
2、查看所有节点索引是否一致
curl -XGET -u elastic:elastic123 "http://localhost:9200/_cat/indices?pretty"
-
7、关闭索引自动创建
-
待es集群搭建完启动成功后,执行以下命令可以关闭索引自动创建功能,当然也可以不关闭,不关闭的话es sdk保存数据时候如果索引不存在则会自动创建索引,尽量还是关闭由专门负责人统一来管理索引
PUT _cluster/settings { "persistent": { "action.auto_create_index": "false" } }
PUT _cluster/settings { "persistent": { "action.auto_create_index": "false" } }
8、修改es集群索引默认分片数
PUT _template/template_http_request_record
{
"index_patterns": [
"*"
],
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
-
ES集群模式数据分布原理
-
集群索引分片策略
-
合理设置分片意义
-
es是一个分式式的搜索引擎,ES中默认为每个索引创建5个分片,每个分片提供一个备份。如果分片过小,当索引数据量非常大的话,每个分片上的数据就会比较多,导致检索时候效率较低,反之分片过多各节点之间的数据同步会过多消耗集群资源、检索时多分片数据归并也会影响效率,所以需要有一些标准来设置合理的分片避免上述问题。
-
-
1、分片过小会导致段过小,进而致使开销增加。您要尽量将分片的平均大小控制在至少几 GB 到几十 GB 之间。 对时序型数据用例而言,分片大小通常介于 20GB 至 40GB 之间。 2、由于单个分片的开销取决于段数量和段大小,所以通过 forcemerge 操作强制将 较小的段合并为较大的段能够减少开销并改善查询性能。理想状况下, 应当在索引内再无数据写入时完成此操作。请注意:这是一个极其耗费资源的操作, 所以应该在非高峰时段进行。 3、每个节点上可以存储的分片数量与可用的堆内存大小成正比关系,但是 Elasticsearch并未 强制规定固定限值。这里有一个很好的经验法则:确保对于节点上已配置的每个 GB,将分片数量 保持在 20 以下。如果某个节点拥有 30GB 的堆内存,那其最多可有 600 个分片,但是在此限值范围内, 您设置的分片数量越少,效果就越好。一般而言,这可以帮助集群保持良好的运行状态。 (编者按:从 8.3 版开始,我们大幅减小了每个分片的堆使用量, 因此对本博文中的经验法则也进行了相应更新。请按照以下提示了解 8.3+ 版本的 Elasticsearch。)
-
其它网上使用经验
-
每个分片的数据量不超过最大JVM堆空间设置,一般不超过32G。如果一个索引大概500G,那分片大概在16个左右比较合适。
-
单个索引分片个数一般不超过节点数的3倍,推荐是1.5 ~ 3倍之间。假如一个集群3个节点,根据索引数据量大小分片数在5-9之间比较合适。
-
主分片、副本和节点数,分配时也可以参考以下关系:节点数<= 主分片数 * (副本数 +1 )
-
-
-
结论
-
集群能承受的分片数
单实例推荐最大分片数: (8G-2G-0.5G) * 20 = 110 个分片
-
依目前3个4C8G实例规格举例来说明:
-
单实例推荐最大分片数: (8G-2G-0.5G) * 20 = 110 个分片
-
2G留给操作系统及其它软件内存使用,0.5G是留给ES本身做计算所需的内存资源。
所以当前集群可以承载110 * 3 = 330 个分片,假如每个索引5个分片,整个集群可以容纳 330 / 5 = 66 个索引-
单个索引分片数
-
依上面经验来计算3个节点的集群,索引分片为5-9个 比较合适
-
-
-
-
安装中文分词插件 IK
-
IK分词git,上面有安装使用说明
-
安装命令-注意版本是否正确,一定要和es版本匹配
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.10.0/elasticsearch-analysis-ik-7.10.0.zip
POST _analyze
{
"analyzer": "ik_max_word",
"text":"中国河南南阳"
}
七、ES SDK及增删改查
完整代码可以从github上下载
https://github.com/caizi12/my-elasticsearch-starter.git
代码说明
封装了易于使用的elasticsearch starter,使用时可以先把代码Deploy到私有仓库中,然后应用程序中依赖使用,如果没有私有仓库可以把代码copy到应用中使用。
Deploy到仓库后使用方式
1、应用添加依赖
<dependency>
<groupId>com.my.es</groupId>
<artifactId>elasticsearch-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
2、application.properties 添加es链接配置
#es链接地址
spring.elasticsearch.uris=http://localhost:9200
#es账号密码,根据实际填写
spring.elasticsearch.username=elastic
spring.elasticsearch.password=123456
#可省配置:连接es集群超时参数,默认毫秒
spring.elasticsearch.connection-timeout=300
spring.elasticsearch.read-timeout=300
3、Demo,(更多示例可以看单元测试部分)
@SpringBootTest
public class MyEsServiceTest {
@Autowired
private MyEsService myEsService;
@Test
public void delIndex() {
boolean result = myEsService.deleteIndexIfExist(Student.class);
Assert.assertTrue(result);
}
@Test
public void delIndexDoc() {
String result = myEsService.delIndexDoc("3007", Student.class);
System.out.println("delIndexDoc:" + Student.class.getName());
}
@Test
public void updateMapping() {
boolean result = myEsService.updateIndexMapping(Student.class);
Assert.assertTrue(result);
}
@Test
public void updateIndexMapping() {
boolean result = myEsService.updateIndexMapping(Shop.class);
Assert.assertTrue(result);
}
@Test
public void createIndex() {
boolean exist = myEsService.existIndex(Student.class);
boolean result = false;
if (!exist) {
result = myEsService.createIndexIfNotExist(Student.class);
} else {
System.out.println("index exist:" + Student.class.getName());
}
Assert.assertTrue(result);
}
@Test
public void addIndexDoc() {
Student student = new Student(1000, "张三", "测试索引添加", "哈哈", "三年二班刘", 10, new Date(), null);
String documentId = myEsService.addIndexDoc(student);
System.out.println("addIndexDoc result:" + documentId);
Assert.assertNotNull(documentId);
}
}
ES SDK封装代码示例
接口定义
package com.my.elasticsearch;
import java.util.List;
import javax.annotation.Nullable;
import com.my.elasticsearch.model.MyEsSearchRequest;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
/**
* es服务接口,该接口提供对es的增删改查操作
*
* @authro nantian
* @date 2022-10-08 15:19
*/
public interface MyEsService {
/**
* 判断索引是否存在, 文档需标注@Document注解
*
* @param clazz
* @return
*/
boolean existIndex(Class<?> clazz);
/**
* 判断索引是否存在, 文档需标注@Document注解
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean existIndex(Class<?> clazz, boolean nonTenantMode);
/**
* 创建索引并设置mapping,setting信息
* 文档需标注@Document注解、包含@Id注解,其它属性字段需要添加@Field注解
*
* @param clazz
* @return
*/
boolean createIndexIfNotExist(Class<?> clazz);
/**
* 创建索引并设置mapping,setting信息
* 文档需标注@Document注解、包含@Id注解,其它属性字段需要添加@Field注解
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean createIndexIfNotExist(Class<?> clazz, boolean nonTenantMode);
/**
* 更新索引mapping信息,已存在的索引重复调用新加的字段会自动更新上去,老字段不会变化
*
* @param clazz
* @return
*/
boolean updateIndexMapping(Class<?> clazz);
/**
* 更新索引mapping信息,已存在的索引重复调用新加的字段会自动更新上去,老字段不会变化
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean updateIndexMapping(Class<?> clazz, boolean nonTenantMode);
/**
* 删除索引,业务应用中不建议用,如果有必要联系管理员在Kibana控台进行操作
*
* @param clazz
* @return
*/
boolean deleteIndexIfExist(Class<?> clazz);
/**
* 删除索引,业务应用中不建议用,如果有必要联系管理员在Kibana控台进行操作
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean deleteIndexIfExist(Class<?> clazz, boolean nonTenantMode);
/**
* 判断一个文档是否存在
*
* @param clazz
* @param docId
* @return
*/
boolean existDocById(Class<?> clazz, String docId);
/**
* 判断一个文档是否存在
*
* @param clazz
* @param docId
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean existDocById(Class<?> clazz, String docId, boolean nonTenantMode);
/**
* 添加一个数据到索引中,推荐使用@addIndexDoc(T model)
*
* @param indexName 索引名
* @param model 索引数据,注解@Id的字段值不允许为空
* @return 文档ID
*/
<T> String addIndexDoc(String indexName, T model);
/**
* 添加一个数据到索引中,推荐使用@addIndexDoc(T model)
*
* @param indexName 索引名
* @param model 索引数据,注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 文档ID
*/
<T> String addIndexDoc(String indexName, T model, boolean nonTenantMode);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 文档数据,注解@Id的字段值不允许为空
* @return
*/
<T> String addIndexDoc(T model);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 文档数据,注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String addIndexDoc(T model, boolean nonTenantMode);
/**
* 添加一个数据到索引中,指定数据版本号
*
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @return
*/
<T> String saveIndexDoc(T model, Long version);
/**
* 添加一个数据到索引中,指定数据版本号
*
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String saveIndexDoc(T model, Long version, boolean nonTenantMode);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
* 指定数据版本号
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @return
*/
<T> String saveIndexDoc(String indexName, T model, Long version);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
* 指定数据版本号
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String saveIndexDoc(String indexName, T model, Long version, boolean nonTenantMode);
/**
* 批量添加索引,推荐使用@bulkAddIndexDoc(Class<?> clazz, List<T> docList)
*
* @param indexName
* @param docList
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(String indexName, List<T> docList);
/**
* 批量添加索引,推荐使用@bulkAddIndexDoc(Class<?> clazz, List<T> docList)
*
* @param indexName
* @param docList
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(String indexName, List<T> docList, boolean nonTenantMode);
/**
* 批量添加索引
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(String indexName, List<T> docList);
/**
* 批量添加索引
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(String indexName, List<T> docList, boolean nonTenantMode);
/**
* 批量添加索引,会自动获取类上的 @Document(indexName)属性当索引名
*
* @param clazz
* @param docList
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(Class<?> clazz, List<T> docList);
/**
* 批量添加索引,会自动获取类上的 @Document(indexName)属性当索引名
*
* @param clazz
* @param docList
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(Class<?> clazz, List<T> docList, boolean nonTenantMode);
/**
* 批量添加索引
*
* @param clazz 会自动获取类上的 @Document(indexName)属性当索引名
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(Class<?> clazz, List<T> docList);
/**
* 批量添加索引
*
* @param clazz 会自动获取类上的 @Document(indexName)属性当索引名
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(Class<?> clazz, List<T> docList, boolean nonTenantMode);
/**
* 更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 注解@Id的字段值不允许为空
* @return
*/
<T> UpdateResponse.Result updateDoc(T model);
/**
* 更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> UpdateResponse.Result updateDoc(T model, boolean nonTenantMode);
/**
* 批量更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param clazz
* @param <T> 注解@Id的字段值不允许为空
* @return
*/
<T> List<IndexedObjectInformation> bulkUpdateDoc(Class<?> clazz, List<T> modelList);
/**
* 批量更新文档
*
* @param clazz
* @param <T> 注解@Id的字段值不允许为空
* @param bulkOptions
* @return
*/
<T> List<IndexedObjectInformation> bulkUpdateDoc(Class<?> clazz, List<T> modelList, BulkOptions bulkOptions);
/**
* 根据ID删除一个索引文档
*
* @param id
* @param clazz
* @return
*/
String delIndexDoc(String id, Class<?> clazz);
/**
* 根据ID删除一个索引文档
*
* @param id
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
String delIndexDoc(String id, Class<?> clazz, boolean nonTenantMode);
/**
* 批量删除索引
*
* @param clazz
* @param ids
* @return
*/
List<String> bulkDelIndexDoc(Class<?> clazz, List<String> ids);
/**
* 批量删除索引
*
* @param clazz
* @param ids
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
List<String> bulkDelIndexDoc(Class<?> clazz, List<String> ids, boolean nonTenantMode);
/**
* 删除一个索引文档,会自动从类上获取注解为@Id属性的value当作ID
*
* @param model
* @param <T>
* @return
*/
<T> String delIndexDoc(T model);
/**
* 删除一个索引文档,会自动从类上获取注解为@Id属性的value当作ID
*
* @param model
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String delIndexDoc(T model, boolean nonTenantMode);
/**
* @param docId
* @param tClass
* @param <T>
* @return
*/
<T> T findById(String docId, Class<T> tClass);
/**
* @param docId
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @param <T>
* @return
*/
<T> T findById(String docId, Class<T> clazz, boolean nonTenantMode);
/**
* 根据ID批量查询
*
* 使用id查询数据实时性更好
*
* @param indexName
* @param clazz
* @param docIdList
* @param nonTenantMode
* @param <T>
* @return
*/
<T> List<T> findByIds(String indexName, Class<T> clazz, List<String> docIdList, boolean nonTenantMode) ;
<T> List<T> findByIds(Class<T> clazz, List<String> docIdList) ;
<T> List<T> findByIds(Class<T> clazz, List<String> docIdList,boolean nonTenantMode) ;
/**
* 更丰富灵活的索引查询,开放spring-boot-es-starter原生NativeSearchQueryBuilder
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param queryBuilder
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, NativeSearchQueryBuilder queryBuilder);
/**
* 更丰富灵活的索引查询,开放spring-boot-es-starter原生NativeSearchQueryBuilder
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param queryBuilder
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, NativeSearchQueryBuilder queryBuilder, boolean nonTenantMode);
/**
* 封装查询对象,简化NativeSearchQueryBuilder构造过程
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param request
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, MyEsSearchRequest request);
/**
* 封装查询对象,简化NativeSearchQueryBuilder构造过程
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param request
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, MyEsSearchRequest request, boolean nonTenantMode);
/**
* 精确查询类场景推荐使用,es不会计算文档相关性分值,性能更好
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param filterBuilder
* @param pageable
* @return
*/
<T> SearchHits<T> searchByFilter(Class<T> clazz, QueryBuilder filterBuilder, @Nullable Pageable pageable);
/**
* 精确查询类场景推荐使用,es不会计算文档相关性分值,性能更好
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param filterBuilder
* @param pageable
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> searchByFilter(Class<T> clazz, QueryBuilder filterBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
/**
* 标题或文章内容检索类场景推荐使用,es会计算文档相关性,并按相关性自动排序
*
* @param clazz
* @param queryBuilder
* @param pageable
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, @Nullable Pageable pageable);
/**
* 标题或文章内容检索类场景推荐使用,es会计算文档相关性,并按相关性自动排序
*
* @param clazz
* @param queryBuilder
* @param pageable
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
/**
* 索引数据查询
*
* @param clazz 索引类
* @param queryBuilder
* @param filterBuilder
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder, @Nullable Pageable pageable);
/**
* 索引数据查询
*
* @param clazz 索引类
* @param queryBuilder
* @param filterBuilder
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
}
实现
package com.my.elasticsearch.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.my.elasticsearch.MyEsService;
import com.my.elasticsearch.cache.EsIndexNameCache;
import com.my.elasticsearch.util.EsReflectUtils;
import com.my.elasticsearch.util.EsTenantUtil;
import com.my.elasticsearch.model.MyEsSearchRequest;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
import org.springframework.data.elasticsearch.core.MultiGetItem;
import org.springframework.data.elasticsearch.core.MyRestIndexTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* es 公共组件服务实现
*
* 该实现提供了租户也非租户模式,租户模式会给索引名前自动加租户code前缀,如果不需要可以进行修改
*
* @authro nantian
* @date 2022-10-08 15:19
*/
public class MyEsServiceImpl implements MyEsService {
private static ObjectMapper objectMapper;
private ElasticsearchRestTemplate elasticsearchRestTemplate;
private static final String PROPERTIES_KEY = "properties";
public MyEsServiceImpl(ElasticsearchRestTemplate elasticsearchRestTemplate) {
this.elasticsearchRestTemplate = elasticsearchRestTemplate;
}
static {
//JavaTimeModule timeModule = new JavaTimeModule();
//timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
//timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
设置NULL值不参与序列化
objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
//.registerModule(timeModule);
}
/**
* 根据使用的租户模式,决定是否对索引名添加租户标识
*
* @param index
* @return
*/
private String convertTenantIndex(String index) {
return EsTenantUtil.getTenantIndex(index);
}
/**
* 构建操作es的索引类
*
* @param index
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
private IndexCoordinates buildIndexCoordinates(String index, boolean nonTenantMode) {
if (!nonTenantMode) {
index = convertTenantIndex(index);
}
return IndexCoordinates.of(index);
}
private IndexCoordinates buildIndexCoordinates(Class<?> clazz) {
return buildIndexCoordinates(clazz, false);
}
private IndexCoordinates buildIndexCoordinates(Class<?> clazz, boolean nonTenantMode) {
if (!nonTenantMode) {
return IndexCoordinates.of(convertTenantIndex(getEsIndexName(clazz)));
}
return IndexCoordinates.of(getEsIndexName(clazz));
}
/**
* 根据类@Document(indexName)属性获取索引名
*
* @param clazz
* @return
*/
private String getEsIndexName(Class<?> clazz) {
return EsIndexNameCache.get(clazz);
}
/**
* 判断索引是否存在
*
* @param indexName 索引名称
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
public boolean existIndex(String indexName, boolean nonTenantMode) {
if (StringUtils.isNotEmpty(indexName)) {
return elasticsearchRestTemplate.indexOps(buildIndexCoordinates(indexName, nonTenantMode)).exists();
}
return Boolean.FALSE;
}
/**
* 判断索引是否存在
*
* @param clazz
* @return
*/
@Override
public boolean existIndex(Class<?> clazz) {
return existIndex(clazz, false);
}
@Override
public boolean existIndex(Class<?> clazz, boolean nonTenantMode) {
if (clazz != null) {
return existIndex(getEsIndexName(clazz), nonTenantMode);
}
return Boolean.FALSE;
}
/**
* 索引不存在时创建索引[无分片及mapping信息,暂不开放使用]
*
* @param indexName 索引名称
* @return 是否创建成功
*/
private boolean createIndexIfNotExist(String indexName) {
return createIndexIfNotExist(indexName, false);
}
/**
* 索引不存在时创建索引[无分片及mapping信息,暂不开放使用]
*
* @param indexName 索引名称
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 是否创建成功
*/
private boolean createIndexIfNotExist(String indexName, boolean nonTenantMode) {
if (!existIndex(indexName, nonTenantMode)) {
return elasticsearchRestTemplate.indexOps(buildIndexCoordinates(indexName, nonTenantMode)).create();
}
return Boolean.FALSE;
}
/**
* 索引不存在时创建索引并设置分片及mapping信息
*
* @param clazz 索引类信息
* @return 是否创建成功
*/
@Override
public boolean createIndexIfNotExist(Class<?> clazz) {
return createIndexIfNotExist(clazz, false);
}
@Override
public boolean createIndexIfNotExist(Class<?> clazz, boolean nonTenantMode) {
boolean result = existIndex(clazz, nonTenantMode);
if (!result) {
//不能直接用createWithMapping,会漏掉租户信息,改写索引类上租户实现比较复杂
//elasticsearchRestTemplate.indexOps(clazz).createWithMapping();
MyRestIndexTemplate esRestIndexTemplate = new MyRestIndexTemplate(elasticsearchRestTemplate, clazz);
Document document = esRestIndexTemplate.createMapping();
Settings settings = esRestIndexTemplate.createSettings();
return esRestIndexTemplate.doCreate(buildIndexCoordinates(clazz, nonTenantMode), settings, document);
}
return Boolean.FALSE;
}
/**
* 更新索引字段,会自动获取类上的索引注解信息进行更新索引mapping,已存在的字段不会更新
*
* @param clazz
* @return
*/
@Override
public boolean updateIndexMapping(Class<?> clazz) {
return updateIndexMapping(clazz, false);
}
@Override
public boolean updateIndexMapping(Class<?> clazz, boolean nonTenantMode) {
boolean result = existIndex(clazz, nonTenantMode);
if (result) {
MyRestIndexTemplate esRestIndexTemplate = new MyRestIndexTemplate(elasticsearchRestTemplate, clazz);
Document document = esRestIndexTemplate.createMapping();
return esRestIndexTemplate.doPutMapping(buildIndexCoordinates(clazz, nonTenantMode), document);
}
return Boolean.FALSE;
}
/**
* 索引存在删除索引
*
* @param indexName 索引名称
* @return 是否删除成功
*/
public boolean deleteIndexIfExist(String indexName) {
return deleteIndexIfExist(indexName, false);
}
/**
* 索引存在删除索引
*
* @param indexName 索引名称
* @return 是否删除成功
*/
public boolean deleteIndexIfExist(String indexName, boolean nonTenantMode) {
if (existIndex(indexName, nonTenantMode)) {
return elasticsearchRestTemplate.indexOps(buildIndexCoordinates(indexName, nonTenantMode)).delete();
}
return Boolean.FALSE;
}
/**
* 索引存在删除索引
*
* @param clazz 索引名称
* @return 是否删除成功
*/
@Override
public boolean deleteIndexIfExist(Class<?> clazz) {
if (existIndex(clazz)) {
return deleteIndexIfExist(getEsIndexName(clazz));
}
return Boolean.FALSE;
}
@Override
public boolean deleteIndexIfExist(Class<?> clazz, boolean nonTenantMode) {
if (existIndex(clazz, nonTenantMode)) {
return deleteIndexIfExist(getEsIndexName(clazz), nonTenantMode);
}
return Boolean.FALSE;
}
/**
* 新增索引文档,根据类上的@Document获取索引名
*
* @param model elasticsearch文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @return 文档ID
*/
@Override
public <T> String addIndexDoc(T model) {
return addIndexDoc(getEsIndexName(model.getClass()), model);
}
@Override
public <T> String addIndexDoc(T model, boolean nonTenantMode) {
return addIndexDoc(getEsIndexName(model.getClass()), model, nonTenantMode);
}
/**
* 新增文档,指定索引名
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @return 文档ID
*/
@Override
public <T> String addIndexDoc(String indexName, T model) {
return addIndexDoc(indexName, model, false);
}
@Override
public <T> String addIndexDoc(String indexName, T model, boolean nonTenantMode) {
Assert.notNull(indexName, "addIndexDoc elasticsearch indexName is null");
Assert.notNull(model, "addIndexDoc document is null");
return elasticsearchRestTemplate.index(
new IndexQueryBuilder().withId(getDocumentIdValue(model)).withObject(model).build(),
buildIndexCoordinates(indexName, nonTenantMode));
}
/**
* 保存文档,指定数据版本号
*
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @param <T>
* @return
*/
@Override
public <T> String saveIndexDoc(T model, Long version) {
return saveIndexDoc(model, version, false);
}
@Override
public <T> String saveIndexDoc(T model, Long version, boolean nonTenantMode) {
return saveIndexDoc(getEsIndexName(model.getClass()), model, version, nonTenantMode);
}
/**
* 保存文档,指定索引名和数据版本号
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @return 文档ID
*/
@Override
public <T> String saveIndexDoc(String indexName, T model, Long version) {
return saveIndexDoc(indexName, model, version, false);
}
/**
* 保存文档,指定索引名和数据版本号
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 文档ID
*/
@Override
public <T> String saveIndexDoc(String indexName, T model, Long version, boolean nonTenantMode) {
Assert.notNull(indexName, "addIndexDoc elasticsearch indexName is null");
Assert.notNull(model, "addIndexDoc document is null");
return elasticsearchRestTemplate.index(
new IndexQueryBuilder().withId(getDocumentIdValue(model)).withVersion(version).withObject(model).build(),
buildIndexCoordinates(indexName, nonTenantMode));
}
@Override
public <T> List<IndexedObjectInformation> bulkAddIndexDoc(Class<?> clazz, List<T> docList) {
return bulkAddIndexDoc(getEsIndexName(clazz), docList);
}
@Override
public <T> List<IndexedObjectInformation> bulkAddIndexDoc(Class<?> clazz, List<T> docList, boolean nonTenantMode) {
return bulkAddIndexDoc(getEsIndexName(clazz), docList, nonTenantMode);
}
@Override
public <T> List<IndexedObjectInformation> bulkSaveIndexDoc(Class<?> clazz, List<T> docList) {
return bulkSaveIndexDoc(clazz, docList, false);
}
@Override
public <T> List<IndexedObjectInformation> bulkSaveIndexDoc(Class<?> clazz, List<T> docList, boolean nonTenantMode) {
return bulkSaveIndexDoc(getEsIndexName(clazz), docList, nonTenantMode);
}
/**
* 批量新增文档
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @return 文档ID
*/
public <T> List<IndexedObjectInformation> bulkAddIndexDoc(String indexName, List<T> docList) {
return bulkAddIndexDoc(indexName, docList, false);
}
/**
* 批量新增文档
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 文档ID
*/
@Override
public <T> List<IndexedObjectInformation> bulkAddIndexDoc(String indexName, List<T> docList, boolean nonTenantMode) {
Assert.notNull(indexName, "bulkAddIndexDoc elasticsearch indexName is null");
Assert.notNull(docList, "bulkAddIndexDoc document is null");
List<IndexQuery> indexQueries = new ArrayList<>();
docList.forEach(doc ->
indexQueries.add(new IndexQueryBuilder().withId(getDocumentIdValue(doc)).withObject(doc).build()));
return elasticsearchRestTemplate.bulkIndex(indexQueries, buildIndexCoordinates(indexName, nonTenantMode));
}
/**
* 批量新增文档
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param <T>
* @return
*/
@Override
public <T> List<IndexedObjectInformation> bulkSaveIndexDoc(String indexName, List<T> docList) {
return bulkSaveIndexDoc(indexName, docList, false);
}
/**
* 批量新增文档
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
@Override
public <T> List<IndexedObjectInformation> bulkSaveIndexDoc(String indexName, List<T> docList, boolean nonTenantMode) {
Assert.notNull(indexName, "bulkAddIndexDoc elasticsearch indexName is null");
Assert.notNull(docList, "bulkAddIndexDoc document is null");
// 验证是否传version值
docList.forEach(doc -> getDocumentVersionValue(doc));
List<IndexQuery> indexQueries = new ArrayList<>();
docList.forEach(doc ->
indexQueries.add(new IndexQueryBuilder().withId(getDocumentIdValue(doc)).withVersion(getDocumentVersionValue(doc)).withObject(doc).build()));
return elasticsearchRestTemplate.bulkIndex(indexQueries, buildIndexCoordinates(indexName, nonTenantMode));
}
/**
* 根据ID查询文档
*
* @param indexName 索引名称
* @param docId 文档ID
* @param clazz 映射类Class
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @param <T>
* @return Elasticsearch 文档
*/
public <T> T findById(String indexName, String docId, Class<T> clazz, boolean nonTenantMode) {
if (StringUtils.isNotEmpty(docId) && clazz != null) {
return elasticsearchRestTemplate.get(docId, clazz, buildIndexCoordinates(indexName, nonTenantMode));
}
return null;
}
public <T> T findById(String docId, Class<T> clazz) {
return findById(docId, clazz, false);
}
@Override
public <T> T findById(String docId, Class<T> clazz, boolean nonTenantMode) {
return findById(getEsIndexName(clazz), docId, clazz, nonTenantMode);
}
/**
* 根据多个ID查询文档
*
* @param indexName 索引名称
* @param docIdList 文档ID
* @param clazz 映射类Class
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @param <T>
* @return Elasticsearch 文档
*/
@Override
public <T> List<T> findByIds(String indexName, Class<T> clazz, List<String> docIdList, boolean nonTenantMode) {
if (CollectionUtils.isEmpty(docIdList) || clazz == null || indexName == null) {
return null;
}
StringQuery query = StringQuery.builder("stringQuery").withIds(docIdList).build();
List<MultiGetItem<T>> result = elasticsearchRestTemplate.multiGet(query, clazz, buildIndexCoordinates(indexName, nonTenantMode));
if(CollectionUtils.isEmpty(result)){
return null;
}
List list = result.stream().map(o->o.getItem()).filter(item->item != null).collect(Collectors.toList());
return list;
}
@Override
public <T> List<T> findByIds(Class<T> clazz,List<String> docIdList) {
return findByIds( clazz, docIdList,false);
}
@Override
public <T> List<T> findByIds(Class<T> clazz, List<String> docIdList,boolean nonTenantMode) {
return findByIds(getEsIndexName(clazz), clazz,docIdList, nonTenantMode);
}
/**
* 根据ID判断文档是否存在
*
* @param indexName 索引名称
* @param docId 文档ID
* @return 存在与否
*/
private boolean existDocById(String indexName, String docId, boolean nonTenantMode) {
if (existIndex(indexName, nonTenantMode) && StringUtils.isNotEmpty(docId)) {
return elasticsearchRestTemplate.exists(docId, buildIndexCoordinates(indexName, nonTenantMode));
}
return Boolean.FALSE;
}
public boolean existDocById(Class<?> clazz, String docId) {
return existDocById(clazz, docId, false);
}
@Override
public boolean existDocById(Class<?> clazz, String docId, boolean nonTenantMode) {
return existDocById(getEsIndexName(clazz), docId, nonTenantMode);
}
public <T> UpdateResponse.Result updateDoc(T elasticsearchModel) {
return updateDoc(elasticsearchModel, false);
}
@Override
public <T> UpdateResponse.Result updateDoc(T elasticsearchModel, boolean nonTenantMode) {
String indexName = getEsIndexName(elasticsearchModel.getClass());
return updateDoc(indexName, elasticsearchModel, nonTenantMode);
}
/**
* 更新文档
*
* @param indexName 索引名称
* @param elasticsearchModel elasticsearch文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id标注的文档ID值不能为空
* @return UpdateResponse.Result
* @throws JsonProcessingException JsonProcessingException
*/
private <T> UpdateResponse.Result updateDoc(String indexName, T elasticsearchModel, boolean nonTenantMode) {
return updateDoc(indexName, elasticsearchModel, this.objectMapper, nonTenantMode);
}
/**
* 更新文档
*
* @param indexName 索引名称
* @param elasticsearchModel elasticsearch文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id标注的文档ID值不能为空
* @param objectMapper objectMapper
* @return UpdateResponse.Result
*/
private <T> UpdateResponse.Result updateDoc(String indexName, T elasticsearchModel, ObjectMapper objectMapper) {
return updateDoc(indexName, elasticsearchModel, objectMapper, false);
}
/**
* 更新文档
*
* @param indexName 索引名称
* @param elasticsearchModel elasticsearch文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id标注的文档ID值不能为空
* @param objectMapper objectMapper
* @param nonTenantMode 是否是租户模式,false表示非租户模式,即通用索引
* @return UpdateResponse.Result
*/
private <T> UpdateResponse.Result updateDoc(String indexName, T elasticsearchModel, ObjectMapper objectMapper, boolean nonTenantMode) {
Assert.notNull(indexName, "bulkUpdateDoc clazz is null");
Assert.notNull(elasticsearchModel, "bulkUpdateDoc modelList is null");
try {
String id = getDocumentIdValue(elasticsearchModel);
Assert.isTrue(existDocById(indexName, id, nonTenantMode), "elasticsearch document is not exist.");
objectMapper = objectMapper == null ? this.objectMapper : objectMapper;
String json = objectMapper.writeValueAsString(elasticsearchModel);
UpdateQuery updateQuery = UpdateQuery.builder(id).withDocument(Document.parse(json)).build();
return elasticsearchRestTemplate.update(updateQuery, buildIndexCoordinates(indexName, nonTenantMode)).getResult();
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public <T> List<IndexedObjectInformation> bulkUpdateDoc(Class<?> clazz, List<T> modelList) {
return bulkUpdateDoc(clazz, modelList, null);
}
public <T> List<IndexedObjectInformation> bulkUpdateDoc(Class<?> clazz, List<T> modelList, BulkOptions bulkOptions) {
return bulkUpdateDoc(clazz, modelList, bulkOptions, objectMapper);
}
/**
* 批量更新文档
*
* @param clazz 索引名称
* @param modelList elasticsearch文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id标注的文档ID值不能为空
* @param objectMapper objectMapper
* @return UpdateResponse.Result
*/
private <T> List<IndexedObjectInformation> bulkUpdateDoc(Class<?> clazz, List<T> modelList, BulkOptions bulkOptions,
ObjectMapper objectMapper) {
Assert.notNull(clazz, "bulkUpdateDoc clazz is null");
Assert.notNull(clazz, "bulkUpdateDoc modelList is null");
try {
List<UpdateQuery> queries = new ArrayList(modelList.size());
UpdateQuery updateQuery = null;
String id = null;
for (T model : modelList) {
id = getDocumentIdValue(model);
Assert.notNull(id, clazz.getName() + " instance document id is null");
String json = objectMapper.writeValueAsString(model);
updateQuery = UpdateQuery.builder(getDocumentIdValue(model)).withDocument(Document.parse(json)).build();
queries.add(updateQuery);
}
bulkOptions = bulkOptions == null ? BulkOptions.defaultOptions() : bulkOptions;
return elasticsearchRestTemplate.doBulkOperation(queries, bulkOptions, buildIndexCoordinates(clazz));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private <T> String getDocumentIdValue(T elasticsearchModel) {
return EsReflectUtils.getDocumentIdValue(elasticsearchModel);
}
private <T> Long getDocumentVersionValue(T elasticsearchModel) {
return EsReflectUtils.getDocumentVersionValue(elasticsearchModel);
}
/**
* 查询文档
*
* @param clazz 映射文档类 文档需标注@Document注解、包含@Id注解字段
* @param queryBuilder 非结构化数据 QueryBuilder; queryBuilder与filterBuilder必须二者存在其一
* @param filterBuilder 过滤查询
* @param <T>
* @return
*/
public <T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder,
Pageable pageable) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, filterBuilder, pageable);
return search(clazz, request);
}
@Override
public <T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder,
Pageable pageable, boolean nonTenantMode) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, filterBuilder, pageable);
return search(clazz, request, nonTenantMode);
}
public <T> SearchHits<T> searchByFilter(Class<T> clazz, QueryBuilder filterBuilder, Pageable pageable) {
MyEsSearchRequest request = new MyEsSearchRequest(null, filterBuilder, pageable);
return search(clazz, request);
}
@Override
public <T> SearchHits<T> searchByFilter(Class<T> clazz, QueryBuilder filterBuilder, @javax.annotation.Nullable Pageable pageable, boolean nonTenantMode) {
MyEsSearchRequest request = new MyEsSearchRequest(null, filterBuilder, pageable);
return search(clazz, request, nonTenantMode);
}
public <T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, Pageable pageable) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, null, pageable);
return search(clazz, request);
}
@Override
public <T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, @javax.annotation.Nullable Pageable pageable, boolean nonTenantMode) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, null, pageable);
return search(clazz, request, nonTenantMode);
}
public <T> SearchHits<T> search(Class<T> clazz, MyEsSearchRequest request) {
return search(clazz, request, false);
}
@Override
public <T> SearchHits<T> search(Class<T> clazz, MyEsSearchRequest request, boolean nonTenantMode) {
return search(getEsIndexName(clazz), clazz, request.getQueryBuilder(), request.getFilterBuilder(),
request.getAggregationBuilder(), request.getPageable(), request.getQueryFields(), nonTenantMode);
}
public <T> SearchHits<T> search(Class<T> clazz, NativeSearchQueryBuilder queryBuilder) {
return search(clazz, queryBuilder, false);
}
@Override
public <T> SearchHits<T> search(Class<T> clazz, NativeSearchQueryBuilder queryBuilder, boolean nonTenantMode) {
return elasticsearchRestTemplate.search(queryBuilder.build(), clazz, buildIndexCoordinates(clazz, nonTenantMode));
}
/**
* 查询文档
*
* <p>
* 查询的文档必须包含映射@Document的@Id字段
* </p>
*
* @param indexName 索引名称
* @param clazz 映射文档类 文档需标注@Document注解、包含@Id注解字段
* @param queryBuilder 非结构化数据 QueryBuilder; queryBuilder与filterBuilder必须二者存在其一
* @param filterBuilder 结构化数据 QueryBuilder; filterBuilder与queryBuilder必须二者存在其一
* @param abstractAggregationBuilder 聚合查询Builder
* @param pageable 分页/排序; 分页从0开始
* @param fields 包含字段
* @param nonTenantMode 是否是租户模式,false表示非租户模式,即通用索引
* @return
*/
private <T> SearchHits<T> search(String indexName, Class<T> clazz, @Nullable QueryBuilder queryBuilder,
@Nullable QueryBuilder filterBuilder,
@Nullable AbstractAggregationBuilder abstractAggregationBuilder,
@Nullable Pageable pageable, @Nullable String[] fields, boolean nonTenantMode) {
if (StringUtils.isNotBlank(indexName)) {
// 查询的文档必须包含映射@Document的@Id字段(
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(
QueryBuilders.existsQuery(EsReflectUtils.getDocumentIdFieldName(clazz)));
if (queryBuilder != null) {
boolQueryBuilder.must(queryBuilder);
}
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder().withQuery(
boolQueryBuilder);
if (filterBuilder != null) {
nativeSearchQueryBuilder.withFilter(filterBuilder);
}
if (abstractAggregationBuilder != null) {
nativeSearchQueryBuilder.withAggregations(abstractAggregationBuilder);
}
if (pageable != null) {
nativeSearchQueryBuilder.withPageable(pageable);
}
if (fields != null && fields.length > 0) {
nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(fields, null));
//nativeSearchQueryBuilder.withFields(fields);
}
// nativeSearchQueryBuilder.withSorts(SortBuilders.fieldSort("id").order(SortOrder.ASC));
return search(clazz, nativeSearchQueryBuilder, nonTenantMode);
}
return null;
}
@Override
public String delIndexDoc(String id, Class<?> clazz) {
return delIndexDoc(id, clazz, false);
}
@Override
public String delIndexDoc(String id, Class<?> clazz, boolean nonTenantMode) {
return elasticsearchRestTemplate.delete(id, buildIndexCoordinates(clazz, nonTenantMode));
}
@Override
public <T> String delIndexDoc(T model) {
return delIndexDoc(model, false);
}
@Override
public <T> String delIndexDoc(T model, boolean nonTenantMode) {
return delIndexDoc(EsReflectUtils.getDocumentIdValue(model), model.getClass(), nonTenantMode);
}
/**
* 根据ID批量删除
* 官方未提供根据id批量删除的,暂时就以循环删除的方式来操作,若有大批量操作存在性能问题考虑转为query delete方式
*
* @param clazz
* @param ids
* @return 返回每个ID删除后的返回结果
*/
@Override
public List<String> bulkDelIndexDoc(Class<?> clazz, List<String> ids) {
return bulkDelIndexDoc(clazz, ids, false);
}
/**
* 根据ID批量删除
* 官方未提供根据id批量删除的,暂时就以循环删除的方式来操作,若有大批量操作存在性能问题考虑转为query delete方式
*
* @param clazz
* @param ids
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 返回每个ID删除后的返回结果
*/
@Override
public List<String> bulkDelIndexDoc(Class<?> clazz, List<String> ids, boolean nonTenantMode) {
if (clazz == null || CollectionUtils.isEmpty(ids)) {
return null;
}
List delResutList = new ArrayList();
for (String id : ids) {
delResutList.add(elasticsearchRestTemplate.delete(id, buildIndexCoordinates(clazz, nonTenantMode)));
}
return delResutList;
}
}
增删改查demo
package com.my.es.test;
import java.util.Date;
import java.util.List;
import com.my.elasticsearch.MyEsService;
import com.my.es.test.model.Shop;
import com.my.es.test.model.Student;
import com.my.elasticsearch.model.MyEsSearchRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import net.minidev.json.JSONObject;
import org.assertj.core.util.Lists;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.query.UpdateResponse.Result;
/**
* es demo
*
* @authro nantian
* @date 2022-10-08 19:33
*/
@SpringBootTest
public class MyEsServiceTest {
@Autowired
private MyEsService myEsService;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Test
public void delIndex() {
boolean result = myEsService.deleteIndexIfExist(Student.class);
Assert.assertTrue(result);
}
@Test
public void delIndexDoc() {
String result = myEsService.delIndexDoc("3007", Student.class);
System.out.println("delIndexDoc:" + Student.class.getName());
}
@Test
public void updateMapping() {
boolean result = myEsService.updateIndexMapping(Student.class);
Assert.assertTrue(result);
}
@Test
public void updateIndexMapping() {
boolean result = myEsService.updateIndexMapping(Shop.class);
Assert.assertTrue(result);
}
@Test
public void createIndex() {
boolean exist = myEsService.existIndex(Student.class);
boolean result = false;
if (!exist) {
result = myEsService.createIndexIfNotExist(Student.class);
} else {
System.out.println("index exist:" + Student.class.getName());
}
Assert.assertTrue(result);
}
@Test
public void createIndex3() {
boolean result = myEsService.createIndexIfNotExist(Shop.class);
System.out.println("index exist:" + Shop.class.getName());
Assert.assertTrue(result);
}
@Test
public void addIndexDoc() {
Student student = new Student(1000, "张三", "测试索引添加", "哈哈", "三年二班刘", 10, new Date(), null);
String documentId = myEsService.addIndexDoc(student);
System.out.println("addIndexDoc result:" + documentId);
Assert.assertNotNull(documentId);
}
@Test
public void saveIndexDocWithVersion() {
Student student = new Student(1009, "张三1001", "测试索引添加1", "哈哈", "三年二班刘11", 10, new Date(), null);
Student existOne = myEsService.findById(student.getId() + "", Student.class);
Long _version = existOne != null ? existOne.getVersion() + 1 : null;
String documentId = myEsService.saveIndexDoc(student, _version);
System.out.println("addIndexDoc result:" + documentId);
Assert.assertNotNull(documentId);
}
@Test
public void existIndex() {
boolean result1 = myEsService.existIndex(Student.class);
boolean result2 = myEsService.existIndex(Student.class, true);
System.out.println(result1 + "------" + result2);
}
@Test
public void saveIndexDocWithNonTenantModel() {
Student student = new Student(1001, "张三1001", "测试索引添加1", "哈哈", "三年二班刘11", 10, new Date(), null);
boolean nonTenantModel = true;
if (nonTenantModel) {
if (!myEsService.existIndex(Student.class, nonTenantModel)) {
myEsService.createIndexIfNotExist(Student.class, nonTenantModel);
}
}
Student existOne = myEsService.findById(student.getId() + "", Student.class, nonTenantModel);
Long _version = existOne != null ? existOne.getVersion() + 1 : null;
String documentId = myEsService.saveIndexDoc(student, _version, nonTenantModel);
System.out.println("addIndexDoc result:" + documentId);
Assert.assertNotNull(documentId);
}
@Test
public void bulkAddIndexDoc2() {
Student student1 = new Student(1000, "zs0", "测试索引添加0", "哈哈33ss", "三年二班刘先生中国", 10, new Date(), null);
Student student2 = new Student(1001, "zs", "测试索引添加1", "哈哈dd", "五年二班周先生美国", 20, new Date(), null);
Student student3 = new Student(1002, "zs", "测试索引添加2", "哈哈aa", "10年二班刘女士中国", 0, new Date(), null);
Student student4 = new Student(1003, "zs1003", "测试索引添加3", "哈哈aadd", "八年二班张女士北京", 50, new Date(), null);
Student student5 = new Student(1004, "zs1004", "测试索引添加4", "哈哈bbaa", "三年二班刘重生北京", 60, new Date(), null);
Student student6 = new Student(1006, "zs1006", "测试索引添加4", "哈哈bbaa", "三年二班刘重生北京", 60, new Date(), null);
Student student7 = new Student(1007, "zs1007", "测试索引添加4", "哈哈bbaa", "三年二班刘重生北京", 60, new Date(), null);
List list = Lists.newArrayList(student1, student2, student3, student4, student5, student6, student7);
List<IndexedObjectInformation> result = myEsService.bulkAddIndexDoc(Student.class, list);
System.out.println("bulkAddIndexDoc result:" + JSONObject.toJSONString(result));
Assert.assertNotNull(result.size() > 0);
}
@Test
public void bulkSaveIndexDoc() {
Student student1 = new Student(1020, "zs0", "测试索引添加0", "哈哈33ss", "三年二班刘先生中国", 11, new Date(), null);
Student student2 = new Student(1021, "zs", "测试索引添加1", "哈哈dd", "五年二班周先生美国", 12, new Date(), null);
Student student3 = new Student(1022, "zs", "测试索引添加2", "哈哈aa", "10年二班刘女士中国", 13, new Date(), null);
List<Student> list = Lists.newArrayList(student1, student2, student3);
for (Student student : list) {
Student existOne = myEsService.findById(student.getId() + "", Student.class);
Long _version = existOne != null ? existOne.getVersion() + 1 : 1;
student.setVersion(_version);
}
List<IndexedObjectInformation> result = myEsService.bulkSaveIndexDoc(Student.class, list);
System.out.println("bulkAddIndexDoc result:" + JSONObject.toJSONString(result));
Assert.assertNotNull(result.size() > 0);
}
@Test
public void getByIdStudent() {
Student student = myEsService.findById("1000", Student.class);
System.out.println(JSONObject.toJSONString(student));
}
@Test
public void updateDoc() throws JsonProcessingException {
Student student = new Student();
student.setId(1000);
student.setAge(30);
student.setText("lisi");
UpdateResponse.Result result = myEsService.updateDoc(student);
System.out.println("update result:" + JSONObject.toJSONString(result));
Student student2 = myEsService.findById("1000", Student.class);
System.out.println(JSONObject.toJSONString(student2));
Assert.assertTrue(Result.UPDATED == result);
}
@Test
public void searchAll() {
SearchHits<Student> hits = myEsService.search(Student.class, QueryBuilders.matchAllQuery(), null);
System.out.println(JSONObject.toJSONString(hits));
}
@Test
public void searchBySingleField() {
QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "zs0");
SearchHits<Student> hits = myEsService.search(Student.class, queryBuilder, null);
System.out.println(JSONObject.toJSONString(hits));
}
@Test
public void searchByFilter() {
MyEsSearchRequest request = new MyEsSearchRequest();
request.setQueryFields(new String[]{"name", "id", "_version"});
//1
QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("zs", "name", "text");
request.setQueryBuilder(queryBuilder);
//2
MatchQueryBuilder queryBuilder1 = QueryBuilders.matchQuery("name", "zs");
RangeQueryBuilder queryBuilder2 = QueryBuilders.rangeQuery("age").gte("10").lte("60");
MatchQueryBuilder fuzzyQueryBuilder = QueryBuilders.matchQuery("desc", "哈哈");
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(queryBuilder1);
boolQueryBuilder.should(queryBuilder2);
boolQueryBuilder.should(fuzzyQueryBuilder);
BoolQueryBuilder filterQueryBuilder = QueryBuilders.boolQuery();
filterQueryBuilder.should(QueryBuilders.matchQuery("id", "1000"));
request.setFilterBuilder(filterQueryBuilder);
//3 分页及排序
request.setQueryBuilder(boolQueryBuilder);
Sort sort = Sort.by(Direction.DESC, "age");
PageRequest pageRequest = PageRequest.of(0, 10, sort);
request.setPageable(pageRequest);
SearchHits<Student> hits = myEsService.search(Student.class, request);
System.out.println(JSONObject.toJSONString(hits));
}
}
八、ES管理工具
安装集群管理工具- Kibana
Kibanan安装比较简单,es集群搭建成功后,修改config/kibana.yml 加入以下配置,启动即可
#设置为中文
i18n.locale: "zh-CN"
#允许其它IP可以访问
server.host: "0.0.0.0"
elasticsearch.username: "kibana_system"
elasticsearch.password: "elastic123"
#es集群地址,填写真实的节点地址
elasticsearch.hosts: ["http://xxx.xx.xx.xx:9200","http://xxx.xx.xx.xx:9200","http://xxx.xx.xx.xx:9200"]
./bin/kibana
es-header插件安装使用
-
说明
# 是否支持跨域,es-header插件使用 http.cors.enabled: true # *表示支持所有域名跨域访问 http.cors.allow-origin: "*" http.cors.allow-headers: Authorization,X-Requested-With,Content-Type,Content-Length
http://localhost:9200/?auth_user=elastic&auth_password=elastic123
-
开启跨域访问,使用es-header需要在es的配置文件中添加以下配置开启跨域访问
-
添加口令访问,如果es集群开启了口令访问,es-header使用时需要在url后添加口令,示例如下:
-
九、ES集群安全策略
类别
|
优点
|
缺点
|
建议
|
nginx
|
对外屏蔽了ES集群的真实IP和端口,配置也较简单
|
只能做一些网络访问安全上面的防护,不能对索引及字段进行精确控制
|
选择X-pack
经验证在免费情况下X-Pack能满足基本诉求,对应用中使用影响也比较小
|
Search Guard
|
开源免费,基于RBAC权限模型设计,能够细粒度进行管控
|
配置复杂,需要安装证书及业务应用代码改造
| |
X-Pack
|
官方提供,基于RBAC权限模型设计,能够细粒度进行管控,与client api及es集群兼容性较好
|
配置稍复杂,基础部分功能免费
|
X-pack安全策略开启及集群配置
第一步:在ES的根目录生成CA证书
bin/elasticsearch-certutil ca
第二步: 使用第一步生成的证书,产生p12密钥
bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12
-
Enter password for CA (elastic-stack-ca.p12)
-
Please enter the desired output file [elastic-certificates.p12]:
-
Enter password for elastic-certificates.p12 :
第三步: 配置证书
xpack.security.enabled: true xpack.license.self_generated.type: basic xpack.security.transport.ssl.enabled: true xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.keystore.path: certs/elastic-certificates.p12 xpack.security.transport.ssl.truststore.path: certs/elastic-certificates.p12
-
第一步和 第二步整个集群只须执行一次,第一个节点配置完后把证书copy到其它节点共用即可
-
elasticsearch.yml 同样集群中每个es节点都需要配置
第四步:重启ES
十、ES运维
数据备份与恢复
第一步:配置备份与恢复目录
path.repo: ["/data/backups", "/data2/backups"]
第二步:创建一个备份/快照
创建备份的三种方式
-
1、kibana上的dev_tools工具
-
该工具就是手动在kibana,填写备份与恢复API、参数等进行数据的备份与恢复,能够灵活的定制一些备份和恢复策略,也可以借助此工具熟悉es数据备份和恢复的各种操作命令,但不能自动进行备份
-
-
2、kibana提供的备份与恢复管理工具 [ 建议使用 ]
-
提供了快照查看、备份目录管理、创建备份快照的策略(如:多长时间创建一次快照)、快照恢复等功能,基本能够满足日常的使用,但存在问题是kibana这个产品上很多数据也是存在ES集群中,如果ES集群出现问题比如数据丢失Kibana也可能无法使用,此时我们就需要使用curl等方式发送http请求来恢复数据
-
工具路径,ip与port自行替换 http://127.0.0.1:5601/app/management/data/snapshot_restore/snapshots
-
-
3、使用curl发送 http请求 方式 【 终极方式 】当kibanan不可用时可以采用此方式,算是个保留的救命手段,此方式在备份与恢复使用的API及请求参数与第1种方式一致,只是使用了不同的工具来发送请求而已。这种方式在发送请求时需要填写集群地址及集群的访问口令,例如查看一个快照状态:
curl -u elastic:elastic123 -X POST 'http://ip:9200/_snapshot/jingkai_backup/snapshot/_status'
快照创建操作命令
(1) 创建一个备份仓库
PUT /_snapshot/backup1 { "type": "fs", "settings": { "location": "/data/backups", "compress": true, "max_snapshot_bytes_per_sec": "50mb", "max_restore_bytes_per_sec": "50mb" } }
-
其它命令
-
属性说明
-
_snapshot 命令是指创建一个备份快照,后面的路径【backup1】可以认为是仓库的名字
-
type=fs: 代表是文件系统来存储备份,其它方式暂没有找到
-
location: 当前备份存放的目录
-
compress: 是否开启压缩
-
max_snapshot_bytes_per_sec:备份时最大写入数据速度,可以根据实际硬件配置进行填写
-
max_restore_bytes_per_sec:恢复时最大读取速度
-
(2)创建一个备份/快照
PUT /_snapshot/backup1/snapshot_20221013?wait_for_completion=true { "indices": "index_1,index_2", "ignore_unavailable": true, "include_global_state": false }
-
wait_for_completion=true 设置为true时,操作界面会等待快照备份的结果,当索引比较大备份时间很长也有可能看不到结果,此时可以设置为false然后用以下命令查看备份进度
GET /_snapshot/backup1/snapshot_20221013/_status
"indices": "index_1,index_2"
: 可以使用该参数只对部分索引进行备份 -
ignore_unavailable=true: 忽略不可用的索引
-
include_global_state =false 可以防止将群集全局状态存储为快照的一部分。默认情况下,如果参与快照的一个或多个索引没有所有可用的主分片,则整个快照将失败。可以通过设置为partial来更改此行为true。
-
GET /_snapshot/backup1/snapshot_20221013
-
GET /_snapshot/backup1/snapshot_20221013/_status
(4)终止创建/删除快照
-
DELETE /_snapshot/backup1/snapshot_20221013
第三步:数据恢复
使用Kibana工具恢复
使用dev_tools、curl方式进行恢复
(1) 执行恢复
POST /_snapshot/backup1/snapshot_20221013/_restore { "indices": "index_1,index_2", "ignore_unavailable": true, "include_global_state": true, "rename_pattern": "index_(.+)", "rename_replacement": "restored_index_$1", "include_aliases": false }
-
_restore : 为恢复快照命令
-
indices: 要恢复的索引,支持使用索引别名, 支持通配符表达式, 多个索引名称用逗号拼接,如果不指定
indices
, 则表示恢复所有索引. -
ignore_unavailable : 为true则恢复时忽略不可用的索引
-
include_global_state:设置为false,则不把快照中的状态恢复到当前集群中
-
rename_pattern: 可省参数,重命名索引的匹配规则,使用此参数可以让A索引恢复到B索引上,有点类似重建或重命名的意思,无此参数则直接还原到当前索引【当前索引必须是close状态】,此参数与rename_replacement须一起使用,一般使用正则匹配
-
rename_replacement:可省参数,重命名恢复的索引,须与rename_pattern结合使用,【$1】为rename_pattern中匹配到的数据,restored_index_则为恢复的索引添加了一个新的前缀名
-
include_aliases: false 防止别名与关联索引一起还原
(2) 终止恢复-慎用
DELETE /_snapshot/backup1/snapshot_20221013
索引重建
POST _reindex { "source": { "index": "app1_pay_order_index" }, "dest": { "index": "app1_pay_order_index_new" } }
索引设置别名
添加&删除别名
POST /_aliases { "actions": [ #删除别名 { "remove": { "index": "app1_pay_order", "alias": "app1_pay_order2" } }, #添加别名 { "add": { "index": "app1_pay_order_new", "alias": "app1_pay_order2" }} ] }
GET _alias
查询指定索引别名
GET app1_pay_order/_alias
十一、ES常用操作命令
-
es启动
-
命令:./bin/elasticsearch
-
访问地址:http://localhost:9200/ 默认9200端口
-
-
Kibana 启动
-
命令:./bin/kibana
-
访问地址: http://localhost:5600/ 默认5600端口
-
-
查看集群状态检查集群运行情况: GET -> localhost:9200/_cat/health?v查看集群节点列表: GET -> localhost:9200/_cat/nodes查看所有索引: GET -> localhost:9200/_cat/indices?v
-
索引操作API
-
es集群所的操作都可以http方式进行操作,当然也可以使用kibana上的开发工具操作,kibana操作更简单些。
-
//1.查询查看分片状态-Authorization方式(postman通过账密获取token) curl -XGET ‘http://127.0.0.1:9200/_cluster/allocation/explain?pretty’ --header ‘Authorization’: Basic ZWxhc3RpYzphcDIwcE9QUzIw’ //2.查询查看分片状态-账密方式 curl -XGET -u elastic "http://127.0.0.1:9200/_cluster/allocation/explain?pretty" -H ‘Content-Type:application/json’ //3.查询集群状态命令 curl -XGET -u elastic:elastic123 "http://127.0.0.1:9200/_cluster/health?pretty" //4.查询Es全局状态 curl -XGET -u elastic:elastic123 "http://127.0.0.1:9200/_cluster/stats?pretty" //5.查询集群设置 curl -XGET -u elastic:elastic123 "http://127.0.0.1:9200/_cluster/settings?pretty" //6.查询集群文档总数 curl -XGET -u elastic:elastic123 "http://127.0.0.1:9200/_cat/count?v" //7.查看当前集群索引分片信息 curl -XGET -u elastic:elastic123 "http://127.0.0.1:9200/_cat/shards?v" //8.查看集群实例存储详细信息 curl -XGET -u elastic "http://127.0.0.1:9200/_cat/allocation?v" //9.查看当前集群的所有实例 curl -XGET -u elastic "http://127.0.0.1:9200/_cat/nodes?v" //10.查看当前集群等待任务 curl -XGET -u elastic "http://127.0.0.1:9200/_cat/pending_tasks?v" //11.查看集群查询线程池任务 curl -XGET -u elastic "http://127.0.0.1:9200/_cat/thread_pool/search?v" //12.查看集群写入线程池任务 curl -XGET -u elastic "http://127.0.0.1:9200/_cat/thread_pool/bulk?v" //13.清理ES所有缓存 curl -XPOST "http://127.0.0.1:9200/_cache/clear" //14.查询索引信息 curl -XGET -u : ‘https://127.0.0.1:9200/licence_info_test?pretty’ //15.关闭索引 curl -XGET -u : ‘https://127.0.0.1:9200/my_index/_close?pretty’ //16.打开索引 curl -XGET -u : ‘https://127.0.0.1:9200/my_index/_open?pretty’
kibana工具
-
十二、ES 相关资料
-
gitHub地址
官方文档
-
wiki
-
Elasticsearch 集群协调迎来新时代
-
https://www.elastic.co/cn/blog/a-new-era-for-cluster-coordination-in-elasticsearch
-
-
Elasticsearch 集群内应该设置多少个分片?
-
https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster
-
-
Es spring-boot-starter wiki
-
https://docs.spring.io/spring-data/elasticsearch/docs/4.3.7/reference/html/#reference
-
-