Elasticsearch初探

556 阅读16分钟

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/

-20200623221650061.png)

安装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"}}

访问下面的地址导入测试数据

github.com/elastic/ela…

使用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 检索时,并不会进行分词,而是精确检索,所以可能会匹配不到数据:
  1. 、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//索引选项控制是否对字段值建立索引。 它接受truefalse,默认为true。未索引的字段不可查询。
    }
  }
}

3、更新映射

对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移

参考文档-mapping

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加密连接

www.elastic.co/guide/en/el…

还可以通过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

如果未提供显式配置,则将使用系统默认配置。