目录
一、背景
二、内容概要
三、基础概念
四、ES版本选择
Elasticsearch版本选择
ES客户端SDK版本选择
五、Elasticsearch多租户
多租户下的架构
租户的识别和路由
六、ES 集群搭建
集群部署架构
单集群示例
多集群示例
ES集群搭建配置
安装中文分词插件 IK
七、ES SDK及增删改查Demo
八、ES管理工具
安装集群管理工具- Kibana
es-header插件安装使用
九、ES集群安全策略
X-pack安全策略开启及集群配置
十、ES运维
数据备份与恢复
索引重建
索引设置别名
十一、ES常用操作命令
十二、ES 相关资料
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"
}
}
}
}
注:由于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版本。
类型\版本 | 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基本也能满足目前需求,稳定性也更有保障
|
Spring Boot兼容性
|
2.1 ~ 2.2版本对6.x支持 |
2.3 ~ 2.7版本对7.x支持
|
/ |
基于目前应用中使用的spring boot版本,只能选择 7.x版本
|
客户端 | 适用版本 | 优点 | 缺点 | 建议 |
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版本进行匹配 | 推荐使用 |
es官方下载地址,es和kibana尽量下载同一版本
elasticsearch各版本下载地址
https://www.elastic.co/cn/downloads/past-releases#elasticsearch
kibana (es的可视化管理工具)
https://www.elastic.co/cn/downloads/past-releases/#kibana
先找任一个节点修改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
./bin/elasticsearch
bin/elasticsearch-setup-passwords interactive
#数据和日志存储路径
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
-Xms6g
-Xmx6g
sudo vim /etc/sysctl.conf
#添加参数
vm.max_map_count = 262144
#重新加载配置
sysctl -p
curl -XGET -u elastic:elastic123 "http://127.0.0.1:9200/_cluster/health?pretty"
curl -XPUT -u elastic:elastic123 "http://127.0.0.1:9200/test-index"
curl -XGET -u elastic:elastic123 "http://localhost:9200/_cat/indices?pretty"
PUT _cluster/settings
{
"persistent": {
"action.auto_create_index": "false"
}
}
PUT _cluster/settings { "persistent": { "action.auto_create_index": "false" } }
PUT _template/template_http_request_record
{
"index_patterns": [
"*"
],
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
合理设置分片意义
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个 比较合适
./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":"中国河南南阳"
}
完整代码可以从github上下载
https://github.com/caizi12/my-elasticsearch-starter.git
代码说明
封装了易于使用的elasticsearch starter,使用时可以先把代码Deploy到私有仓库中,然后应用程序中依赖使用,如果没有私有仓库可以把代码copy到应用中使用。
Deploy到仓库后使用方式
com.my.es
elasticsearch-starter
1.0.0-SNAPSHOT
#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
@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
*/
String addIndexDoc(String indexName, T model);
/**
* 添加一个数据到索引中,推荐使用@addIndexDoc(T model)
*
* @param indexName 索引名
* @param model 索引数据,注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 文档ID
*/
String addIndexDoc(String indexName, T model, boolean nonTenantMode);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 文档数据,注解@Id的字段值不允许为空
* @return
*/
String addIndexDoc(T model);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 文档数据,注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
String addIndexDoc(T model, boolean nonTenantMode);
/**
* 添加一个数据到索引中,指定数据版本号
*
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @return
*/
String saveIndexDoc(T model, Long version);
/**
* 添加一个数据到索引中,指定数据版本号
*
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
String saveIndexDoc(T model, Long version, boolean nonTenantMode);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
* 指定数据版本号
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @return
*/
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
*/
String saveIndexDoc(String indexName, T model, Long version, boolean nonTenantMode);
/**
* 批量添加索引,推荐使用@bulkAddIndexDoc(Class> clazz, List docList)
*
* @param indexName
* @param docList
* @return
*/
List bulkAddIndexDoc(String indexName, List docList);
/**
* 批量添加索引,推荐使用@bulkAddIndexDoc(Class> clazz, List docList)
*
* @param indexName
* @param docList
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
List bulkAddIndexDoc(String indexName, List docList, boolean nonTenantMode);
/**
* 批量添加索引
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @return
*/
List bulkSaveIndexDoc(String indexName, List docList);
/**
* 批量添加索引
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
List bulkSaveIndexDoc(String indexName, List docList, boolean nonTenantMode);
/**
* 批量添加索引,会自动获取类上的 @Document(indexName)属性当索引名
*
* @param clazz
* @param docList
* @return
*/
List bulkAddIndexDoc(Class> clazz, List docList);
/**
* 批量添加索引,会自动获取类上的 @Document(indexName)属性当索引名
*
* @param clazz
* @param docList
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
List bulkAddIndexDoc(Class> clazz, List docList, boolean nonTenantMode);
/**
* 批量添加索引
*
* @param clazz 会自动获取类上的 @Document(indexName)属性当索引名
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @return
*/
List bulkSaveIndexDoc(Class> clazz, List docList);
/**
* 批量添加索引
*
* @param clazz 会自动获取类上的 @Document(indexName)属性当索引名
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
List bulkSaveIndexDoc(Class> clazz, List docList, boolean nonTenantMode);
/**
* 更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 注解@Id的字段值不允许为空
* @return
*/
UpdateResponse.Result updateDoc(T model);
/**
* 更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
UpdateResponse.Result updateDoc(T model, boolean nonTenantMode);
/**
* 批量更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param clazz
* @param 注解@Id的字段值不允许为空
* @return
*/
List bulkUpdateDoc(Class> clazz, List modelList);
/**
* 批量更新文档
*
* @param clazz
* @param 注解@Id的字段值不允许为空
* @param bulkOptions
* @return
*/
List bulkUpdateDoc(Class> clazz, List 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 bulkDelIndexDoc(Class> clazz, List ids);
/**
* 批量删除索引
*
* @param clazz
* @param ids
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
List bulkDelIndexDoc(Class> clazz, List ids, boolean nonTenantMode);
/**
* 删除一个索引文档,会自动从类上获取注解为@Id属性的value当作ID
*
* @param model
* @param
* @return
*/
String delIndexDoc(T model);
/**
* 删除一个索引文档,会自动从类上获取注解为@Id属性的value当作ID
*
* @param model
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
String delIndexDoc(T model, boolean nonTenantMode);
/**
* @param docId
* @param tClass
* @param
* @return
*/
T findById(String docId, Class tClass);
/**
* @param docId
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @param
* @return
*/
T findById(String docId, Class clazz, boolean nonTenantMode);
/**
* 根据ID批量查询
*
* 使用id查询数据实时性更好
*
* @param indexName
* @param clazz
* @param docIdList
* @param nonTenantMode
* @param
* @return
*/
List findByIds(String indexName, Class clazz, List docIdList, boolean nonTenantMode) ;
List findByIds(Class clazz, List docIdList) ;
List findByIds(Class clazz, List docIdList,boolean nonTenantMode) ;
/**
* 更丰富灵活的索引查询,开放spring-boot-es-starter原生NativeSearchQueryBuilder
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param queryBuilder
* @return
*/
SearchHits search(Class clazz, NativeSearchQueryBuilder queryBuilder);
/**
* 更丰富灵活的索引查询,开放spring-boot-es-starter原生NativeSearchQueryBuilder
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param queryBuilder
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
SearchHits search(Class clazz, NativeSearchQueryBuilder queryBuilder, boolean nonTenantMode);
/**
* 封装查询对象,简化NativeSearchQueryBuilder构造过程
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param request
* @return
*/
SearchHits search(Class clazz, MyEsSearchRequest request);
/**
* 封装查询对象,简化NativeSearchQueryBuilder构造过程
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param request
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
SearchHits search(Class clazz, MyEsSearchRequest request, boolean nonTenantMode);
/**
* 精确查询类场景推荐使用,es不会计算文档相关性分值,性能更好
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param filterBuilder
* @param pageable
* @return
*/
SearchHits searchByFilter(Class clazz, QueryBuilder filterBuilder, @Nullable Pageable pageable);
/**
* 精确查询类场景推荐使用,es不会计算文档相关性分值,性能更好
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param filterBuilder
* @param pageable
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
SearchHits searchByFilter(Class clazz, QueryBuilder filterBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
/**
* 标题或文章内容检索类场景推荐使用,es会计算文档相关性,并按相关性自动排序
*
* @param clazz
* @param queryBuilder
* @param pageable
* @return
*/
SearchHits search(Class clazz, QueryBuilder queryBuilder, @Nullable Pageable pageable);
/**
* 标题或文章内容检索类场景推荐使用,es会计算文档相关性,并按相关性自动排序
*
* @param clazz
* @param queryBuilder
* @param pageable
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
SearchHits search(Class clazz, QueryBuilder queryBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
/**
* 索引数据查询
*
* @param clazz 索引类
* @param queryBuilder
* @param filterBuilder
* @return
*/
SearchHits search(Class clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder, @Nullable Pageable pageable);
/**
* 索引数据查询
*
* @param clazz 索引类
* @param queryBuilder
* @param filterBuilder
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
SearchHits search(Class 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 String addIndexDoc(T model) {
return addIndexDoc(getEsIndexName(model.getClass()), model);
}
@Override
public 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 String addIndexDoc(String indexName, T model) {
return addIndexDoc(indexName, model, false);
}
@Override
public 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
* @return
*/
@Override
public String saveIndexDoc(T model, Long version) {
return saveIndexDoc(model, version, false);
}
@Override
public 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 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 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 List bulkAddIndexDoc(Class> clazz, List docList) {
return bulkAddIndexDoc(getEsIndexName(clazz), docList);
}
@Override
public List bulkAddIndexDoc(Class> clazz, List docList, boolean nonTenantMode) {
return bulkAddIndexDoc(getEsIndexName(clazz), docList, nonTenantMode);
}
@Override
public List bulkSaveIndexDoc(Class> clazz, List docList) {
return bulkSaveIndexDoc(clazz, docList, false);
}
@Override
public List bulkSaveIndexDoc(Class> clazz, List docList, boolean nonTenantMode) {
return bulkSaveIndexDoc(getEsIndexName(clazz), docList, nonTenantMode);
}
/**
* 批量新增文档
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @return 文档ID
*/
public List bulkAddIndexDoc(String indexName, List docList) {
return bulkAddIndexDoc(indexName, docList, false);
}
/**
* 批量新增文档
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 文档ID
*/
@Override
public List bulkAddIndexDoc(String indexName, List docList, boolean nonTenantMode) {
Assert.notNull(indexName, "bulkAddIndexDoc elasticsearch indexName is null");
Assert.notNull(docList, "bulkAddIndexDoc document is null");
List 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
* @return
*/
@Override
public List bulkSaveIndexDoc(String indexName, List docList) {
return bulkSaveIndexDoc(indexName, docList, false);
}
/**
* 批量新增文档
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
@Override
public List bulkSaveIndexDoc(String indexName, List 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 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
* @return Elasticsearch 文档
*/
public T findById(String indexName, String docId, Class clazz, boolean nonTenantMode) {
if (StringUtils.isNotEmpty(docId) && clazz != null) {
return elasticsearchRestTemplate.get(docId, clazz, buildIndexCoordinates(indexName, nonTenantMode));
}
return null;
}
public T findById(String docId, Class clazz) {
return findById(docId, clazz, false);
}
@Override
public T findById(String docId, Class clazz, boolean nonTenantMode) {
return findById(getEsIndexName(clazz), docId, clazz, nonTenantMode);
}
/**
* 根据多个ID查询文档
*
* @param indexName 索引名称
* @param docIdList 文档ID
* @param clazz 映射类Class
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @param
* @return Elasticsearch 文档
*/
@Override
public List findByIds(String indexName, Class clazz, List docIdList, boolean nonTenantMode) {
if (CollectionUtils.isEmpty(docIdList) || clazz == null || indexName == null) {
return null;
}
StringQuery query = StringQuery.builder("stringQuery").withIds(docIdList).build();
List> 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 List findByIds(Class clazz,List docIdList) {
return findByIds( clazz, docIdList,false);
}
@Override
public List findByIds(Class clazz, List 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 UpdateResponse.Result updateDoc(T elasticsearchModel) {
return updateDoc(elasticsearchModel, false);
}
@Override
public 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 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 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 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 List bulkUpdateDoc(Class> clazz, List modelList) {
return bulkUpdateDoc(clazz, modelList, null);
}
public List bulkUpdateDoc(Class> clazz, List 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 List bulkUpdateDoc(Class> clazz, List modelList, BulkOptions bulkOptions,
ObjectMapper objectMapper) {
Assert.notNull(clazz, "bulkUpdateDoc clazz is null");
Assert.notNull(clazz, "bulkUpdateDoc modelList is null");
try {
List 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 String getDocumentIdValue(T elasticsearchModel) {
return EsReflectUtils.getDocumentIdValue(elasticsearchModel);
}
private Long getDocumentVersionValue(T elasticsearchModel) {
return EsReflectUtils.getDocumentVersionValue(elasticsearchModel);
}
/**
* 查询文档
*
* @param clazz 映射文档类 文档需标注@Document注解、包含@Id注解字段
* @param queryBuilder 非结构化数据 QueryBuilder; queryBuilder与filterBuilder必须二者存在其一
* @param filterBuilder 过滤查询
* @param
* @return
*/
public SearchHits search(Class clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder,
Pageable pageable) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, filterBuilder, pageable);
return search(clazz, request);
}
@Override
public SearchHits search(Class clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder,
Pageable pageable, boolean nonTenantMode) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, filterBuilder, pageable);
return search(clazz, request, nonTenantMode);
}
public SearchHits searchByFilter(Class clazz, QueryBuilder filterBuilder, Pageable pageable) {
MyEsSearchRequest request = new MyEsSearchRequest(null, filterBuilder, pageable);
return search(clazz, request);
}
@Override
public SearchHits searchByFilter(Class clazz, QueryBuilder filterBuilder, @javax.annotation.Nullable Pageable pageable, boolean nonTenantMode) {
MyEsSearchRequest request = new MyEsSearchRequest(null, filterBuilder, pageable);
return search(clazz, request, nonTenantMode);
}
public SearchHits search(Class clazz, QueryBuilder queryBuilder, Pageable pageable) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, null, pageable);
return search(clazz, request);
}
@Override
public SearchHits search(Class clazz, QueryBuilder queryBuilder, @javax.annotation.Nullable Pageable pageable, boolean nonTenantMode) {
MyEsSearchRequest request = new MyEsSearchRequest(queryBuilder, null, pageable);
return search(clazz, request, nonTenantMode);
}
public SearchHits search(Class clazz, MyEsSearchRequest request) {
return search(clazz, request, false);
}
@Override
public SearchHits search(Class clazz, MyEsSearchRequest request, boolean nonTenantMode) {
return search(getEsIndexName(clazz), clazz, request.getQueryBuilder(), request.getFilterBuilder(),
request.getAggregationBuilder(), request.getPageable(), request.getQueryFields(), nonTenantMode);
}
public SearchHits search(Class clazz, NativeSearchQueryBuilder queryBuilder) {
return search(clazz, queryBuilder, false);
}
@Override
public SearchHits search(Class clazz, NativeSearchQueryBuilder queryBuilder, boolean nonTenantMode) {
return elasticsearchRestTemplate.search(queryBuilder.build(), clazz, buildIndexCoordinates(clazz, nonTenantMode));
}
/**
* 查询文档
*
*
* 查询的文档必须包含映射@Document的@Id字段
*
*
* @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 SearchHits search(String indexName, Class 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 String delIndexDoc(T model) {
return delIndexDoc(model, false);
}
@Override
public 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 bulkDelIndexDoc(Class> clazz, List ids) {
return bulkDelIndexDoc(clazz, ids, false);
}
/**
* 根据ID批量删除
* 官方未提供根据id批量删除的,暂时就以循环删除的方式来操作,若有大批量操作存在性能问题考虑转为query delete方式
*
* @param clazz
* @param ids
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 返回每个ID删除后的返回结果
*/
@Override
public List bulkDelIndexDoc(Class> clazz, List 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 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 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 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 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 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 hits = myEsService.search(Student.class, request);
System.out.println(JSONObject.toJSONString(hits));
}
}
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插件使用
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
类别
|
优点
|
缺点
|
建议
|
nginx
|
对外屏蔽了ES集群的真实IP和端口,配置也较简单
|
只能做一些网络访问安全上面的防护,不能对索引及字段进行精确控制
|
选择X-pack
经验证在免费情况下X-Pack能满足基本诉求,对应用中使用影响也比较小
|
Search Guard
|
开源免费,基于RBAC权限模型设计,能够细粒度进行管控
|
配置复杂,需要安装证书及业务应用代码改造
|
|
X-Pack
|
官方提供,基于RBAC权限模型设计,能够细粒度进行管控,与client api及es集群兼容性较好
|
配置稍复杂,基础部分功能免费
|
bin/elasticsearch-certutil ca
bin/elasticsearch-certutil cert --ca elastic-stack-ca.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
path.repo: ["/data/backups", "/data2/backups"]
curl -u elastic:elastic123 -X POST 'http://ip:9200/_snapshot/jingkai_backup/snapshot/_status'
PUT /_snapshot/backup1 { "type": "fs", "settings": { "location": "/data/backups", "compress": true, "max_snapshot_bytes_per_sec": "50mb", "max_restore_bytes_per_sec": "50mb" } }
PUT /_snapshot/backup1/snapshot_20221013?wait_for_completion=true { "indices": "index_1,index_2", "ignore_unavailable": true, "include_global_state": false }
GET /_snapshot/backup1/snapshot_20221013/_status
"indices": "index_1,index_2"
: 可以使用该参数只对部分索引进行备份
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 }
indices
, 则表示恢复所有索引.
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
//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工具