ES的介绍和使用

news2024/9/20 12:34:11

全文搜索引擎 Elastic Search

第一节 引言

当系统数据量上了10亿、100亿条的时候,我们用什么数据库好?如何解决单点故障?如何提升检索速度?如何解决统计分析问题?

传统数据库的应对解决方案

  • 关系型数据库

通过主从备份解决数据安全性问题;
通过数据库代理中间件心跳监测,解决单点故障问题;
通过代理中间件将查询语句分发到各个slave节点进行查询,并汇总结果

  • 非关系型数据库

通过副本备份保证数据安全性;
通过节点竞选机制解决单点问题;
先从配置库检索分片信息,然后将请求分发到各个节点,最后由路由节点合并汇总结果

另辟蹊径

存储数据时按有序存储;
将数据和索引分离;
压缩数据;

于是,Elastic Search就在这种背景下诞生了

第二节 Elastic Search 介绍

1.1 ES简介

​ Elastic Search 简称ES,是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。

1.2 ESLucene 的关系

  • Lucene 只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene 非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的
  • ES也使用Java开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API来隐藏 Lucene 的复杂性,从而让全文搜索变得简单

第二节 安装 Elastic Search

2.1 安装 Elastic Search

解压 elasticsearch-7.6.2-windows-x86_64.zip

2.2 安装 Kibana

解压 kibana-7.6.2-windows-x86_64.zip

  • 修改配置

    elasticsearch.hosts: ["http://localhost:9200"]
    

2.3 安装 IK Analyzer

  • 进入 elasticsearch-7.6.2\plugins 目录
  • 创建文件夹 ik
  • elasticsearch-analysis-ik-7.6.2.zip 拷贝至 ik 目录下,然后解压

ELK = Elasticsearch Logstash Kibana => 日志采集系统

第三节 ES 结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结构说明:

  • ES中的索引(index)相当于 MySQL 中的数据库(database),但ES7之后建议每个索引下只存放一种类型(type)
  • ES中的数据分片(shard)是特有的,在集群模式下,海量的数据可以分别存储在不同的分片中,每个分片都有备份数据,其备份数据称为replica,备份数据的主要作用就是在主分片数据不可用的情况下,会将备份数据(replica)作为新的主分片使用
  • ES中的类型(type)相当于 MySQL 中的表(table)
  • ES中的文档(document)相当于 MySQL 中的表的一行数据(row)
  • ES中的字段(field)相当于 MySQL 中的表的字段(field)

第四节 ES 字段类型

4. 1 字符串类型

  • text:一般被用于全文检索。 将当前Field进行分词。
  • keyword:当前Field不会被分词。

4.2 数值类型

  • long:占用8个字节
  • integer:占用4个字节
  • short:占用2个字节
  • byte:占用1个字节
  • double:占用8个字节
  • float:占用4个字节

4.3 范围类型

  • long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
  • integer_range:同上
  • double_range:同上
  • float_range:同上
  • date_range:同上
  • ip_range:同上

4.4 其他类型

  • date类型,针对时间类型指定具体的格式

  • boolean类型,表达true和false

  • binary类型暂时支持Base64 encode string

  • 经纬度类型:geo_point:用来存储经纬度的

  • ip类型:ip:可以存储IPV4或者IPV6

第五节 ES RESTful 操作

5.1 索引操作

5.1.1 创建索引
# 创建索引  请求方式只能是PUT   /user表示创建一个user索引
# settings表示这个索引的一些设置  number_of_shards表示分片数量
# number_of_replicas 表示每个分片的副本数量
PUT /user
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}

结果:

# acknowledged表示索引创建的回执信息,也就是响应的结果
# shards_acknowledged表示索引分片创建的回执信息
# index表示索引的名称
{
  "acknowledged" : true, 
  "shards_acknowledged" : true,
  "index" : "user"
}
5.1.2 查看索引
GET /user

结果:

# user表示索引名称
# aliases表示索引的别名
# mappings表示索引中的字段
# settings表示索引的创建信息
{
  "user" : {
    "aliases" : { },
    "mappings" : { },
    "settings" : {
      "index" : {
        "creation_date" : "1667629798434",
        "number_of_shards" : "3",
        "number_of_replicas" : "1",
        "uuid" : "VwShKX6-TliIeCIKttsRwA",
        "version" : {
          "created" : "7060299"
        },
        "provided_name" : "user"
      }
    }
  }
}
5.1.3 删除索引
DELETE /user

结果:

{
  "acknowledged" : true
}

5.2 类型操作

5.2.1 创建类型
# /user/_mappings表示设置user索引的具体数据结构,相当于定义类中的属性
# class User{ String name; String sex; int age; Date birthday;}
PUT /user/_mappings
{
  "properties": {
    "name": {
      "type": "text",
      "analyzer": "ik_max_word",
      "index": true,
      "store": false
    },
    "sex": {
      "type": "keyword"
    },
    "age": {
      "type": "integer"
    },
    "birthday": {
      "type": "date",
      "format": "yyyy-MM-dd"
    }
  }
}

结果:

{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "user"
}
5.2.2 查看类型
GET /user

结果:

{
  "user" : {
    "aliases" : { },
    "mappings" : {
      "properties" : {
        "age" : {
          "type" : "integer"
        },
        "birthday" : {
          "type" : "date",
          "format" : "yyyy-MM-dd"
        },
        "name" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        },
        "sex" : {
          "type" : "keyword"
        }
      }
    },
    "settings" : {
      "index" : {
        "creation_date" : "1667631425739",
        "number_of_shards" : "5",
        "number_of_replicas" : "1",
        "uuid" : "hfhUHBKWQZukfeZLlteHKQ",
        "version" : {
          "created" : "7060299"
        },
        "provided_name" : "user"
      }
    }
  }
}

5.3 文档操作

5.3.1 增加文档
POST /user/_doc
{
  "name": "张三",
  "sex": "男",
  "age": 20,
  "birthday": "2000-05-06"
}

结果:

{
  "_index" : "user",
  "_type" : "_doc",
  "_id" : "lfKkRoQB18W7DskkwOaq",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}
5.3.2 查看文档
GET /user/_search
{
  "query": {
    "match_all": {}
  }
}

结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "user",
        "_type" : "_doc",
        "_id" : "lfKkRoQB18W7DskkwOaq",
        "_score" : 1.0,
        "_source" : {
          "name" : "张三",
          "sex" : "男",
          "age" : 20,
          "birthday" : "2000-05-06"
        }
      }
    ]
  }
}
5.3.3 修改文档
# 修改语法: /索引/_doc/数据ID
# 这种修改需要注意,不是只修改一个属性,而是对整个文档进行修改,可以理解为是用一个新的文档去替换原来的文档完成修改。如果新的文档
# 只有1个属性,那么替换后的文档也只有1个属性。
PUT /user/_doc/lfKkRoQB18W7DskkwOaq
{
  "sex": "女"
}

结果:

{
  "_index" : "user",
  "_type" : "_doc",
  "_id" : "lfKkRoQB18W7DskkwOaq",
  "_version" : 3,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 2,
  "_primary_term" : 1
}
5.3.4 删除文档
DELETE /user/_doc/lfKkRoQB18W7DskkwOaq

结果:

{
  "_index" : "user",
  "_type" : "_doc",
  "_id" : "lfKkRoQB18W7DskkwOaq",
  "_version" : 4,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 3,
  "_primary_term" : 1
}

第六节 Java 操作 ES

6.1 创建maven工程

<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.3.2.RELEASE</version>
</parent>
<groupId>com.qf</groupId>
<artifactId>es</artifactId>
<version>1.0</version>

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
	<!--spring-data提供的操作elasticsearch的包-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

6.2 编写实体类

@Data
//Document表示文档,文档的类型就是Article,文档中的字段就是Article中定义的字段
//indexName表示这个文档所在的索引,shards表示分片数量,replicas表示副本的数量
@Document(indexName = "article", shards = 10, replicas = 2)
public class Article {

    //Id表示这个字段就是确定这条数据的
    @Id
    //Field表示字段的定义,name表示在文档中存储的名字,type表示字段类型
    @Field(name = "id", type = FieldType.Long)
    private Long id;

    //Field表示字段的定义,analyzer表示存储时使用的分词器,searchAnalyzer表示查询时使用的分词器
    @Field(name = "title", type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
    private String title;

    //Field表示字段的定义,FieldType.Keyword表示这个字段就是个关键词,不需要再分词,
    //index表示是否建立索引,默认为true,不需要建立索引时需要显示指定
    @Field(name = "content", type = FieldType.Keyword, index = false)
    private String content;


    @Field(name = "author", type = FieldType.Text)
    private String author;

    @Field(name = "author_name", type = FieldType.Text)
    private String authorName;

    //日期格式网址说明
    // https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html
    @Field(name = "publish_date", type = FieldType.Date, format = DateFormat.year_month_day)
    private Date publishDate;

    @Field(name = "category", type=FieldType.Text)
    private String category;

    @Field(name = "publishType", type = FieldType.Text)
    private String publishType; //发布形式:全部可见,部门可见

    @Field(name = "level", type = FieldType.Text)
    private String level; //文章等级: 初级、中级、高级
}

6.3 索引操作

6.3.1 编写测试类
@SpringBootTest
class IndexTest {
    
    @Autowired
    private ElasticsearchRestTemplate restTemplate;
}
6.3.2 创建索引
@Test
public void createIndex(){
   //获取一个索引操作对象,如果索引不存在,则直接创建
    IndexOperations indexOperations = restTemplate.indexOps(Article.class);
    System.out.println(indexOperations);
}
6.3.3 删除索引
@Test
public void deleteIndex(){
   //获取一个索引操作对象
    IndexOperations indexOperations = restTemplate.indexOps(Article.class);
    indexOperations.delete();
}

6.4 文档操作

6.4.1 编写Repository
//ElasticsearchRepository  继承所有父接口的特性
//PagingAndSortingRepository 分页操作
//CrudRepository 基本的CRUD操作
@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {
}
6.4.2 编写测试类
@SpringBootTest
class DocumentTest {

    @Autowired
    private ArticleRepository articleRepository;
}
6.4.3 添加文档
@Test
public void addArticle(){
    Article article = new Article();
    article.setId(1L);
    article.setAuthor("admin");
    article.setContent("这是一篇很有意思的文章");
    article.setCreatedDate(new Date());
    article.setTitle("Java是一门很简单的语言");
    articleRepository.save(article);
}
6.4.4 修改文档
@Test
public void updateArticle(){
    Article article = new Article();
    article.setId(1L);
    article.setAuthor("admin");
    article.setContent("这是一篇很有意思的文章");
    article.setCreatedDate(new Date());
    article.setTitle("Java很牛逼");
    articleRepository.save(article);
}
6.4.5 查询文档
@Test
public void searchArticle(){
    Optional<Article> opt = articleRepository.findById(1L);
    Article article = opt.orElse(null);
    System.out.println(article);
}
6.4.6 删除文档
@Test
public void deleteArticle(){
    articleRepository.deleteById(1L);
}
6.4.7 批量保存
@Test
public void batchAddArticle(){
    List<Article> articles = new ArrayList<>();
    for(int i=0; i<100; i++){
        Article article = new Article();
        article.setId((long) (i+2));
        article.setAuthor("author"+i);
        article.setContent("这是一篇很有意思的文章" + i);
        article.setCreatedDate(new Date());
        article.setTitle("Java很牛逼" + i);
        articles.add(article);
    }
    articleRepository.saveAll(articles);
}
6.4.8 分页查询
@Test
public void pageSearch(){
    //查询构建器,这里用的匹配所有的查询构建器
    QueryBuilder builder = new MatchAllQueryBuilder();
    //分页对象
    Pageable pageable = PageRequest.of(1, 20);
    //分页查询
    Page<Article> articlePage = articleRepository.search(builder, pageable);
    //获取总条数
    long total = articlePage.getTotalElements();
    System.out.println("总条数:" + total);
    //获取查询结果
    List<Article> articleList = articlePage.getContent();
    articleList.forEach(System.out::println);
}
6.4.9 排序查询
@Test
public void sortSearch(){
    Sort.Order dateOrder = Sort.Order.desc("createdDate"); //日期降序排列
    Sort.Order idOrder = Sort.Order.asc("id");//ID升序排列
    Iterable<Article> articles = articleRepository.findAll(Sort.by(dateOrder, idOrder));
    articles.forEach(System.out::println);
}
6.4.10 自定义查询

自定义查询的命名规则:方法名必须是get、find、read、query其中之一开始,后面接字段名以及条件,条件之间的组合使用and或者or, 方法参数必须与使用的字段一一匹配

@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {
    //文章标题模糊查询,并且ID在给定的范围之内
    List<Article> getByTitleLikeAndIdBetween(String title, Long min, Long max);
}

测试:

@Test
public void customerQuery(){
    Calendar c = Calendar.getInstance();
    c.roll(Calendar.DAY_OF_MONTH, -1);
    List<Article> articles = articleRepository.getByTitleLikeAndIdBetween("Java", 20L, 30L);
    articles.forEach(System.out::println);
}

6.5 ES查询操作[重点]

新建一个测试类

@SpringBootTest
class QueryTest {
    
    @Autowired
    private RestHighLevelClient client;
}
6.5.1 term 查询

term的查询是代表完全匹配,这里的完全匹配指的是,查询的内容不会被分词,而是作为一个整体到存储的数据中去匹配,如果数据对应的字段有进行分词,那么只要其中任何一个分词结果与查询内同匹配,那么该数据将在查询结果中展示

查询语法:

# from表示开始的位置,size表示最大查询的条数, query表示查询的条件,term表示这里使用的是精确查找
# 精确查找的条件就是id字段的值为2, term中只能有一个字段作为条件
GET /article/_search
{
  "from": 0,
  "size": 20,
  "query": {
    "term": {
      "id": {
        "value": 2
      }
    }
  }
}

测试:

@Test
public void termQueryTest() throws IOException {
    //查询资源构建器
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.from(0).size(20); //相当于 LIMIT 0, 20
    //term查询 = 精确查询
    builder.query(QueryBuilders.termQuery("id", 2L));
    //创建查询请求
    SearchRequest request = new SearchRequest("article");
    //将查询构建器放入请求中
    request.source(builder);
    //查询并得到结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //从结果中获取查询命中信息
    SearchHits searchHits = response.getHits();
    //获取总命中数
    long totalHits = searchHits.getTotalHits();
    System.out.println("查询命中条数:" + totalHits);
    //获取所有的命中数据
    SearchHit[] hits = searchHits.getHits();//获取每一条被命中的信息
    for(SearchHit hit: hits){
        //将命中的数据转换为一个map
        Map<String, Object> rowData = hit.getSourceAsMap();
        System.out.println(rowData);
    }
    client.close();
}
6.5.2 terms 查询

terms查询与term查询的原理是一样的,只是terms查询针对的是一个字段可能对应多个值的情况,相当于 MySQL 中的条件in

# 查询id在2,3,4,5中的数据
POST /article/_search
{
  "from": 0,
  "size": 20,
  "query": {
    "terms": {
      "id": [2, 3, 4, 5]
    }
  }
}

测试:

@Test
public void termsQueryTest() throws IOException {
    //查询资源构建器
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.from(0).size(20); //相当于 LIMIT 0, 20
    //terms查询 = 相当于MySQL中的IN
    builder.query(QueryBuilders.termsQuery("id", Arrays.asList(2,3,4,5)));
    //创建查询请求
    SearchRequest request = new SearchRequest("article");
    //将查询构建器放入请求中
    request.source(builder);
    //查询并得到结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //从结果中获取查询命中信息
    SearchHits searchHits = response.getHits();
    //获取总命中数
    long totalHits = searchHits.getTotalHits();
    System.out.println("查询命中条数:" + totalHits);
    //获取所有的命中数据
    SearchHit[] hits = searchHits.getHits();//获取每一条被命中的信息
    for(SearchHit hit: hits){
        //将命中的数据转换为一个map
        Map<String, Object> rowData = hit.getSourceAsMap();
        System.out.println(rowData);
    }
    client.close();
}
6.5.3 match查询[重点]

match查询属于高层查询,会根据查询的字段类型不一样,采用不同的查询方式。

  • 查询的是日期或者是数值的话,会将你基于的字符串查询内容转换为日期或者数值对待。
  • 如果查询的内容是一个不能被分词的内容(keyword),match查询不会对你指定的查询关键字进行分词。
  • 如果查询的内容是一个可以被分词的内容(text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。

match查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起

6.5.3.1 查询日期
GET /article/_search
{
  "size": 500, 
  "query": {
    "match": {
      "publish_date": "2022-11-07"
    }
  }
}

测试:

@Test
public void date_matchTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //这里的日期格式自动转换为日期匹配
    builder.query(QueryBuilders.matchQuery("publish_date","2022-11-07"));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.3.2 查询内容分词

如果查询的字段使用了分词,那么查询的内容也将分词。只要数据匹配其中的一个分词内容即可。

GET /article/_search
{
  "size": 500, 
  "query": {
    "match": {
      "title": "中国人民"
    }
  }
}

测试:

@Test
public void analyzer_matchTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //这里的title使用的查询内容是"中国人民",这里会进行分词,分词之后再匹配
    builder.query(QueryBuilders.matchQuery("title","中国人民"));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.3.2 查询内容不分词

如果查询的字段没有使用分词,那么查询的内容就不会分词。

GET /article/_search
{
  "size": 500, 
  "query": {
    "match": {
      "author_name": "张三丰"
    }
  }
}

测试:

@Test
public void none_analyzer_matchTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //这里的author_name使用的查询内容是"张三丰",这里会进行分词,分词之后再匹配
    builder.query(QueryBuilders.matchQuery("author_name","张三丰"));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.3.3 布尔 match 查询

基于一个Field匹配的内容,采用and或者or的方式连接

# 这里的"中华共和"会被分词,分为"中华"和"共和",这两个词用 and 衔接,表示要同时匹配上
GET /article/_search
{
  "size": 500, 
  "query": {
    "match": {
      "title": {
        "query": "中华共和",
        "operator": "and"
      }
    }
  }
}

测试:

@Test
public void bool_analyzer_matchTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //这里的title使用的查询内容是"中华共和",这里会进行分词,分词之后的所有分词结果必须要全部匹配
    builder.query(QueryBuilders.matchQuery("title","中华共和").operator(Operator.AND));
    //默认衔接操作就是OR
    //        builder.query(QueryBuilders.matchQuery("title","中国人民").operator(Operator.OR));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.3.4 multi_match 查询

查询内容可以与多个字段匹配的就需要使用 multi_match查询

GET /article/_search
{
  "size": 500, 
  "query": {
    "multi_match": {
      "query": "中国",
      "fields": ["category", "title"]
    }
  }
}

测试:

@Test
public void multi_matchTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //这里的"中国"既可以与title匹配,也可以与category匹配,任意满足即可
    builder.query(QueryBuilders.multiMatchQuery("中国","title", "category"));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.4 其他查询
6.5.4.1 prefix 查询
GET /article/_search
{
  "size": 500, 
  "query": {
    "prefix": {
      "content": "测试1"
    }
  }
}

测试:

@Test
public void prefixQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //content字段内容以"测试1"开始
    builder.query(QueryBuilders.prefixQuery("content","测试1"));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.4.2 fuzzy查询

在实际的搜索中,我们有时候会打错字,从而导致搜索不到。在 Elastic Search 中,我们可以使用 fuzziness 属性来进行模糊查询,从而达到搜索有错别字的情形。fuzziness 表示编辑距离,编辑距离是对两个字符串差异长度的量化,及一个字符至少需要处理多少次才能变成另一个字符,比如lucenelucece只差了一个字符他们的编辑距离是1。 编辑距离的值可以是0,1,2或者auto

GET /article/_search
{
  "size": 500, 
  "query": {
    "fuzzy": {
      "title": {
        "value": "中华国",
        "fuzziness": 2
      }
    }
  }
}

测试:

@Test
public void fuzzyQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //中华国变化两次可以与title的内容匹配
    builder.query(QueryBuilders.fuzzyQuery("title","中华国").fuzziness(Fuzziness.TWO));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.4.3 wildcard 查询

通配查询,和 MySQL 中的like是一个套路,可以在查询时,在字符串中指定通配符*和占位符?

GET /article/_search
{
  "size": 500, 
  "query": {
    "wildcard": {
      "title": {
        "value": "中华*"
      }
    }
  }
}

测试:

@Test
public void wildcardQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //title的内容需要以中华开始
    builder.query(QueryBuilders.wildcardQuery("title","中华*"));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.4.4 range查询

范围查询,只针对数值类型,对某一个Field进行大于或者小于的范围指定。需要注意的是,ES中的存储比较都是按照字符串顺序来的

GET /article/_search
{
  "size": 500, 
  "query": {
    "range": {
      "id": {
        "gte": 100,
        "lte": 50
      }
    }
  }
}

测试:

@Test
public void rangeQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);
    //id的字符串顺序在100~50
    builder.query(QueryBuilders.rangeQuery("id").gte(100).lte(50));
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.5 复合查询[重点]
6.5.5.1 bool 查询

复合过滤器,将多个查询条件,以一定的逻辑组合在一起。

  • must: 所有的条件,用must组合在一起,表示And的意思
  • must_not:将must_not中的条件,全部都不能匹配,表示Not的意思
  • should:所有的条件,用should组合在一起,表示Or的意思
GET /article/_search
{
  "size": 500, 
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "author_name": {
              "value": "张三"
            }
          }
        },
        {
          "term": {
            "category": {
              "value": "中国文化"
            }
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "category": "黄赌毒"
          }
        }
      ],
      "must": [
        {
          "range": {
            "publish_date": {
              "gte": "2022-11-06",
              "lte": "2022-11-08"
            }
          }
        }
      ]
    }
  }
}

测试

@Test
public void boolQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);

    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

    boolQueryBuilder.should(QueryBuilders.termQuery("author_name", "张三"));
    boolQueryBuilder.should(QueryBuilders.termQuery("category", "中国文化"));

    boolQueryBuilder.mustNot(QueryBuilders.matchQuery("category", "黄赌毒"));

    boolQueryBuilder.must(QueryBuilders.rangeQuery("publish_date").gte("2022-11-06").lte("2022-10-08"));
    builder.query(boolQueryBuilder);
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.5.5.2 boosting 查询

boosting查询可以帮助我们去影响查询后的score。

  • positive:只有匹配上positive的查询的内容,才会被放到返回的结果集中。
  • negative:如果匹配上和positive并且也匹配上了negative,就可以降低这样的文档score。
  • negative_boost:指定系数,必须小于1.0

关于查询时,分数是如何计算的:

  • 搜索的关键字在文档中出现的频次越高,分数就越高
  • 指定的文档内容越短,分数就越高
  • 我们在搜索时,指定的关键字也会被分词,这个被分词的内容,被分词库匹配的个数越多,分数越高
GET /article/_search
{
  "size": 500, 
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "title": "中国共和"
        }
      },
      "negative": {
        "match": {
          "level": "高级"
        }
      },
      "negative_boost": 0.4
    }
  }
}

测试:

@Test
public void boostingQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);

    BoostingQueryBuilder boostingQueryBuilder = QueryBuilders.boostingQuery(
        QueryBuilders.matchQuery("title", "中华共和"),
        QueryBuilders.matchQuery("level", "高级")
    ).negativeBoost(0.4f);

    builder.query(boostingQueryBuilder);
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = resp.getHits();
    //4. 输出结果
    for (SearchHit hit : hits.getHits()) {
        float score = hit.getScore();
        Map<String, Object> map = hit.getSourceAsMap();
        System.out.println(map + " => " + score);
    }
}
6.5.6 filter 查询[重点]

query,根据你的查询条件,去计算文档的匹配度得到一个分数,并且根据分数进行排序,不会做缓存的。

filter,根据你的查询条件去查询文档,不去计算分数,而且filter会对经常被过滤的数据进行缓存。

GET /article/_search
{
  "size": 500, 
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "title": "中国"
          }
        },
        {
          "range": {
            "publish_date": {
              "gte": "2022-11-06",
              "lte": "2022-11-08"
            }
          }
        }
      ]
    }
  }
}

测试:

@Test
public void filterQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);

    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.filter(QueryBuilders.termQuery("title", "中国"));
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("publish_date").gte("2022-11-06").lt("2022-11-08"));

    builder.query(boolQueryBuilder);

    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = resp.getHits();
    //4. 输出结果
    for (SearchHit hit : hits.getHits()) {
        float score = hit.getScore();
        Map<String, Object> map = hit.getSourceAsMap();
        System.out.println(map + " => " + score);
    }
}
6.5.7 高亮查询[重点]

高亮查询就是用户输入的关键字,以一定的特殊样式展示给用户,让用户知道为什么这个结果被检索出来。

高亮展示的数据,本身就是文档中的一个Field,单独将Field以highlight的形式返回给你。

ES提供了一个highlight属性,和query同级别的。

  • fragment_size:指定高亮数据展示多少个字符回来。
  • pre_tags:指定前缀标签,举个例子< font color=“red” >
  • post_tags:指定后缀标签,举个例子< /font >
  • fields:指定哪几个Field以高亮形式返回
GET /article/_search
{
  "size": 500, 
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "title": "中国"
          }
        },
        {
          "range": {
            "publish_date": {
              "gte": "2022-11-06",
              "lte": "2022-11-08"
            }
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    }, 
    "pre_tags": "<font color='red'>",
    "post_tags": "</font>"
  }
}

测试:

@Test
public void highlightQueryTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(500);

    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.filter(QueryBuilders.termQuery("title", "中国"));
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("publish_date").gte("2022-11-06").lt("2022-11-08"));

    builder.query(boolQueryBuilder);

    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("title").preTags("<font color='red'>").postTags("</font>");
    builder.highlighter(highlightBuilder);

    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = resp.getHits();
    //4. 输出结果
    for (SearchHit hit : hits.getHits()) {
        HighlightField highlightField = hit.getHighlightFields().get("title");
        String fieldName = highlightField.getName();
        Text[] fragments = highlightField.getFragments();
        System.out.println(fieldName + " => "+ Arrays.stream(fragments).map(Text::string).collect(Collectors.joining(",")));
    }
}
6.5.8 聚合查询[重点]

ES的聚合查询和 MySQL 的聚合查询类似,ES的聚合查询相比 MySQL 要强大的多,ES提供的统计数据的方式多种多样。

# ES聚合查询的RESTful语法
POST /index/type/_search
{
    "aggs": {
        "名字(agg)": {
            "agg_type": {
                "属性": "值"
            }
        }
    }
}
6.5.8.1 去重计数查询
GET /article/_search
{
  "aggs": {
    "authorNameAgg": {
      "cardinality": {
        "field": "author_name"
      }
    }
  }
}

测试:

@Test
public void cardinalityTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();

    builder.aggregation(AggregationBuilders.cardinality("authorNameAgg").field("author_name"));

    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    Aggregations aggregations = resp.getAggregations();
    Cardinality cardinality = aggregations.get("authorNameAgg");
    String name = cardinality.getName();
    long value = cardinality.getValue();
    System.out.println(name + "=" + value);
}
6.5.8.2 范围统计

统计一定范围内出现的文档个数,比如,针对某一个Field的值在 0100,100200,200~300之间文档出现的个数分别是多少。

范围统计可以针对普通的数值,针对时间类型,针对ip类型都可以做相应的统计。

range,date_range,ip_range

GET /article/_search
{
  "aggs": {
    "publishDateAgg": {
      "range": {
        "field": "publish_date",
        "ranges": [
          {
            "from": "2022-11-01", 
            "to": "2022-11-10"
          },
           {
            "from": "2022-11-11", 
            "to": "2022-11-20"
          },
           {
            "from": "2022-11-21", 
            "to": "2022-11-30"
          }
        ]
      }
    }
  }
}

测试:

@Test
public void rangeAggregationTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();

    builder.aggregation(AggregationBuilders.dateRange("publishDateAgg")
                        .field("publish_date")
                        .addRange("2022-11-01", "2022-11-10")
                        .addRange("2022-11-11", "2022-11-20")
                        .addRange("2022-11-21", "2022-11-30"));

    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    Aggregations aggregations = resp.getAggregations();
    Range range = aggregations.get("publishDateAgg");
    List<? extends Range.Bucket> buckets = range.getBuckets();
    buckets.forEach(bucket -> {
        String key = bucket.getKeyAsString();
        long docCount = bucket.getDocCount();
        System.out.println(key + " => " + docCount);
    });
}
6.5.8.2 统计聚合查询

可以查询指定Field的最大值,最小值,平均值,平方和等

GET /article/_search
{
  "aggs": {
    "agg": {
      "extended_stats": {
        "field": "publish_date"
      }
    }
  }
}

测试:

@Test
public void extended_statsAggregationTest() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest("article");
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();

    builder.aggregation(AggregationBuilders.extendedStats("agg").field("publish_date"));

    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    Aggregations aggregations = resp.getAggregations();
    ExtendedStats extendedStats = aggregations.get("agg");
    String minAsString = extendedStats.getMinAsString();
    String maxAsString = extendedStats.getMaxAsString();
    String avgAsString = extendedStats.getAvgAsString();
    System.out.println(minAsString);
    System.out.println(maxAsString);
    System.out.println(avgAsString);
}

第七节 倒排索引

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将存放的数据,以一定的方式进行分词,并且将分词的内容存放到一个单独的分词库中。

当用户去查询数据时,会将用户的查询关键字进行分词。

然后去分词库中匹配内容,最终得到数据的id标识。

根据id标识去存放数据的位置拉取到指定的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2073319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

后端Web之登录校验(下篇)

目录 1.概述 ​2.过滤器Fliter 3.拦截器Interceptor 1.概述 Filter过滤器&#xff1a;在Web开发中&#xff0c;过滤器&#xff08;Filter&#xff09;是一种非常重要的组件&#xff0c;用于在请求到达目标资源&#xff08;如Servlet或静态资源&#xff09;之前或之后&#…

10、Redis高级:多级缓存、JVM进程缓存、OpenResty本地缓存、缓存同步Canal

多级缓存 0.学习目标 1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;如图&#xff1a; 存在下面的问题&#xff1a; •请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈 …

Earth‘s Future | 西南大学时伟宇团队揭示长江上游径流变化对气候变化与人类活动响应的驱动机制不同

本文首发于“生态学者”微信公众号&#xff01; 径流是全球水循环的重要组成部分&#xff0c;对社会经济发展、维持农业生产和维护生态安全具有重要意义。自20世纪末&#xff0c;气候变化与人类活动双重加剧&#xff0c;长江上游径流变化对长江上游乃至长江流域具有重要影响。因…

SSRF以及CSRF

ssrf 服务端请求伪造&#xff1a;由于服务端提供了从其他服务器应用获取数据的功能&#xff0c;但又没有对目标地址做严格过滤与限制&#xff0c;导致攻击者可以传入任意的地址来让后端服务器对其发起请求&#xff0c;并返回对该目标地址请求的数据 数据流&#xff1a;攻击者…

AI大模型日报#0823:GPT-4无师自通预测蛋白质结构登Nature子刊、豆包版《Her》升级上新

导读&#xff1a;AI大模型日报&#xff0c;爬虫LLM自动生成&#xff0c;一文览尽每日AI大模型要点资讯&#xff01;目前采用“文心一言”&#xff08;ERNIE-4.0-8K-latest&#xff09;、“智谱AI”&#xff08;glm-4-0520&#xff09;生成了今日要点以及每条资讯的摘要。欢迎阅…

第一次运行Neo4J

在浏览器中输入127.0.0.1:7474&#xff08;如Neo4J装在其它机器上输入相应的IP地址即可&#xff09; 1、创建简单节点 这里我创建一个简单的“Employee”节点&#xff0c;在数据浏览器中的命令框&#xff08;美元提示符下&#xff09;键入以下命令 CREATE (emp:Employee) 执行…

leetcode139. 单词拆分,动态规划

leetcode139. 单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&#xff1a; 输入: s…

JDK、JRE、JVM关系

JDK:Java Development Kit&#xff0c;是java开发工具包 ,开发java必备工具&#xff0c;JDKJRE开发工具集&#xff08;javac等&#xff09; JRE:Java Runtime Environment&#xff0c;是java运行时的环境&#xff0c;包含了java虚拟机jvm java基础类库&#xff0c;是使用java…

【GD32】FreeRTOS实时操作系统移植(GD32F470ZGT6)

1. 简介 在日常的应用开发项目中&#xff0c;常常需要单片机具有处理多种任务的需求&#xff0c;如果使用裸机开发那么肯定是不现实的&#xff0c;因为受限于IO与处理器的巨大速度差异&#xff0c;在裸机下处理器常常要等待当前IO操作完成才能进行下一个任务&#xff0c;效率大…

亦菲喊你来学机器学习(11) --回归树算法

文章目录 回归树回归树结构回归树的工作原理优点与缺点构建回归树模型回归树模型参数介绍训练模型测试模型 总结 回归树 决策树是一种常用的机器学习算法&#xff0c;广泛应用于分类和回归任务中。当决策树用于回归任务时&#xff0c;我们称之为回归树&#xff08;Regression …

零基础5分钟上手亚马逊云科技 - 网络安全分析最佳实践

简介&#xff1a; 欢迎来到小李哥全新亚马逊云科技AWS云计算知识学习系列&#xff0c;适用于任何无云计算或者亚马逊云科技技术背景的开发者&#xff0c;通过这篇文章大家零基础5分钟就能完全学会亚马逊云科技一个经典的服务开发架构方案。 我会每天介绍一个基于亚马逊云科技…

【逐行注释】基于CV/CT模型的IMM|MATLAB程序|源代码复制后即可运行,无需下载

订阅专栏后可以直接查看完整的源代码(和注释),无需付费下载或其他的操作。代码复制到MATLAB上面可以得到和我一样的运行结果。 文章目录 程序概述完整代码与逐行注释运行结果解释按模块分析代码程序概述 基于EKF的多模型交互。以CV和CT两个模型进行交互,这里对代码进行逐…

Django后台管理Xadmin使用DjangoUeditor富文本编辑器

Django后台管理Xadmin使用DjangoUeditor富文本编辑器 一、下载 点击github下载 https://github.com/twz915/DjangoUeditor3 1、下载完后解压到跟xadmin同一层级目录: 2、解压后名称可能为DjangoUeditor3-master,需要改为DjangoUeditor 3、进入DjangoUeditor目录,把Djan…

Visiual Studio如何添加C语言的依赖和一些快捷键

Debug 和 Release 项目输出设置 Debug: 调试版本&#xff0c;包含调试信息&#xff0c;并且把进行任何优化&#xff0c;便于程序员调试。Debug模式下生成两个文件&#xff0c;除了 .exe 或者 .dll文件外&#xff0c;还有一个 .pdb 文件&#xff0c;这个文件记录了代码中断点等…

查找数学类文献的专业数据库有哪些 如何获取这些数据库资源

一、MathSciNet&#xff08;美国数学会《数学评论》&#xff09; MathSciNet数据库是美国数学学会出版的《数学评论》Mathematical Reviews和Current Mathematical Publications的网络版&#xff0c;包含《数学评论》自1940年出版以来的所有评论文章&#xff0c;包括期刊、图书…

【Node】【4】事件循环和EventEmitter类

事件循环 事件驱动&#xff1a;node中程序的执行是由事件的发生和相应的事件处理器&#xff08;eventHandler&#xff09;来驱动的编程范式。 程序监听并响应发生的事件。每个异步事件都生成一个事件观察者&#xff0c;在执行某个事件&#xff08;主题&#xff09;结束的时候…

SVN项目的文件泄露分析和漏洞修复

说明:本文仅是用于学习分析自己搭建的SVN漏洞内容和原理,请勿用在非法途径上,违者后果自负,与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其相关法规内容【学法时习之丨网络安全在身边一图了解网络安全法_中央网络安全和信息化委员会办公室】 …

信息学奥赛初赛天天练-75-NOIP2016普及组-完善程序-二分答案、二分查找、贪心算法、贪心策略

文章PDF链接: https://pan.baidu.com/s/1SVcGU_rApvoUWrUoviPCiA?pwdht2j 提取码: ht2j 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 1 完善程序 (单选题 &#xff0c;每小题3分&#xff0c;共30分) 郊游活动 有 n名同学参加学校组织的郊游活动&#xff0c…

gateway的学习

1.网关的作用 1.负载均衡 2.过滤器的使用 1.通过配置文件实现的过滤器 2.代码逻辑层面实现全局过滤器 //全局过滤器代码逻辑实现 Component //Order(1):注解配置过滤器的执行顺序 public class GlobalFilter implements GatewayFilter, Ordered {/*** 处理当前请求&#xff0c;…

RocketMQ~高性能设计与实现(零拷贝技术)、多种集群模式

与Kafka类似&#xff0c;RocketMQ也使用了零拷贝技术、对于分区&#xff0c;其也有分队列的思维在。 零拷贝技术 传统的IO读写其实就是readwrite的操作&#xff0c;整个过程会分为如下几步 用户调用read()方法&#xff0c;开始读取数据&#xff0c;此时发生一次上下文从用户…