# 概述
TIP
Elastic:[ɪ'læstɪk]。adj.橡皮圈(或带)的;有弹性的;有弹力的;灵活的
# 学习目标 & 内容
# 目标
- 开发
- 产品基本功能
- 低层原理
- 数据建模最佳实践
- 运维
- 容量规划
- 性能优化
- 问题诊断,异常检测
- 滚动升级
- 方案
- 搜索如何解决搜索的相关性问题
- 大数据分析实践与项目实战,理论知识的实际运用
# 内容与结构
- ES 入门与深入
- 环境搭建 / 搜索与聚合 / 架构原理 / 数据建模
- ELK 进行大数据分析
- 可视化分析 / 时序型数据 / 异常检测
- ES 集群管理
- 水平扩展及性能优化 / 最佳实践
- 项目实战
# ELK Stack 简介
# ElasticSearch
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。Github、Stack Overflow等都在使用。
突出优点:
- 速度快:通过有限状态转换器实现了用于全文检索的倒排索引,实现了用于存储数值数据和地理位置数据的 BKD 树,以及用于分析的列存储
- 可扩展性:可以在笔记本电脑上运行。 也可以在承载了 PB 级数据的成百上千台服务器上运行。
- 弹性:Elasticsearch 运行在一个分布式环境中,检测硬件故障、网络分割故障并确保您的集群(和数据)的安全性和可用性
分布式、RESTful 风格的搜索和数据分析引擎,可以集中存储数据
- 主要功能
- 分布式搜索引擎
- 大数据近实时分析引擎
- 产品特性
- 高性能,和 T+1 说不
- 容易使用 / 扩展
# Logstash 和 Beats
Logstash
动态数据收集管道,拥有可扩展的插件生态系统
Beats
面向轻量型采集器的平台,这些采集器可从边缘机器发送数据。
# Kibana
可视化数据
# 6 索引管理—RestClient
# 6.1 搭建工程
ES提供多种不同的客户端:
TransportClient:ES提供的传统客户端,官方计划8.0版本删除此客户端。- RestClient:官方推荐使用的,它包括两种 Java Low Level REST Client和 Java High Level REST Client ES在6.0之后提供 Java High Level REST Client 还处于完善中,有些功能还没有。
Maven依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.2.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.2.1</version>
</dependency>
创建搜索工程(maven工程,直接导入):xc-service-search,添加RestHighLevelClient依赖及junit依赖。
application.yml
server:
port: ${port:40100}
spring:
application:
name: xc-search-service
xuecheng:
elasticsearch:
hostlist: ${eshostlist:127.0.0.1:9200} #多个结点中间用逗号分隔
配置类com.xuecheng.search.config.ElasticsearchConfig
@Configuration
public class ElasticsearchConfig {
@Value("${xuecheng.elasticsearch.hostlist}")
private String hostlist;
@Bean
public RestHighLevelClient restHighLevelClient(){
//解析hostlist配置信息
String[] split = hostlist.split(",");
//创建HttpHost数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for(int i=0;i<split.length;i++){
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
//创建RestHighLevelClient客户端
return new RestHighLevelClient(RestClient.builder(httpHostArray));
}
//项目主要使用RestHighLevelClient,对于低级的客户端暂时不用
@Bean
public RestClient restClient(){
//解析hostlist配置信息
String[] split = hostlist.split(",");
//创建HttpHost数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for(int i=0;i<split.length;i++){
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
return RestClient.builder(httpHostArray).build();
}
}
启动类
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.search")//扫描实体类
@ComponentScan(basePackages={"com.xuecheng.api"})//扫描接口
@ComponentScan(basePackages={"com.xuecheng.search"})//扫描本项目下的所有类
@ComponentScan(basePackages={"com.xuecheng.framework"})//扫描common下的所有类
public class SearchApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SearchApplication.class, args);
}
}
# 6.2 API+RestClient
# 6.2.1 创建索引/映射
创建索引(库):put http://localhost:9200/索引名称
{
"settings":{
"index":{
"number_of_shards":1,
"number_of_replicas":0
}
}
}
创建映射:put http://localhost:9200/xc_course/doc/_mapping
{
"properties": {
"name": {
"type": "text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
},
"description": {
"type": "text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
},
"studymodel": {
"type": "keyword"
},
"price": {
"type": "float"
},
"timestamp": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis"
}
}
}
# 6.2.2 添加文档
POST/PUT http://localhost:9200/{index}/{type}/{id} { "field": "value", ... },不指定id会自动生成
{
"name":"spring cloud实战",
"description":"本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战SpringBoot 4.注册中心eureka。",
"studymodel":"201001",
"price":5.6
}
# 6.2.3 根据ID查询文档
POST/GET /{index}/{type}/{id}
# 6.2.4 更新文档
ES更新文档的顺序是:先检索到文档、将原来的文档标记为删除、创建新文档、删除旧文档,创建新文档就会重建索引。
通过请求Url有两种方法:
完全替换
POST http://localhost:9200/xc_test/doc/3{ "name":"spring cloud实战", "description":"本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战SpringBoot 4.注册中心eureka。", "studymodel":"201001" "price":5.6 }局部更新
POST http://localhost:9200/xc_test/doc/3/_update{ "doc":{"price":66.6} }
# 6.2.5 删除文档
根据id删除DELETE /{index}/{type}/{id}
搜索匹配删除,将搜索出来的记录删除POST /{index}/{type}/_delete_by_query
{
"query":{
"term":{
"studymodel":"201001"
}
}
}
上边例子的搜索匹配删除会将studymodel为201001的记录全部删除
搜索匹配删除还没有具体的api,可以采用先搜索出文档id,根据文档id删除。
# 6.2.6 删除索引库
手动
# 6.2.7 Java Client
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestIndex {
@Autowired
private RestHighLevelClient client;
@Autowired
private RestClient restClient;
//创建索引库
@Test
public void testCreateIndex() throws IOException {
//创建索引对象
CreateIndexRequest createIndexRequest = new CreateIndexRequest("xc_course");
//设置参数
createIndexRequest.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));
//指定映射
createIndexRequest.mapping("doc", " {\n" +
" \t\"properties\": {\n" +
" \"studymodel\":{\n" +
" \"type\":\"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\":\"keyword\"\n" +
" },\n" +
" \"description\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\":\"ik_max_word\",\n" +
" \"search_analyzer\":\"ik_smart\"\n" +
" },\n" +
" \"pic\":{\n" +
" \"type\":\"text\",\n" +
" \"index\":false\n" +
" }\n" +
" \t}\n" +
"}", XContentType.JSON);
//操作索引的客户端
IndicesClient indices = client.indices();
//执行创建索引库
CreateIndexResponse createIndexResponse = indices.create(createIndexRequest);
//得到响应
boolean acknowledged = createIndexResponse.isAcknowledged();
System.out.println(acknowledged);
}
//删除索引库
@Test
public void testDeleteIndex() throws IOException {
//删除索引对象
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("xc_course");
//操作索引的客户端
IndicesClient indices = client.indices();
//执行删除索引
DeleteIndexResponse delete = indices.delete(deleteIndexRequest);
//得到响应
boolean acknowledged = delete.isAcknowledged();
System.out.println(acknowledged);
}
//添加文档
@Test
public void testAddDoc() throws IOException {
//文档内容
//准备json数据
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("name", "spring cloud实战");
jsonMap.put("description", "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战Spring Boot 4.注册中心eureka。");
jsonMap.put("studymodel", "201001");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
jsonMap.put("timestamp", dateFormat.format(new Date()));
jsonMap.put("price", 5.6f);
//创建索引创建对象
IndexRequest indexRequest = new IndexRequest("xc_course", "doc");
//文档内容
indexRequest.source(jsonMap);
//通过client进行http的请求
IndexResponse indexResponse = client.index(indexRequest);
DocWriteResponse.Result result = indexResponse.getResult();
System.out.println(result);
}
//根据ID查询文档
@Test
public void testGetDoc() throws IOException {
//查询请求对象
GetRequest getRequest = new GetRequest("xc_course", "doc", "ZEqZP2kBDP-dgPpQBZbx");
GetResponse getResponse = client.get(getRequest);
//得到文档的内容
Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
System.out.println(sourceAsMap);
}
//更新文档—局部更新
@Test
public void updateDoc() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("xc_course", "doc","ZEqZP2kBDP-dgPpQBZbx");
Map<String, String> map = new HashMap<>();
map.put("name", "spring cloud实战2");
updateRequest.doc(map);
UpdateResponse update = client.update(updateRequest);
RestStatus status = update.status();
System.out.println(status);
}
//根据id删除文档
@Test
public void testDelDoc() throws IOException {
//删除索引请求对象
DeleteRequest deleteRequest = new DeleteRequest("xc_course","doc","ZEqZP2kBDP-dgPpQBZbx");
//响应对象
DeleteResponse deleteResponse = client.delete(deleteRequest);
//获取响应结果
DocWriteResponse.Result result = deleteResponse.getResult();
System.out.println(result);
}
//搜索匹配删除还没有具体的api,可以采用先搜索出文档id,根据文档id删除。
}
# 7 搜索管理—DSL搜索
DSL(Domain Specific Language)是ES提出的基于json的搜索方式,在搜索时传入特定的json数据来完成不同的搜索需求。
DSL比URI搜索方式功能强大,在项目中建议使用DSL方式来完成搜索。
# 7.1 准备环境
环境准备
创建xc_course索引(库):
put http://localhost:9200/xc_course创建如下映射:
post http://localhost:9200/xc_course/doc/_mapping{ "properties": { "description": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "name": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "pic":{ "type":"text", "index":false }, "price": { "type": "float" }, "studymodel": { "type": "keyword" }, "timestamp": { "type": "date", "format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis" } } }向xc_course/doc中插入以下数据,如
POST http://localhost:9200/xc_course/doc/1,指定id分别为1、2、3{ "name": "Bootstrap开发", "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。", "studymodel": "201002", "price":38.6, "timestamp":"2018‐04‐25 19:11:35", "pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" }{ "name": "spring开发基础", "description": "spring 在java领域非常流行,java程序员都在用。", "studymodel": "201001", "price":88.6, "timestamp":"2018‐02‐24 19:11:35", "pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" }{ "name": "spring开发基础", "description": "spring 在java领域非常流行,java程序员都在用。", "studymodel": "201001", "price":88.6, "timestamp":"2018‐02‐24 19:11:35", "pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" }简单搜索:通过url进行查询,以get方式请求ES
GET ../_search?q=.....如
http://localhost:9200/xc_course/doc/_search?q=name:java,搜索name中包括java的文档
# 7.2 查询所有文档(search**中)
查询所有索引(库)的文档:
POST http://localhost:9200/_search查询指定索引库指定类型下的文档(通过使用此方法):
POST http://localhost:9200/xc_course/doc/_search{ "query": { "match_all": {} }, "_source" : ["name","studymodel"] }_source:source源过滤设置,指定结果中所包括的字段有哪些。查询结果分析:
{ "took": 13, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 3, "max_score": 1, "hits": [ { "_index": "xc_course", "_type": "doc", "_id": "1", "_score": 1, "_source": { "studymodel": "201002", "name": "Bootstrap开发" } }, ] } }took:本次操作花费的时间,单位为毫秒
timed_out:请求是否超时
_shards:说明本次操作共搜索了哪些分片
hits:搜索命中的记录
hits.total : 符合条件的文档总数 hits.hits :匹配度较高的前N个文档
hits.max_score:文档匹配得分,这里为最高分
_score:每个文档都有一个匹配度得分,按照降序排列
_source:显示了文档的原始内容
# 7.3 分页查询(search**中)
POST http://localhost:9200/xc_course/doc/_search
{
"from" : 0, "size" : 1,
"query": {
"match_all": {}
},
"_source" : ["name","studymodel"]
}
form:表示起始文档的下标,从0开始。
size:查询的文档数量(记录数)。
【注意】命中的总记录数就是全部的,但是在遍历记录时只会遍历到符合分页条件的
# 7.4 精确查询 term/ id(search**中)
在搜索时会整体匹配关键字,不再将搜索关键字分词
POST http://localhost:9200/xc_course/doc/_search
{
"query": {
"term" : {
"name": "spring"
}
},
"_source" : ["name","studymodel"]
}
ES提供根据多个id值匹配的方法:
POST http://127.0.0.1:9200/xc_course/doc/_search
{
"query": {
"ids" : {
"type" : "doc",
"values" : ["2", "3", "100"]
}
}
}
【注意】termQuery 和 termsQuery 区别
# 7.5 match(search**中)
match query与Term query区别是match query在搜索前先将搜索关键字分词,再拿各各词语去索引中搜索。
POST http://localhost:9200/xc_course/doc/_search
{
"query": {
"match" : {
"description" : {
"query" : "spring开发框架",
"operator" : "or",
"minimum_should_match": "80%"
}
}
}
}
query:搜索的关键字,对于英文关键字如果有多个单词则中间要用半角逗号分隔,而对于中文关键字中间可以用逗号分隔也可以不用。operatoror表示只要有一个词在文档中出现则就符合条件and表示每个词都在文档中出现则才符合条件。
minimum_should_match指定文档匹配词的占比。“spring开发框架”会被分为三个词:spring、开发、框架"80%"表示,三个词在文档的匹配占比为80%,即3*0.8=2.4,向下取整得2,表示至少有两个词在文档中要匹配成功。
# 7.6 ==multi match==(search**中)
上边学习的termQuery和matchQuery一次只能匹配一个Field,本节学习multiQuery,一次可以匹配多个字段。
POST http://localhost:9200/xc_course/doc/_search
{
"query": {
"multi_match" : {
"query" : "spring css",
"minimum_should_match": "50%",
"fields": [ "name^10", "description" ]
}
}
}
提升boost,通常关键字匹配上name的权重要比匹配上description的权重高,这里可以对name的权重提升。
“name^10” 表示权重提升10倍
# 7.7 ==布尔查询==(search**中,组合其他)
布尔查询对应于Lucene的BooleanQuery查询,实现将多个查询组合起来。三个参数:
- must:文档必须匹配must所包括的查询条件,相当于 “AND”
- should:文档应该匹配should所包括的查询条件其中的一个或多个,相当于 "OR"
- must_not:文档不能匹配must_not所包括的该查询条件,相当于“NOT”
POST http://localhost:9200/xc_course/doc/_search
{
"_source" : [ "name", "studymodel", "description"],
"from" : 0, "size" : 1,
"query": {
"bool" : {
"must":[
{
"multi_match" : {
"query" : "spring框架",
"minimum_should_match": "50%",
"fields": [ "name^10", "description" ]
}
},
{
"term":{
"studymodel" : "201001"
}
}
]
}
}
}
# 7.8 ==过滤器==(布尔中)
过虑是针对搜索的结果进行过虑,过虑器主要判断的是文档是否匹配,不去计算和判断文档的匹配度得分,所以过虑器性能比查询要高,且方便缓存,推荐尽量使用过虑器去实现查询或者过虑器和查询共同使用。过虑器在布尔查询中使用
{
"_source" : [ "name", "studymodel", "description","price"],
"query": {
"bool" : {
"must":[
{
"multi_match" : {
"query" : "spring框架",
"minimum_should_match": "50%",
"fields": [ "name^10", "description" ]
}
}
],
"filter": [
{ "term": { "studymodel": "201001" }},
{ "range": { "price": { "gte": 60 ,"lte" : 100}}}
]
}
}
}
term:项匹配过虑,保留studymodel等于"201001"的记录。range:范围过虑,保留大于等于60 并且小于等于100的记录。
【注意】:range和term一次只能对一个Field设置范围过虑。
# 7.9 ==排序==(search**中)
可以在字段上添加一个或多个排序,支持在keyword、date、float等类型上添加,text类型的字段上不允许添加排序。
过虑0--10元价格范围的文档,并且对结果进行排序,先按studymodel降序,再按价格升序:
POST http://localhost:9200/xc_course/doc/_search
{
"_source": ["name","studymodel","description","price"],
"query": {
"bool": {
"filter": [
{ "range": { "price": {"gte": 0,"lte": 100}} }
]
}
},
"sort": [
{ "studymodel": "desc" },
{ "price": "asc" }
]
}
# 7.10 ==高亮==(只在search**中)
高亮显示可以将搜索结果一个或多个字突出显示,以便向用户展示匹配关键字的位置。
POST http://127.0.0.1:9200/xc_course/doc/_search
{
"_source" : [ "name", "studymodel", "description","price"],
"query": {
"bool" : {
"must":[
{
"multi_match" : {
"query" : "开发框架",
"minimum_should_match": "50%",
"fields": [ "name^10", "description" ],
"type":"best_fields"
}
}
],
"filter": [
{ "range": { "price": { "gte": 0 ,"lte" : 100}}}
]
}
},
"sort" : [
{
"price" : "asc"
}
],
"highlight": {
"pre_tags": ["<tag>"],
"post_tags": ["</tag>"],
"fields": {
"name": {},
"description":{}
}
}
}
# 7.11 RestClient
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestSearch {
@Autowired
RestHighLevelClient client;
@Autowired
RestClient restClient;
private SearchRequest searchRequest = null;////搜索请求对象,传入索引(可变参数)、类型
private SearchSourceBuilder searchSourceBuilder = null;//搜索源构建对象
private SearchHit[] searchHits = null;//匹配度高的文档数组
@Before
public void init() {
//搜索请求对象,传入索引(可变参数)
searchRequest = new SearchRequest("xc_course");
searchRequest.types("doc");
//搜索源构建对象
searchSourceBuilder = new SearchSourceBuilder();
}
//搜索全部记录
@Test
public void testSearchAll() {
//搜索方式
//matchAllQuery搜索全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
}
//分页查询
@Test
public void testSearchPage() {
searchSourceBuilder.from(0);//(当前页-1)*每页显示记录数
searchSourceBuilder.size(2);
//搜索方式
//matchAllQuery搜索全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
}
//TermQuery,注意termQuery
@Test
public void testTermQuery() {
searchSourceBuilder.query(QueryBuilders.termQuery("name", "spring"));
}
//根据id查询,注意termsQuery
@Test
public void testTermQueryByIds() {
searchSourceBuilder.query(QueryBuilders.termsQuery("_id", "1", "2", "100"));
}
//MatchQuery。以下三个任选其一
@Test
public void testMatchQuery() {
searchSourceBuilder.query(QueryBuilders.matchQuery("description", "spring开发框架").operator(Operator.OR));
searchSourceBuilder.query(QueryBuilders.matchQuery("description", "spring开发框架").operator(Operator.AND));
searchSourceBuilder.query(QueryBuilders.matchQuery("description", "spring开发框架").minimumShouldMatch("8%"));
}
//MultiMatchQuery
@Test
public void testMultiMatchQuery() {
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("spring css", "name", "description").field("name", 10));
}
//BoolQuery
@Test
public void testBoolQuery() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.multiMatchQuery("spring框架", "name", "description").field("name", 10))
.must(QueryBuilders.termQuery("studymodel", "201001"));
searchSourceBuilder.query(boolQueryBuilder);
}
//filter,在BoolQuery中使用
@Test
public void testFilter() {
//定义一个boolQuery,并用must和filter过滤
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.multiMatchQuery("spring框架", "name", "description").field("name", 10))
.filter(QueryBuilders.termQuery("studymodel", "201001"))
.filter(QueryBuilders.rangeQuery("price").gte(90).lte(100));
QueryBuilders
searchSourceBuilder.query(boolQueryBuilder);
}
//Sort,在searchSourceBuilder中使用
@Test
public void testSort() {
//定义一个boolQuery,用filter过滤
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.filter(QueryBuilders.rangeQuery("price").gte("0").lte("100"));
searchSourceBuilder.query(boolQueryBuilder)
.sort("studymodel", SortOrder.DESC)
.sort("price", SortOrder.ASC);
}
//Highlight
@Test
public void testHighlight() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.multiMatchQuery("开发框架", "name", "description").field("name", 10))
.filter(QueryBuilders.rangeQuery("price").gte(0).lte(100));
searchSourceBuilder
.query(boolQueryBuilder)
.highlighter(new HighlightBuilder().preTags("<tag>").postTags("</tag>").field("name").field("description"));
}
//字段过滤等
@After
public void loopShow() throws IOException, ParseException {
//设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段
searchSourceBuilder.fetchSource(new String[]{"name", "studymodel", "price", "timestamp"}, new String[]{});
//向搜索请求对象中设置搜索源
searchRequest.source(searchSourceBuilder);
//执行搜索,向ES发起http请求
SearchResponse searchResponse = client.search(searchRequest);
//搜索结果
SearchHits hits = searchResponse.getHits();
//匹配到的总记录数(此时总记录数是1,是真正匹配的数目)
long totalHits = hits.getTotalHits();
//得到匹配度高的文档
searchHits = hits.getHits();//SearchHit[]
for (SearchHit hit : searchHits) {
String index = hit.getIndex();//索引(库)名称
String id = hit.getId();//文档的主键
//源文档内容
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//由于前边设置了源文档字段过虑,这时description是取不到的,为null
String description = (String) sourceAsMap.get("description");
//学习模式
String studymodel = (String) sourceAsMap.get("studymodel");
//价格
Double price = (Double) sourceAsMap.get("price");
//日期(有可能因为传入的字符串格式不是UTF-8,编码问题,会解析时报错)
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
//源文档的name字段内容(由于description过滤了,所以不考虑)
String name = (String) sourceAsMap.get("name");
//取出高亮字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (Objects.nonNull(highlightFields)) {
//取出name高亮字段
HighlightField nameHighlightField = highlightFields.get("name");
if (Objects.nonNull(nameHighlightField)) {
//组合name高亮片段
Text[] fragments = nameHighlightField.getFragments();
StringBuilder buffer = new StringBuilder();
for (Text text : fragments) {
buffer.append(text);
}
name = buffer.toString();
}
}
}
}
}
# 8 集群管理
# 8.1 集群结构
ES通常以集群方式工作,这样做不仅能够提高 ES的搜索能力还可以处理大数据搜索的能力,同时也增加了系统的容错能力及高可用,ES可以实现PB级数据的搜索。

结点
ES集群由多个服务器组成,每个服务器即为一个Node结点(该服务只部署了一个ES进程)。
分片
当我们的文档量很大时,由于内存和硬盘的限制,同时也为了提高ES的处理能力、容错能力及高可用能力,我们将 索引分成若干分片,每个分片可以放在不同的服务器,这样就实现了多个服务器共同对外提供索引及搜索服务。 一个搜索请求过来,会分别从各个分片去查询,最后将查询到的数据合并返回给用户。
副本
为了提高ES的高可用同时也为了提高搜索的吞吐量,我们将分片复制一份或多份存储在其它的服务器,这样即使当 前的服务器挂掉了,拥有副本的服务器照常可以提供服务。
主结点
一个集群中会有一个或多个主结点,主结点的作用是集群管理,比如增加节点,移除节点等,主结点挂掉后ES会重 新选一个主结点。
结点转发
每个结点都知道其它结点的信息,我们可以对任意一个结点发起请求,接收请求的结点会转发给其它结点查询数据。
# 8.2 搭建集群
# 8.2.4 创建索引库
使用head连上其中一个结点,下图表示两个结点已经创建成功。

创建索引(库),共2个分片,每个分片1个副本。

创建成功,刷新head。下图可以看到共有4个分片,其中两个分片是副本。

# 8.2.5 集群的健康
通过访问 GET /_cluster/health 来查看Elasticsearch 的集群健康情况。
green:所有的主分片和副本分片都正常运行。
yellow:所有的主分片都正常运行,但有些副本分片运行不正常。
red:存在主分片运行不正常。
# 8.3 测试
连接其中任意一台结点,创建映射写入文档。
POST http://localhost:9200/xc_course/doc/1{ "name": "spring开发基础", "description": "spring 在java领域非常流行,java软件开发人员都在用。", "studymodel": "201001", "price":66.6 }响应结果:提示可看出,两个分片都保存成功。
{ "_index": "xc_course", "_type": "doc", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }搜索:向其它一个结点发起搜索请求,查询全部数据。
关闭一个结点,ES会重新选中一个主结点(前提在配置结点时允许它可以为主结点)
此时向活的结点发起搜索请求,仍然正常。
添加一个结点3
结点名:xc_node_3,http端口是:9202,集群管理端口是9302,配置:
node.master: false node.data: true启动结点3,刷新head,下图显示ES将分片分在了3个结点

向结点3发起搜索请求:全部数据可被正常搜索到。
# 9 Spring Data Elasticsearch
配置 →