Elasticsearch
一、基本概念
1、index(索引)
动词,相当于MYSQL中的insert;
名词,相当于MYSQL中的database;
2、Type(类型)
在index(索引)中,可以定义一个或者多个类型。
类似于MYSQL中的table;每一种类型的数据放在一起;
3、Document(文档)
保存在某个索引(Index)下,某种类型(Type)的一个数据(Document) ,文档是ISON格式的,Document就像是MySQL中的某个Table里面的内容;
4、倒排索引机制

二、Docker安装
安装elasticsearch
1、下载镜像
docker pull elasticsearch:7.4.2 # 储存和检索数据
docker pull kibana:7.4.2 #可视化界面
2、创建实例
# 在mydata文件夹下创建es的config文件夹,将docker中es的配置挂载在外部,当我们在linux虚拟机中修改es的配置文件时,就会同时修改docker中的es的配置
mkdir -p /mydata/elasticsearch/config
#在mydata文件夹下创建es的data文件夹
mkdir -p /mydata/elasticsearch/data
# [http.host:0.0.0.0]允许任何远程机器访问e 注意yml配置文件中key: value格式冒号后面要跟一个空格。
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
# 启动实例
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2
# docker run --name elasticsearch 创建一个es容器并起一个名字;
# -p 9200:9200 将linux的9200端口映射到docker容器的9200端口,用来给es发送http请求
# -p 9300:9300 9300是es在分布式集群状态下节点之间的通信端口 \ 换行符
# -e 指定一个参数,当前es以单节点模式运行
# *注意,ES_JAVA_OPTS非常重要,指定开发时es运行时的最小和最大内存占用为64M和128M,否则就会占用全部可用内存
# -v 挂载命令,将虚拟机中的路径和docker中的路径进行关联
# -d 后台启动服务
# 可以使用此命令检查日志
docker logs elasticsearch
# 修改一下 es 的配置文件的权限
chmod -R 777 /mydata/elasticsearch/
在浏览器地址栏访问http://49.233.141.36:9200/

安装kibana
#安装 注意,一定要将修改为自己的虚拟机或服务器地址
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://49.233.141.36:9200 -p 5601:5601 \
-d kibana:7.4.2
浏览器访问http://49.233.141.36:5601/
三、初步探索
1、_cat
查看节点信息
http://49.233.141.36:9200/_cat/nodes
查看健康状况
http://49.233.141.36:9200/_cat/health
查看主节点信息
http://49.233.141.36:9200/_cat/master
查看所有索引(相当于查看所有数据库)
http://49.233.141.36:9200/_cat/indices
2、索引一个文档(保存)
PUT请求
保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识PUT customer/external/1;
比如:在 customer 索引下的 external 类型下保存 1 号数据为

#put请求 索引(库)/类型(表)/唯一标识
PUT customer/external/1
得到响应如下:
# _为元数据
{
"_index": "customer", # 索引下
"_type": "external", # 索引下
"_id": "1", # id
"_version": 1, # 版本
"result": "created", # 结果
"_shards": { # 分片
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0, # 乐观锁操作
"_primary_term": 1
}
PUT请求不带id会失败
POST请求
#POST请求 索引(库)/类型(表)/唯一标识
POST customer/external/1
post请求既可以新增也可以更新
新增:不带id,但之前没有数据
修改:带id,并且有数据
3、查询文档
#POST请求 索引(库)/类型(表)/唯一标识
POST customer/external/1
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 2,
"_seq_no": 1, # 并发控制字段,每次更新就会+1,用来做乐观锁
"_primary_term": 1, # 同上,主分片重新分配,如重启,就会变化
"found": true, # 表示找到了数据
"_source": {
"name": "lohn Doe"
}
}
乐观锁修改
更新的时候携带参数 ?if_seq_no=0&if_primary_term=1
{{host}}/customer/external/1?if_seq_no=1&if_primary_term=1
4、更新文档
| 更新操作 | 参数或结论 |
|---|---|
| POST customer/external/1/_update | { “doc”: { “name”: “Jane Doe”, “age”: 20 } } |
| 或者POST customer/external/1 | { “name”: “John Nash2” } |
| 或者PUT customer/external/1 | { “name”: “John Nash3” } |
| 不同 | POST 操作会对比源文档数据,如果相同不会有什么操作,文档 version 、_seq_no 不增加; PUT 操作总会将数据重新保存并增加 version 版本; 带 _update 对比元数据如果一样就不进行任何操作。 |
| 看场景 | 对于大并发更新,不带update; 对于大并发查询偶尔更新,带update;对比更新,重新计算分配规则。 |
| 更新同时增加属性 POST customer/external/1/_update | { “doc”: { “name”: “Jane Doe”, “age”: 20 } } |
| 更新同时增加属性 PUT&POST customer/external/1 | { “name”: “John Nash2”, “age”: 40 } |
5、删除文档&索引
| 删除类型 | 方法或路径参数 |
|---|---|
| 删除文档 | DELETE customer/external/1 |
| 删除索引 | DELETE customer |
那么问题来了,既然可以删除文档和索引,那么能不能删除类型呢?
在 ES 中,一个索引下有很多种类型,但是 ES 没有提供删除类型的方法,删除了索引,就会删除所有类型。
6、bulk 批量 API
| 操作 | 参数 |
|---|---|
| POST customer/external/_bulk | {“index” {"_id":“1”} {“name”: “John Nash”} {“index”:"_id"2"} {“name”: “Jane Nash”} |
| 语法格式 | {action: {metadata}} {request body}\n {action: {metadata}}\n {request body}\n |
| 复杂实例 POST /_bulk |
{“delete”:{"_index":“website”,"_type":“blog”,"_id":“123”}} {“create”:{"_index":“website”,"_type":“blog”,"_id":“123”}} {“title”:“My first blog post”} {“index”:{"_index":“website”,"_type":“blog”}} {“title”:“My second blog post”} {“update”:{"_index":“website”,"_type":“blog”,"_id":“123”}} {“doc”:{“title”:“My updated blog post”}} |

POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}
#! Deprecation: [types removal] Specifying types in bulk requests is deprecated.
{
"took" : 17,
"errors" : false,
"items" : [
{
"index" : {
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 8,
"_primary_term" : 1,
"status" : 201
}
},
{
"index" : {
"_index" : "customer",
"_type" : "external",
"_id" : "2",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 9,
"_primary_term" : 1,
"status" : 201
}
}
]
}
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"My first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"My second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"My updated blog post"}}
访问下面的地址导入测试数据
使用POST bank/account/_bulk命令导入
四、进阶检索
1、search API
ES支持两种基本方式检索:
- 一个是通过使用 REST request URI,发送搜索参数(uri+检索参数)
- 另一个是通过使用 REST request body 来发送它们(uri+请求体)
参考官方文档:
#- query 定义如何查询;
#- match_all 查询类型【代表查询所有的所有】, es 中可以在 query 中组合非常多的查询类型完成复杂查询
#- 除了 query 参数之外,我们也可以传递其它的参数以改变查询结果。如 sort,size;
#- from+size 限定,完成分页功能;
#- sort 排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准
GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": "asc"
},
{
"balance": "desc"
}
]
}
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
],
"from": 1,
"size": 2
}
# 返回部分字段 只返回 _source 中指定的字段,类似于 MySQL 中的 select field_1,field_2,... from table
GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"balance": {
"order": "desc"
}
}
],
"from": 0,
"size": 5,
"_source": ["balance","firstname"]
}
# multi_match 【多字段匹配】得分最高的在前面
GET /bank/_search
{
"query": {
"multi_match": {
"query": "mill",
"fields": ["address","state"]
}
}
}
GET /bank/_search
{
"query": {
"multi_match": {
"query": "mill movico",
"fields": ["address","city"]
}
}
}
# must 子句(查询)必须出现在匹配的文档中,并将有助于得分。
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "F"
}
},
{
"match": {
"address": "Mill"
}
}
]
}
}
}
# must_not 子句(查询)不得出现在匹配的文档中。子句在过滤器上下文中执行,这意味着计分被忽略,并且子句被视为用于缓存。由于忽略计分,0因此将返回所有文档的分数。
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "F"
}
},
{
"match": {
"address": "Mill"
}
}
],
"must_not": [
{"match": {
"age": 31
}}
]
}
}
}
# should 子句(查询)应出现在匹配的文档中。
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "Mill"
}
}
],
"must_not": [
{"match": {
"age": 30
}}
],
"should": [
{"match": {
"lastname": "Holland"
}}
]
}
}
}
# filter 子句(查询)必须出现在匹配的文档中。但是不像 must查询的分数将被忽略。Filter子句在filter上下文中执行,这意味着计分被忽略,并且子句被考虑用于缓存。
# 在 filter 元素下指定的查询对得分没有影响-得分以 0 形式返回。分数仅受指定查询的影响。
GET /bank/_search
{
"query": {
"bool": {
"must": [
{"range": {
"age": {
"gte": 18,
"lte": 30
}
}}
]
}
}
}
# 使用 filter 来替代 must 查询,需要注意的是,使用filter查询出的结果和must查询出的结果是一致的,差异仅是没有相关性得分:
GET /bank/_search
{
"query": {
"bool": {
"filter": {
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}
}
}
}
# 我们在 should 之后还可以加上 filter 条件进行过滤:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "Mill"
}
}
],
"must_not": [
{"match": {
"age": 30
}}
],
"should": [
{"match": {
"lastname": "Holland"
}}
],
"filter": {
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}
}
}
}
# term
# 和 match 一样。匹配某个属性的值。全文检索字段用 match,其他非 text 字段匹配用 term。
GET /bank/_search
{
"query": {
"term": {
"age":28
}
}
}
# match 的 xxx.keyword,文本的精确匹配检索,没有.keyword就是全文分词匹配
GET /bank/_search
{
"query": {
"match": {
"address.keyword": "789 Madison"
}
}
}
#match_phrase,将需要匹配的值当成一个整体单词(不分词)进行检索:
GET /bank/_search
{
"query": {
"match_phrase": {
"address": "789 Madison"
}
}
}
# 注意:如果对于文本值使用 term 检索时,并不会进行分词,而是精确检索,所以可能会匹配不到数据:
- 、aggregations (执行聚合) 聚合提供了从数据中分组和提取数据的能力。 最简单的聚合方法大致等于 SQL GROUP BY 和 SQL 聚合函数。 在 Elasticsearch 中,您有执行搜索返回 hits (命中结果),并且同时返回聚合结果, 把一个响应中的所有hits(命中结果)分隔开的能力。这是非常强大且有效的,您可以执行查询和多个聚合, 并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的 API 来避免网络往返。 aggregations 查询语法:
"aggregations" : {
"<aggregation_name>" : {
"<aggregation_type>" : {
<aggregation_body>
}
[,"meta" : { [<meta_data_body>] } ]?
[,"aggregations" : { [<sub_aggregation>]+ } ]?
}
[,"<aggregation_name_2>" : { ... } ]*
}
- 搜索address中包含mill的所有人的年龄分布以及平均年龄,但不显示这些人的详情。
GET /bank/_search
{
"query": { //查询
"match": {
"address": "mill"
}
},
"aggs": { //聚合
"ageAgg": { //年龄分布
"terms": {
"field": "age",
"size": 10 //只取10中聚合的结果
}
},
"ageAvg":{//平均年龄,基于上一次的结果
"avg": {
"field": "age"
}
},
"balanceAvg":{//平均薪资
"avg": {
"field": "balance"
}
}
},
"size": 0 //不显示搜索数据,只显示聚合结果
}
aggs,执行聚合。聚合语法如下:
"aggs":{
"ages_name 这次聚合的名字,方便展示在结果集中":{
"AGG-TYPE 聚合的类型(avg,term,terms) ":{}
}
}
- 复杂聚合:按照年龄聚合,并且请求这些年龄段的这些人的平均薪资(使用一个子聚合)
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {//年龄范围分布聚合
"field": "age",
"size": 100//返回100中情况
},
"aggs": {//基于ageAgg的结果做聚合
"ageAvg": {
"avg": {//求balance的平均值
"field": "balance"
}
}
}
}
}
}
- 复杂聚合进阶:查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均新资
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {//聚合
"ageAgg":{
"terms": {//年龄分布
"field": "age",
"size": 100
},
"aggs": {//基于ageAgg做聚合
"genderAgg": {//性别分布
"terms": {
//文本字段聚合使用keyword进行精确匹配,否则会报错
"field": "gender.keyword",
"size": 10
},
"aggs": {//基于genderAgg做聚合
"balanceAvg": {//求性别为M和F的各自的平均薪资
"avg": {
"field": "balance"
}
}
}
},
"ageBalanceAvg":{//基于ageAgg,求各个年龄段的平均薪资
"avg": {
"field": "balance"
}
}
}
}
}
}
3、Mapping
1)、字段类型
- 核心类型
- 字符串(string) text,keyword
- 数字类型(Numeric) long, integer, short, byte, double, float, half_float, scaled_float
- 日期类型(Date) date
- 布尔类型(Boolean) boolean
- 二进制类型(Binary) binary
- 复合类型
- 数组类型(Array) Array 支持不针对特定的数据类型
- 对象类型(Object) object 用于单个JSON对象的对象
- 嵌套类型(Nested) nested 用于JSON对象的数组
- 地理类型(Geo)
- 地理坐标(Geo-point) geo_point 纬度/经度坐标
- 地理圆形(Geo-shape) geo_shape 用于多边形等复杂形状
- 特定类型
- IP 类型(IP) ip 用于描述 IPv4 和 IPv6 地址
- 补全类型(Completion) completion 提供自动完成提示
- 令牌计数类型(Token count) token_count 用来统计字符串中词条的数量
- 附件类型(attachment) 参考 mapper-attachments 插件,支持将附件例如Microsoft Office格式,open document格式,ePub,HTML等索引为 attachment 数据类型。
- 抽取类型(Percolator) 接受来自领域特定语言(query-dsl)的查询
更多字段类型,请参考 ES 官方文档:参考文档-mapping-types
2)、映射
Mapping (映射) Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的。比如,使用mapping来定义:
-
哪些字符串属性应该被看做全文本属性(full text fields)
-
哪些属性包含数字,日期或者地理位置
-
文档中的所有属性是否都能被索引(_all 配置)
-
日期的格式
-
自定义映射规则来执行动态添加属性
-
查看 mapping 信息:
-
GET bank/_mapping
-
-
修改 mapping 信息:
-
创建索引
-
PUT /my-index { "mappings": {//映射规则 "properties": { "age": { "type": "integer" }, "email": { "type": "keyword" },//keyword不会进行全文检索 "name": { "type": "text" }//text保存的时候进行分词,搜索的时候进行全文检索 } } }
-
ES 自动猜测的映射类型:
| JSON type | 域 type |
|---|---|
| 布尔型:true、false | boolean |
| 整数:123 | long |
| 浮点数:1.23 | double |
| 字符串,有效日期 2020-02-02 | date |
| 字符串,foo bar | string |
| 对象,也称为哈希,存储对象类型 | object |
3)、新版本改变
ES7 及以上移除了 type 的概念。
关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,但ES中不是这样的。 elasticsearch 是基于 Lucene 开发的搜索引擎,而 ES 中不同 type 下名称相同的 filed 最终在 Lucene 中的处理方式是一样的。 两个不同 type 下的两个 user_name,在 ES 同一个索引下其实被认为是同一个 filed,你必须在两个不同的 type 中定义相同的 filed 映射。否则,不同 type 中的相同字段名称就会在处理中出现冲突的情况,导致 Lucene 处理效率下降。 去掉type就是为了提高 ES 处理数据的效率。 Elasticsearch 7.x:
URL中的 type 参数为可选。比如,索引一个文档不再要求提供文档类型。 Elasticsearch 8.x:
不再支持URL中的type参数。 解决: 1)、将索引从多类型迁移到单类型,每种类型文档一个独立索引 2)、将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移
1、创建映射
创建索引并指定映射
2、添加新的字段映射
PUT /my-index/_mapping
{
"properties": {
"employee-id": {
"type": "keyword",
"index": false//索引选项控制是否对字段值建立索引。 它接受true或false,默认为true。未索引的字段不可查询。
}
}
}
3、更新映射
对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移
4、数据迁移
先创建出 twitter 的正确映射。然后使用如下方式进行数据迁移
# 7.x 之后的写法
POST _reindex //固定写法
{
"source": { //老索引
"index": "twitter"
},
"dest": { //目标索引
"index": "new_twitter"
}
}
# 7.x之前的带 type 的写法
将旧索引的 type 下的数据进行迁移
POST _reindex //固定写法
{
"source": {
"index": "twitter", //老索引
"type": "twitter", //老类型
},
"dest": { //目标索引
"index": "new_twitter"
}
}
举例:
创建一个新的索引:
PUT /newbank
{
"mappings": {
"properties": {
"account_number": {
"type": "long"
},
"address": {
"type": "text"
},
"age": {
"type": "integer"
},
"balance": {
"type": "long"
},
"city": {
"type": "keyword"
},
"email": {
"type": "keyword"
},
"employer": {
"type": "keyword"
},
"firstname": {
"type": "text"
},
"gender": {
"type": "keyword"
},
"lastname": {
"type": "text"
},
"state": {
"type": "keyword"
}
}
}
}
数据迁移:
POST _reindex
{
"source": {
"index": "bank",
"type": "account"
},
"dest": {
"index": "newbank"
}
}
查看迁移后的数据:
可以看到,不用 type,老的数据可以迁移过来
GET newbank/_search
4、分词
一个 tokenizer (分词器)接收一个字符流,将之分割为独立的 tokens (词元,通常是独立的单词),然后输出 tokens流。
例如, whitespace tokenizer 遇到空白字符时分割文本。它会将文本"Quick brown fox!"分割为[Quick, brown, fox!l。
该 tokenizer (分词器)还负责记录各个 term (词条)的顺序或 position位置(用于 phrase短语和 word proximity 词近邻查询) ,以及 term (词条)所代表的原始 word (单词)的 start(起始)和 end (结束)的 character offsets (字符偏移量) (用于高亮显示搜索的内容)。
Elasticsearch 提供了很多内置的分词器,可以用来构建 custom analyzers (自定义分词器) 。
测试 ES 默认的标准分词器:
英文:
POST _analyze
{
"analyzer": "standard",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
英文是按照空格转小写分词
中文:
POST _analyze
{
"analyzer": "standard",
"text": "pafcmall电商项目"
}
汉字被分割成了单个的字,而不是词组,自实际中肯定不合适
1)、安装 ik 分词器
五、Elasticsearch-Rest-Client
Java 操作 ES 的两种方式: 1) 、9300:TCP (我们不在9300操作,官方也不建议)
spring-data-elasticsearch:transport-api.jar; springboot 版本不同,transport-api.jar不同,不能适配es版本 7.x 已经不建议使用,8 以后就要废弃 2)、9200:HTTP(推荐使用)
JestClient:非官方,更新慢 RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦 HttpClient:同上 I Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作, API 层次分明,上手简单最终选择 Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)
SSL加密连接
还可以通过HttpClientConfigCallback配置使用TLS的加密通信。 作为参数接收的org.apache.http.impl.nio.client.HttpAsyncClientBuilder公开了多种配置加密通信的方法:setSSLContext,setSSLSessionStrategy和setConnectionManager(从最低优先级开始按优先级排序)。
当访问在HTTP层上为TLS设置的Elasticsearch集群时,客户端需要信任Elasticsearch使用的证书。 以下是一个示例,当PKCS#12密钥库中有可用的CA证书时,将客户端设置为信任已签署Elasticsearch使用的证书的CA:
// 加载证书
Path trustStorePath = Paths.get("/path/to/truststore.p12");
KeyStore truststore = KeyStore.getInstance("pkcs12");
try (InputStream is = Files.newInputStream(trustStorePath)) {
truststore.load(is, keyStorePass.toCharArray());
}
// 生成加密通信
SSLContextBuilder sslBuilder = SSLContexts.custom()
.loadTrustMaterial(truststore, null);
final SSLContext sslContext = sslBuilder.build();
// 生成客户端
RestClientBuilder builder = RestClient.builder(
// 地址,端口,类型
new HttpHost("localhost", 9200, "https"))
.setHttpClientConfigCallback(new HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setSSLContext(sslContext);
}
});
下面是一个示例,当该CA证书作为PEM编码文件可用时,如何设置客户端来信任已签署了Elasticsearch正在使用的证书的CA。
Path caCertificatePath = Paths.get("/path/to/ca.crt");
CertificateFactory factory =
CertificateFactory.getInstance("X.509");
Certificate trustedCa;
try (InputStream is = Files.newInputStream(caCertificatePath)) {
trustedCa = factory.generateCertificate(is);
}
KeyStore trustStore = KeyStore.getInstance("pkcs12");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", trustedCa);
SSLContextBuilder sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(trustStore, null);
final SSLContext sslContext = sslContextBuilder.build();
RestClient.builder(
new HttpHost("localhost", 9200, "https"))
.setHttpClientConfigCallback(new HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setSSLContext(sslContext);
}
});
当Elasticsearch配置为要求客户端TLS身份验证时(例如,配置了PKI领域时),客户端需要在TLS握手期间提供客户端证书才能进行身份验证。 以下是使用存储在PKCS#12密钥库中的证书和私钥为TLS身份验证设置客户端的示例。
Path trustStorePath = Paths.get("/path/to/your/truststore.p12");
Path keyStorePath = Paths.get("/path/to/your/keystore.p12");
KeyStore trustStore = KeyStore.getInstance("pkcs12");
KeyStore keyStore = KeyStore.getInstance("pkcs12");
try (InputStream is = Files.newInputStream(trustStorePath)) {
trustStore.load(is, trustStorePass.toCharArray());
}
try (InputStream is = Files.newInputStream(keyStorePath)) {
keyStore.load(is, keyStorePass.toCharArray());
}
SSLContextBuilder sslBuilder = SSLContexts.custom()
.loadTrustMaterial(trustStore, null)
.loadKeyMaterial(keyStore, keyStorePass.toCharArray());
final SSLContext sslContext = sslBuilder.build();
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200, "https"))
.setHttpClientConfigCallback(new HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setSSLContext(sslContext);
}
});
如果客户端证书和密钥在密钥库中不可用,而在PEM编码文件中可用,则不能直接使用它们来构建SSLContext。 您必须依靠外部库将PEM密钥解析为PrivateKey实例。 或者,您可以使用外部工具从PEM文件构建密钥库,如以下示例所示:
openssl pkcs12 -export -in client.crt -inkey private_key.pem \
-name "client" -out client.p12
如果未提供显式配置,则将使用系统默认配置。