Elasticsearch的调用方式
- HTTP Restful调用
- kibana操作(dev tools)
- 客户端操作(java)
1.客户端操作Elasticsearch
方式一:es官方的Java API
快速开始https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/connecting.html方式二:HighLevelRestClient (过时)
方式三:Spring Data Elasticsearch(推荐)
自定义方法:用户可以指定接口的方法名称,框架帮忙自动生成
Spring-Data-系列 :Spring提供的操作数据的框架
Spring-Data-reids :操作redis的一套方法
注意! 版本强一致!
官方文档https://docs.spring.io/spring-data/elasticsearch/docs/4.4.10/reference/html/
2.Elasticsearch实现搜索接口
2.0.1-------------------------------------------首先:Elasticsearch配置信息---------------------------------------
spring:
elasticsearch:
uris: http://localhost:9200
username: xxx
password: xxx
2.0.2引jar包(依赖)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2.0.3编写实体类(xxxEsDTO)
!!! @Document(indexName = "post")
注解:指明该实体类对应的Elasticsearch的索引
2.1数据库表结构
---post---帖子表
-- 帖子表
create table if not exists post
(
id bigint auto_increment comment 'id' primary key,
title varchar(512) null comment '标题',
content text null comment '内容',
tags varchar(1024) null comment '标签列表(json 数组)',
thumbNum int default 0 not null comment '点赞数',
favourNum int default 0 not null comment '收藏数',
userId bigint not null comment '创建用户 id',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
index idx_userId (userId)
) comment '帖子' collate = utf8mb4_unicode_ci;
2.2Elasticsearch索引结构
---post---帖子表索引
索引结构(ES mapping)的相关概念
- id可以不放在字段设置中
- es中尽量存放用户需要筛选(搜索)的数据
- aliases:别名,方便后期对数据迁移
- type:type为 " text ",可以分词,可以模糊查询;如果为" keyword ",只能精确匹配
- analyzer:存储时生效的分词器,使用"ik_max_word",拆的碎,索引更多,更有可能搜出来
- search_analyzer:搜索生效的分词器,使用"ik_smart",偏向用户想搜的词
- 若想让type类型为text的字段支持精确匹配,可以keyword类型的子字段
与数据库的表结构相比,索引mapping的不同之处:
thumbNum点赞数,favourNum收藏数不存入es中
id不在字段设置中,因为存入es成功系统会生成id
PUT post_v1
{
"aliases": {
"post": {}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"tags": {
"type": "keyword"
},
"userId": {
"type": "keyword"
},
"createTime": {
"type": "date"
},
"updateTime": {
"type": "date"
},
"isDelete": {
"type": "keyword"
}
}
}
}
2.3简易-增删改查
ElasticsearchRepository<T, id>提供默认简单的增删改查
public interface PostEsDao extends ElasticsearchRepository<PostEsDTO, Long> {
List<PostEsDTO> findByUserId(Long userId);
}
接口中提供的方法
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAllById(Iterable<? extends ID> ids);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
ES 中,_开头的字段表示系统默认字段,比如 _id,如果系统不指定,会自动生成。但是 不会在 _source 字段中补充 id 的值,所以建议大家手动指定。
2.3.1测试客户端新增数据到es
@Resource
private PostEsDao postEsDao;
@Resource
private PostService postService;
@Test
void testAdd() {
PostEsDTO postEsDTO = new PostEsDTO();
postEsDTO.setId(2L);
postEsDTO.setTitle("test2");
postEsDTO.setContent("test2");
postEsDTO.setTags(Arrays.asList("java", "python"));
postEsDTO.setUserId(1L);
postEsDTO.setCreateTime(new Date());
postEsDTO.setUpdateTime(new Date());
postEsDTO.setIsDelete(0);
postEsDao.save(postEsDTO);
System.out.println(postEsDTO.getId());
}
2.3.2elasticsearch中查询结果
GET post/_doc/2
{
"_index" : "post_v1",
"_type" : "_doc",
"_id" : "2",
"_version" : 2,
"_seq_no" : 12,
"_primary_term" : 1,
"found" : true,
"_source" : {
"_class" : "com.yupi.springbootinit.model.dto.post.PostEsDTO",
"id" : 2,
"title" : "test2",
"content" : "test2",
"tags" : [
"java",
"python"
],
"userId" : 1,
"createTime" : "2023-09-08T02:25:09.473Z",
"updateTime" : "2023-09-08T02:25:09.473Z",
"isDelete" : 0
}
}
客户端查询es数据---根据id查询
@Test
void testFindById() {
Optional<PostEsDTO> postEsDTO = postEsDao.findById(2L);
System.out.println(postEsDTO);
}
控制台输出的结果 --- elasticsearch查询结果一致
2.4自定义方法-增删改查
例如,自定义根据标签title搜索的方法
List<PostEsDTO> findByTitle(String title);
对于复杂的查询
- 在es中梳理查询的逻辑
- 按照es的逻辑自定义Java客户端查询方法
示例代码
必须:title字段必须包含"test",content字段必须包含"test"
过滤查询:isDelete字段为0(精确查询),createTime大于"2015-01-01"(范围查询)
满足以上条件(bool)才可以被查询到
GET post/_search
{
"query": {
"bool": { //bool组合查询
"must": [ //must必须都满足
{ "match": { "title": "test" }}, //match模糊查询
{ "match": { "content": "test" }}
],
"filter": [
{ "term": { "isDelete": 0 }}, //term精确查询
{ "range": { "createTime": { "gte": "2015-01-01" }}} //range范围查询
]
}
}
}
示例:关于文章的es搜索 PostServiceImpl的searchFromEs方法部分代码
// 过滤
boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0));
if (id != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("id", id));
}
if (notId != null) {
boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId));
}
if (userId != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId));
}
// 必须包含所有标签
if (CollectionUtils.isNotEmpty(tagList)) {
for (String tag : tagList) {
boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag));
}
}
// 包含任何一个标签即可
if (CollectionUtils.isNotEmpty(orTagList)) {
BoolQueryBuilder orTagBoolQueryBuilder = QueryBuilders.boolQuery();
for (String tag : orTagList) {
orTagBoolQueryBuilder.should(QueryBuilders.termQuery("tags", tag));
}
orTagBoolQueryBuilder.minimumShouldMatch(1);
boolQueryBuilder.filter(orTagBoolQueryBuilder);
}
// 按关键词检索
if (StringUtils.isNotBlank(searchText)) {
boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText));
boolQueryBuilder.should(QueryBuilders.matchQuery("description", searchText));
boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText));
boolQueryBuilder.minimumShouldMatch(1);
}
// 按标题检索
if (StringUtils.isNotBlank(title)) {
boolQueryBuilder.should(QueryBuilders.matchQuery("title", title));
boolQueryBuilder.minimumShouldMatch(1);
}
// 按内容检索
if (StringUtils.isNotBlank(content)) {
boolQueryBuilder.should(QueryBuilders.matchQuery("content", content));
boolQueryBuilder.minimumShouldMatch(1);
}
// 排序
SortBuilder<?> sortBuilder = SortBuilders.scoreSort();
if (StringUtils.isNotBlank(sortField)) {
sortBuilder = SortBuilders.fieldSort(sortField);
sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC);
}
// 分页
PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize);
// 构造查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
.withPageable(pageRequest).withSorts(sortBuilder).build();
SearchHits<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);
Page<Post> page = new Page<>();
page.setTotal(searchHits.getTotalHits());
List<Post> resourceList = new ArrayList<>();
3.数据同步
3.1全量同步(首次)
/**
* 全量同步帖子到 es
*
*/
// todo 取消注释开启任务
//@Component
@Slf4j
public class FullSyncPostToEs implements CommandLineRunner {
@Resource
private PostService postService;
@Resource
private PostEsDao postEsDao;
@Override
public void run(String... args) {
List<Post> postList = postService.list();
if (CollectionUtils.isEmpty(postList)) {
return;
}
List<PostEsDTO> postEsDTOList = postList.stream().map(PostEsDTO::objToDto).collect(Collectors.toList());
final int pageSize = 500;
int total = postEsDTOList.size();
log.info("FullSyncPostToEs start, total {}", total);
for (int i = 0; i < total; i += pageSize) {
int end = Math.min(i + pageSize, total);
log.info("sync from {} to {}", i, end);
postEsDao.saveAll(postEsDTOList.subList(i, end));
}
log.info("FullSyncPostToEs end, total {}", total);
}
}
3.2定时任务
定时任务,⽐如 1 分钟 1 次,找到 MySQL 中过去⼏分钟内(⾄少是定时周期的 2 倍)发⽣改变的 数据,然后更新到 ES。
3.3Logstash数据同步管道
(⼀般要配合 kafka 消息队列 + beats 采集器)
运行cong文件出现bug :could not find java; set JAVA_HOME or ensure java is in PATH
参考文章 https://mp.csdn.net/mp_blog/creation/editor/132796288
3.3.1新建conf文件
Logstash的config目录下新建对数据处理的配置文件 xxx.conf
jdbc.conf --- 数据库中取更新的文件并插入es
sql_last_value: 是取上次查到的数据的最后⼀⾏的指定的字段,如果要全量更新,只要删除掉
logstash_jdbc_last_run 文件即可(这个⽂件存储了上次同步到的数据)
- tracking_column => "updatetime" #使用递增列的值
- tracking_column_type => "timestamp" #递增字段的类型
- use_column_value => true #使用递增列的值
----------------------------------------------------jdbc.conf-----------------------------------------------------------------
input {
jdbc {
jdbc_driver_library => "E:\software\elasticsearch\logstash\config\mysql-connector-java-8.0.29.jar"
jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
jdbc_connection_string => "jdbc:mysql://localhost:3306/my_db_search"
jdbc_user => "xxx"
jdbc_password => "xxxxxx"
statement => "SELECT * from post where updateTime > :sql_last_value order by updateTime desc"
tracking_column => "updatetime"
tracking_column_type => "timestamp"
use_column_value => true
parameters => { "favorite_artist" => "Beethoven" }
schedule => "*/5 * * * * *"
jdbc_default_timezone => "Asia/Shanghai"
}
}
output {
stdout { codec => rubydebug }
elasticsearch {
hosts => "http://localhost:9200"
index => "post"
document_id => "%{id}"
}
}
3.3.2启动配置文件
bin目录下命令行:Logstash logstash.bat -f ..\config\jdbc.conf
在数据库中修改一条记录,结果--->
控制台输入内容
elasticsearch查询发现 es与数据库的更改同步!
3.3.3数据处理
输出结果不是预期的,例如:
①有一些不想出现的字段:thumbnum,favournum等
②字段全变成小写:userid,updatetime等
可以编写过滤:
#过滤
filter {
mutate {
#重命名
rename => {
"updatetime" => "updateTime"
"userid" => "userId"
"createtime" => "createTime"
"isdelete" => "isDelete"
}
#移除不需要的字段
remove_field => ["thumbnum", "favournum"]
}
}
4.数据可视化看板
---面试题
1、elasticsearch 的倒排索引是什么?
传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表 即为倒排索引。
传统的检索,我们是查看一篇文章包含了哪些词;倒排索引,它从词出发,记载了这个词在哪些文 档中出现过,由两部分组成——词典和倒排表。