Springboot搭配ElasticSearch常用api以及面试题
Springboot搭配ElasticSearch常用api
ElasticSearch是什么
Elasticsearch (ES)是一个基于Lucene构建的开源、分布式、RESTful 接口全文搜索引擎。 Elasticsearch 还是一个分布式文档数据库,其中每个字段均是被索引的数据且可被搜索,它 能够扩展至数以百计的服务器存储以及处理PB级的数据。它可以在很短的时间内在存储、搜 索和分析大量的数据。它通常作为具有复杂搜索场景情况下的核心发动机。
es是由java语言编写的
ElasticSearch能干什么
- 他有强大的查询、分析、聚合能力使得它与数据库有良好的配合性
- 从数据获取,存储计算到可视化,ES 开发了一整套解决方案,Logstash 、Beat 负责数据抓取,ES 负责存储计算,kibana 对数据进行展示分析。另外还有收费的 X-Pack 可以实现安全、告警、监控和 ML 等更丰富的功能。ES 在搜索、日志分析、指标分析和安全分析等领域应用广泛。从前端到后端到数据分析,从云服务到最流行的机器学习,ES 都提供了一整套解决方案
ElasticSearch有什么好处
-
横向可扩展性:只需要增加台服务器,做一点儿配置,启动一下Elasticsearch就可以并入集群。
-
分片机制提供更好的分布性:同一个索引分成多个分片(sharding), 这点类似于HDFS的块机制;分而治之的方式可提升处理效率。
-
高可用:提供复制( replica) 机制,一个分片可以设置多个复制,使得某台服务器在宕机的情况下,集群仍旧可以照常运行,并会把服务器宕机丢失的数据信息复制恢复到其他可用节点上。
-
使用简单:共需一条命令就可以下载文件,然后很快就能搭建一一个站内搜索引擎。
ElasticSearch为什么数据库不如它
-
倒排索引 倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。
- 由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字所对应的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。
SpringBoot操作ElasticSearch
导入依赖 由于默认的es依赖支持6.x的所以使用新的依赖
<properties>
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
<!--引入依赖: elasticsearch-rest-high-level-client -->
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
</dependencies>
定义yml文件
es:
host: # 服务器ip 本地的 127.0.0.1
port: 9200
protocol: http
定义配置类读取yml文件信息
package cn.chy.com.config;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* @Description: TODO
* @author: 颜淮川
* @date: 2021年12月7日15:14:11
* 第二遍
*/
@Configuration
@Slf4j
// 读取为es前缀的信息
@ConfigurationProperties("es")
// 这种读取方式必须设置setter方法
@Setter
public class ElasticSearchConfig {
private String host;
private int port;
private String protocol;
public static final RequestOptions REQUEST_OPTIONS;
static {
// 读取RequestOptions 并赋值
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
REQUEST_OPTIONS = builder.build();
}
// 在spring中添加一个 restHighLevelClient 信息
@Bean
public RestHighLevelClient restHighLevelClient() {
// 调用yml文件中配置信息
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, protocol));
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
return restHighLevelClient;
}
}
根据以下查询对应的实体类
package cn.chy.com.es;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description: TODO
* @author: 颜景琦
* @date: 2021年12月07日 10:24
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company {
private String name;
private String job;
private String logo;
private Double payment;
}
增
public boolean save(String index, String id, String jsonResult) {
// 新增请求
IndexRequest request = new IndexRequest(index);
// 新增id
request.id(id);
// 新增内容
request.source(jsonResult, XContentType.JSON);
try {
// 新增响应的结果
IndexResponse response =
// 这边调用的是 他被spring托管的信息 使用@Resource注入一下
restHighLevelClient.index(request, ElasticSearchConfig.REQUEST_OPTIONS);
System.out.println(response);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
### 测试方法
@Test
public void testSave() {
Company company = Company.builder().logo("yyy").name("颜淮川")
.job("2000").payment(2909.3).build();
String result = JSON.toJSONString(company);
esUtil.save("company-index", "10", result);
}
删
public boolean delete(String index, String id) {
DeleteRequest request = new DeleteRequest(index);
request.id(id);
try {
DeleteResponse response =
restHighLevelClient.delete(request, ElasticSearchConfig.REQUEST_OPTIONS);
System.out.println(response);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
### 测试方法
@Test
public void testDelete() {
esUtil.delete("company-index", "10");
}
改
public boolean update(String index, String id, String jsonResult) {
UpdateRequest request = new UpdateRequest(index, id);
request.doc(jsonResult, XContentType.JSON);
try {
UpdateResponse response =
restHighLevelClient.update(request, ElasticSearchConfig.REQUEST_OPTIONS);
System.out.println(response);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
### 测试方法
@Test
public void testUpdate() {
Company company = Company.builder().logo("yyy").name("颜道修")
.job("2000").payment(2909.3).build();
String result = JSON.toJSONString(company);
esUtil.update("company-index", "10", result);
}
查 id
public T getById(String index, String id, Class<T> target) {
GetRequest request = new GetRequest(index, id);
try {
GetResponse response =
restHighLevelClient.get(request, ElasticSearchConfig.REQUEST_OPTIONS);
String result = response.getSourceAsString();
return JSON.parseObject(result, target);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
###
@Test
public void testId() {
// SearchSourceBuilder search = new SearchSourceBuilder();
// search.query(QueryBuilders.termQuery("_id", 10));
Company utilById = esUtil.getById("company-index", "10", Company.class);
System.out.println(utilById);
}
查所有
public List<T> list(String index,
SearchSourceBuilder searchSourceBuilder
, Class<T> target) {
SearchRequest request = new SearchRequest(index);
request.source(searchSourceBuilder);
List<T> tList = new ArrayList<>();
try {
SearchResponse response =
restHighLevelClient.search(request, ElasticSearchConfig.REQUEST_OPTIONS);
SearchHits hits = response.getHits();
SearchHit[] totalHits = hits.getHits();
if (ObjectUtils.isEmpty(totalHits)) {
return null;
}
for (SearchHit totalHit : totalHits) {
tList.add(JSON.parseObject(totalHit.getSourceAsString(), target));
}
return tList;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
### 测试方法
@Test
public void testList() {
SearchSourceBuilder search = new SearchSourceBuilder();
search.query(QueryBuilders.termQuery("name", "张三"));
List<Company> list = esUtil.list("company-index", search, Company.class);
System.out.println(list);
}
查询page信息
public Map<String, Object> pageInfo(String index,
SearchSourceBuilder searchSourceBuilder,
Class<T> target,
int from,
int size) {
Map<String, Object> map = new HashMap<>();
SearchRequest request = new SearchRequest(index);
searchSourceBuilder.from(from);
searchSourceBuilder.size(size);
request.source(searchSourceBuilder);
List<T> tList = new ArrayList<>();
int page = 0;
try {
SearchResponse response =
restHighLevelClient.search(request, ElasticSearchConfig.REQUEST_OPTIONS);
SearchHits hits = response.getHits();
page = (int) Math.ceil(hits.getTotalHits().value / size);
SearchHit[] totalHits = hits.getHits();
if (ObjectUtils.isEmpty(totalHits)) {
return null;
}
for (SearchHit totalHit : totalHits) {
tList.add(JSON.parseObject(totalHit.getSourceAsString(), target));
}
} catch (IOException e) {
e.printStackTrace();
}
map.put("page", page);
map.put("result", tList);
return map;
}
### 测试方法
@Test
public void testPage() {
SearchSourceBuilder search = new SearchSourceBuilder();
search.query(QueryBuilders.termQuery("name", "张三"));
Map<String, Object> map = esUtil.pageInfo("company-index", search, Company.class, 0, 3);
System.out.println(map);
}
面试天堑
- 我要开始了
1.为什么要使用Elasticsearch?
- ==在我们商城中的数据,将来会非常多,所以采用以往的模糊查询,模糊查询前置配置,会放弃索引,导致商品查询是全表扫面,在百万级别的数据库中,效率非常低下,而我们使用ES做一个全文索引,我们将经常查询的商品的某些字段,比如说商品名,描述、价格还有id这些字段我们放入我们索引库里,可以提高查询速度。==
2.详细描述一下Elasticsearch索引文档的过程。
- ==协调节点默认使用文档ID参与计算(也支持通过routing),以便为路由提供合适的分片。 shard = hash(document_id) % (num_of_primary_shards) 当分片所在的节点接收到来自协调节点的请求后,会将请求写入到Memory Buffer,然后定时(默认是每隔1秒)写入到Filesystem Cache,这个从Momery Buffer到Filesystem Cache的过程就叫做refresh; 当然在某些情况下,存在Momery Buffer和Filesystem Cache的数据可能会丢失,ES是通过translog的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到translog中,当Filesystem cache中的数据写入到磁盘中时,才会清除掉,这个过程叫做flush; 在flush过程中,内存中的缓冲将被清除,内容被写入一个新段,段的fsync将创建一个新的提交点,并将内容刷新到磁盘,旧的translog将被删除并开始一个新的translog。 flush触发的时机是定时触发(默认30分钟)或者translog变得太大(默认为512M)时;==
3.详细描述一下Elasticsearch更新和删除文档的过程
- ==删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更; 磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。 在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。==
- 在并发情况下,Elasticsearch如果保证读写一致?
- ==可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突; 另外对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。 对于读操作,可以设置replication为sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置replication为async时,也可以通过设置搜索请求参数_preference为primary来查询主分片,确保文档是最新版本。==
- ElasticSearch中的分片是什么?
- ==在大多数环境中,每个节点都在单独的盒子或虚拟机上运行。 索引 - 在Elasticsearch中,索引是文档的集合。 分片 -因为Elasticsearch是一个分布式搜索引擎,所以索引通常被分割成分布在多个节点上的被称为分片的元素==
- ElasticSearch中的集群、节点、索引、文档、类型是什么?
-
群集是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分。
-
节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。
-
索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。 MySQL =>数据库 ElasticSearch =>索引
-
文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。 MySQL => Databases => Tables => Columns / Rows ElasticSearch => Indices => Types =>具有属性的文档
-
类型是索引的逻辑类别/分区,其语义完全取决于用户。