关于Elasticsearch的调用

Elasticsearch的调用方式

  1. HTTP Restful调用
  2. kibana操作(dev tools)
  3. 客户端操作(java)

1.客户端操作Elasticsearch

方式一:es官方的Java API 

快速开始icon-default.png?t=N7T8https://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的一套方法

注意! 版本强一致!

官方文档icon-default.png?t=N7T8https://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);

对于复杂的查询

  1. 在es中梳理查询的逻辑
  2. 按照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 的倒排索引是什么?

传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表 即为倒排索引。

传统的检索,我们是查看一篇文章包含了哪些词;倒排索引,它从词出发,记载了这个词在哪些文 档中出现过,由两部分组成——词典和倒排表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值