ElasticSearch

ElasticSearch_第1张图片

文章目录

      • 一、引言
        • 1.1 海量数据
        • 1.2 全文检索
      • 二、ES概述
        • 2.1 ES的介绍
        • 2.2 ES的由来
      • 三、 ElasticSearch安装
        • 3.1 安装ES&Kibana
        • 3.2 安装IK分词器
      • 四、 ElasticSearch基本操作
        • 4.1 ES的结构
          • 4.1.1 索引Index,分片和备份
          • 4.1.2 类型 Type
          • 4.1.3 文档 Doc
          • 4.1.4 属性 Field
        • 4.2 操作ES的RESTful语法
        • 4.3 索引的操作
          • 4.3.1 创建一个索引
          • 4.3.2 查看索引信息
          • 4.3.3 删除索引
        • 4.4 ES中Field可以指定的类型
        • 4.5 创建索引并指定数据结构
        • 4.6 文档的操作
          • 4.6.1 新建文档
          • 4.6.2 修改文档
          • 4.6.3 删除文档
      • 五、Springboot操作ElasticSearch【`重点`】
        • 5.1 版本选择
        • 5.2 操作索引
          • 5.2.1 创建索引
          • 5.2.2 检查索引是否存在
          • 5.2.3 删除索引
        • 5.3 Java操作文档
          • 5.3.1 添加文档操作
          • 5.3.2 修改文档
          • 5.3.3 删除文档
        • 5.4 Java批量操作文档
          • 5.4.1 批量添加
          • 5.4.2 批量删除
        • 5.5 ElasticSearch 查询数据导入
      • 六、 ElasticSearch的各种查询
        • 6.1 term&terms查询【`重点`】
          • 6.1.1 term查询
          • 6.1.2 terms查询
        • 6.2 match查询【`重点`】
          • 6.2.1 match_all查询
          • 6.2.2 match查询
          • 6.2.3 布尔match查询
          • 6.2.4 multi_match查询
        • 6.3 其他查询
          • 6.3.1 id查询
          • 6.3.2 ids查询
          • 6.3.3 prefix查询
          • 6.3.4 fuzzy查询
          • 6.3.5 wildcard查询
          • 6.3.6 range查询
          • 6.3.7 regexp查询
        • 6.4 复合查询
          • 6.4.1 bool查询
          • 6.4.2 boosting查询
      • 七、面试题
        • 什么是倒排索引(实现全文检索功能的底层数据结构)

一、引言


京东搜索:手机11苹果

1.1 海量数据

mysql 单表的数据量达到500w条以后,检索效率就大幅度的下降了。

ES本身就支持海量数据的检索

1.2 全文检索

mysql的模糊查询,只能进行关键字上的“%xxx%”这种模糊查询,但是不能进行分词检索

ES:本身就支持按照用户输入的关键词进行拆分的查询,并且可以容错(输入错别字的时候,依然可以查询。)

二、ES概述


2.1 ES的介绍

  • ES是一个使用Java语言并且基于Lucene编写的搜索引擎框架,他提供了分布式的全文搜索功能,提供了一个统一的基于RESTful风格的WEB接口,官方客户端也对多种语言都提供了相应的API。

  • Lucene:https://lucene.apache.org/ 是apche的顶级项目,提供搜索的基础类库。

  • 全文检索:区别与关系数据库里的模糊查询,可以把用户输入的内容切词后,去分词库比对,然后把匹配的结果进行返回。

  • RESTful风格的WEB接口:GET,POST,PUT,DELETE

  • 应用广泛:github, wiki , jingdong

2.2 ES的由来

ES回忆时光
ElasticSearch_第2张图片

三、 ElasticSearch安装


3.1 安装ES&Kibana

yml文件

# 注意:先执行如下修改,在运行 docker-compose up -d 

Docker安装 elasticsearch 报错max virtual memory areas vm.max_map_count [65530] is too low
使用下面命令解决:
cat /etc/sysctl.conf
vi /etc/sysctl.conf
添加 一行 
vm.max_map_count=655360

加载参数
sysctl -p
version: "3.1"
services:
  elasticsearch:
    image: daocloud.io/library/elasticsearch:6.8.12
    restart: always
    container_name: elasticsearch
    environment: 
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m" #设置使用jvm内存大小
    ports:
      - 9200:9200
  kibana:
    image: daocloud.io/library/kibana:6.8.12
    restart: always
    container_name: kibana
    ports:
      - 5601:5601
    environment:
      - elasticsearch_url=http://elasticsearch:9200
    depends_on:
      - elasticsearch

3.2 安装IK分词器

下载离线安装包

  • 下载IK分词器的地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.8.12/elasticsearch-analysis-ik-6.8.12.zip
  • 上传文件到虚拟机
  • 解压文件
  • 在es容器内创建目录:/usr/share/elasticsearch/plugins/ik
  • 将解压好的所有文件放入es容器内的目录:/usr/share/elasticsearch/plugins/ik
  • 重启ES容器

直接在线安装(github网络限制,可能连接失败)

  • 进去到ES容器内部,跳转到bin目录下,执行bin目录下的脚本文件:
  • ./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.8.12/elasticsearch-analysis-ik-6.8.12.zip
  • 或者使用如下地址:
  • ./elasticsearch-plugin install http://39.98.95.55/down/elasticsearch-analysis-ik-6.8.12.zip
  • 重启ES的容器,让IK分词器生效。
  • docker-compose restart
校验IK分词器
ElasticSearch_第3张图片

四、 ElasticSearch基本操作


ElasticSearch_第4张图片

4.1 ES的结构

4.1.1 索引Index,分片和备份
  • ES的服务中,可以创建多个索引。

  • 每一个索引默认被分成5片存储。

  • 每一个分片都会存在至少一个备份分片。

  • 备份的分片应该放在不同的服务器中。

ElasticSearch_第5张图片

4.1.2 类型 Type

ES5 : 一个索引下,可以创建多个类型。

ES6: 一个索引下,只能创建一个类型(type)。

ES7:取消了type的概念

Ps:根据版本不同,类型的创建也不同。

ElasticSearch_第6张图片

4.1.3 文档 Doc

ES里的文档(Doc),本质上就是一条json字符传数据。

一个类型下,可以有多个文档。这个文档就类似于MySQL表中的行数据。

ElasticSearch_第7张图片

4.1.4 属性 Field

一个文档中,可以包含多个属性。类似于MySQL表中的一行数据存在多个列。

ElasticSearch_第8张图片

4.2 操作ES的RESTful语法

  • GET请求:
    • http://ip:port/index:查询索引信息
    • http://ip:port/index/type/doc_id:查询指定的文档信息
  • POST请求:
    • http://ip:port/index/type/_search:查询文档,可以在请求体中添加json字符串来代表查询条件
    • http://ip:port/index/type/doc_id/_update:修改文档,在请求体中指定json字符串代表修改的具体信息
  • PUT请求:
    • http://ip:port/index:创建一个索引,需要在请求体中指定索引的信息,类型,结构
    • http://ip:port/index/type/_mappings:代表创建索引时,指定索引文档存储的属性的信息
  • DELETE请求:
    • http://ip:port/index:删除索引
    • http://ip:port/index/type/doc_id:删除指定的文档

4.3 索引的操作

https://www.elastic.co/guide/en/elasticsearch/reference/6.8/index.html

4.3.1 创建一个索引

语法如下

# 创建一个索引
PUT /person
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  }
}
4.3.2 查看索引信息

语法如下

# 查看索引信息
GET /person
查看信息
ElasticSearch_第9张图片
4.3.3 删除索引

语法如下

# 删除索引
DELETE /person

4.4 ES中Field可以指定的类型

  • 字符串类型:

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

    • long:取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节
    • integer:取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节
    • short:取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节
    • byte:取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节
    • double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节
    • float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节
  • 时间类型:

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

  • 布尔类型:

    • boolean类型,表达true和false

其他的数据类型参考官网:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/mapping-types.html

4.5 创建索引并指定数据结构

语法如下

https://www.elastic.co/guide/en/elasticsearch/reference/6.5/index.html

# 创建索引,指定数据结构
PUT /book
{
  "settings": {
    # 分片数  
    "number_of_shards": 5,
    # 备份数  
    "number_of_replicas": 1
  },
  "mappings": {
    # type 的名称  
    "novel": {
      # 从这里开始指定 field
      "properties": {
        "name": {
          # 字段的类型 ,text ,可以全文检索
          "type": "text",
          # text类型使用的分词器 
          "analyzer": "ik_max_word"
        },
        "author": {
          "type": "keyword"
        },
        "count": {
          "type": "long"
        },
        "on-sale": {
          "type": "date",
          "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        },
        "descr": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
      }
    }
  }
}

4.6 文档的操作

文档在ES服务中的唯一标识,_index_type_id三个内容为组合,锁定一个文档,操作是添加还是修改。

4.6.1 新建文档

自动生成_id

# 添加文档,自动生成id
POST /book/novel
{
  "name": "盘龙",
  "author": "我吃西红柿",
  "count": 100000,
  "on-sale": "2000-01-01",
  "descr": "山重水复疑无路,柳暗花明又一村"
}

手动指定_id

# 添加文档,手动指定id
PUT /book/novel/1
{
  "name": "红楼梦",
  "author": "曹雪芹",
  "count": 10000000,
  "on-sale": "1985-01-01",
  "descr": "一个是阆苑仙葩,一个是美玉无瑕"
}

查询当前索引下的全部文档数据

POST /book/novel/_search

# 根据id查询文档信息 
GET /book/novel/4AZze4IBTWJVgBPuWRch
4.6.2 修改文档

覆盖式修改

# 添加文档,手动指定id
PUT /book/novel/1
{
  "name": "红楼梦",
  "author": "曹雪芹",
  "count": 4353453,
  "on-sale": "1985-01-01",
  "descr": "一个是阆苑仙葩,一个是美玉无瑕"
}

doc修改方式

# 修改文档,基于doc方式
POST /book/novel/1/_update
{
  "doc": {
     # 指定上需要修改的field和对应的值
    "count": "1234565"
  }
}
4.6.3 删除文档

根据id删除

# 根据id删除文档
DELETE /book/novel/_id

五、Springboot操作ElasticSearch【重点


5.1 版本选择

https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#repositories

ElasticSearch_第10张图片

导入依赖

      <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-elasticsearchartifactId>
      dependency>
 

yml配置

spring:
  elasticsearch:
    rest:
      uris: http://192.168.57.190:9200

5.2 操作索引

5.2.1 创建索引

代码如下

@Data
@AllArgsConstructor
@NoArgsConstructor
// createIndex 是否随IOC容器启动自动创建索引
@Document(indexName = "user_info",shards = 1,replicas = 1,createIndex = false)
public class UserInfo {
    @Id
    @Field(type = FieldType.Long)
    private Long id;

    @Field(type = FieldType.Keyword)
    private String name;

    @Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private String remark;

    @Field(type = FieldType.Date)
    private Date jobday;
}

@SpringBootTest
public class AppTest {

    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Test
    public void createIndex() {
        elasticsearchRestTemplate.createIndex(UserInfo.class);
        elasticsearchRestTemplate.putMapping(UserInfo.class);
    }
}

5.2.2 检查索引是否存在

代码如下

    @Test
    void queryIndex() {
        final boolean b = elasticsearchRestTemplate.indexExists(UserInfo.class);
        System.out.println(b);
    }
5.2.3 删除索引

代码如下

    @Test
    public void deleteIndex() {
        elasticsearchRestTemplate.deleteIndex("user_info");
    }

5.3 Java操作文档

5.3.1 添加文档操作

代码如下

    @Test
    void save() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1l);
        userInfo.setName("张三");
        userInfo.setRemark("这是一个测试用户,用的手机是小米手机,没用苹果手机");
        userInfo.setJobday(new Date());

        IndexQuery indexQuery = new IndexQueryBuilder()
                .withId(userInfo.getId().toString())
                .withObject(userInfo)
                .build();

        //返回值是索引 
        String index = elasticsearchRestTemplate.index(indexQuery);
        System.out.println(index);
    }

简化方式

public interface UserRepository extends ElasticsearchRepository<UserInfo, Long> {

}
 @Autowired
    UserRepository userRepository;

    @Test
    void indexDoc() {

        final UserInfo userInfo = new UserInfo();
        userInfo.setId(100l);
        userInfo.setName("张三");
        userInfo.setJobday(new Date());
        userInfo.setRemark("张三是法外狂徒");

        userRepository.save(userInfo);
    }
5.3.2 修改文档

代码如下

    @Test
    void updateDoc() {
        final UserInfo userInfo = new UserInfo();
        userInfo.setId(100l);
        userInfo.setName("张三1");
        userInfo.setJobday(new Date());
        userInfo.setRemark("张三是法外狂徒111");

        userRepository.index(userInfo);
    }
5.3.3 删除文档

代码如下

    @Test
    void deleteDoc() {
        userRepository.deleteById(100l);
    }

5.4 Java批量操作文档

5.4.1 批量添加

代码如下

@Test
    void bulkIndex() {

        List<IndexQuery> list = new ArrayList<>();

        UserInfo userInfo1 = new UserInfo();
        userInfo1.setId(2l);
        userInfo1.setName("李四");
        userInfo1.setRemark("这是一个用户,用的手机是苹果手机");
        userInfo1.setJobday(new Date());
        IndexQuery indexQuery1 = new IndexQueryBuilder()
                .withId(userInfo1.getId().toString())
                .withObject(userInfo1)
                .build();
        list.add(indexQuery1);

        UserInfo userInfo2 = new UserInfo();
        userInfo2.setId(3l);
        userInfo2.setName("王五");
        userInfo2.setRemark("这是一个用户wangwu,用的手机是小米手机");
        userInfo2.setJobday(new Date());
        IndexQuery indexQuery2 = new IndexQueryBuilder()
                .withId(userInfo2.getId().toString())
                .withObject(userInfo2)
                .build();
        list.add(indexQuery2);
        list.add(indexQuery2);

        elasticsearchRestTemplate.bulkIndex(list);

    }
 @Test
    void testSaveAll() {
        List<UserInfo> list = new ArrayList<>();

        UserInfo userInfo1 = new UserInfo();
        userInfo1.setId(5l);
        userInfo1.setName("李四");
        userInfo1.setRemark("这是一个用户,用的手机是苹果手机");
        userInfo1.setJobday(new Date());
        list.add(userInfo1);

        UserInfo userInfo2 = new UserInfo();
        userInfo2.setId(6l);
        userInfo2.setName("王五");
        userInfo2.setRemark("这是一个用户wangwu,用的手机是小米手机");
        userInfo2.setJobday(new Date());
        list.add(userInfo2);

        userRepository.saveAll(list);
    }
5.4.2 批量删除

代码如下

@Test
    void testDeleteBatch() {
        List<UserInfo> list = new ArrayList<>();

        UserInfo userInfo1 = new UserInfo();
        userInfo1.setId(5l);

        list.add(userInfo1);

        UserInfo userInfo2 = new UserInfo();
        userInfo2.setId(6l);

        list.add(userInfo2);
        
        userRepository.deleteAll(list);
    }

5.5 ElasticSearch 查询数据导入

创建索引,指定数据结构

索引名:sms-logs-index

类型名:sms-logs-type

结构如下:

索引结构图
ElasticSearch_第11张图片
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "sms-logs-index",type = "sms-logs-type",shards = 1,replicas = 1,createIndex = false)
public class SmsLogs {

    @Id
    @Field(type = FieldType.Long)
    private Long id;// 唯一ID 1
    @Field(type = FieldType.Date)
    private Date createDate;// 创建时间
    @Field(type = FieldType.Date)
    private Date sendDate; // 发送时间
    @Field(type = FieldType.Keyword)
    private String longCode;// 发送的长号码
    @Field(type = FieldType.Keyword)
    private String mobile;// 下发手机号
    @Field(type = FieldType.Keyword)
    private String corpName;// 发送公司名称
    @Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private String smsContent; // 下发短信内容
    @Field(type = FieldType.Integer)
    private Integer state; // 短信下发状态 0 成功 1 失败
    @Field(type = FieldType.Integer)
    private Integer operatorId; // '运营商编号 1 移动 2 联通 3 电信
    @Field(type = FieldType.Keyword)
    private String province;// 省份
    @Field(type = FieldType.Ip)
    private String ipAddr; //下发服务器IP地址
    @Field(type = FieldType.Integer)
    private Integer replyTotal; //短信状态报告返回时长(秒)
    @Field(type = FieldType.Long)
    private Integer fee;  // 费用

}
public interface SmsLogsRepository extends ElasticsearchRepository<SmsLogs, Long> {

}
@SpringBootTest
public class SmsLogsRepositoryTest {

    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    SmsLogsRepository smsLogsRepository;

    @Test
    void createIndex() {
        elasticsearchRestTemplate.createIndex(SmsLogs.class);
        elasticsearchRestTemplate.putMapping(SmsLogs.class);
    }


    @Test
    void deleteIndxe() {
        elasticsearchRestTemplate.deleteIndex(SmsLogs.class);
    }

    @Test
    void genData() {

        final ArrayList<SmsLogs> list = new ArrayList<>();

        SmsLogs smsLogs1 = new SmsLogs();
        smsLogs1.setMobile("13800000000");
        smsLogs1.setCorpName("途虎养车");
        smsLogs1.setCreateDate(new Date());
        smsLogs1.setSendDate(new Date());
        smsLogs1.setIpAddr("10.126.2.9");
        smsLogs1.setLongCode("10690000988");
        smsLogs1.setReplyTotal(10);
        smsLogs1.setState(0);
        smsLogs1.setSmsContent("【途虎养车】亲爱的张三先生/女士,您在途虎购买的货品(单号TH123456)已 到指定安装店多日," + "现需与您确认订单的安装情况,请点击链接按实际情况选择(此链接有效期为72H)。您也可以登录途 虎APP进入" + "“我的-待安装订单”进行预约安装。若您在服务过程中有任何疑问,请致电400-111-8868向途虎咨 询。");
        smsLogs1.setProvince("北京");
        smsLogs1.setOperatorId(1);
        smsLogs1.setFee(3);
        smsLogs1.setId(1l);

        list.add(smsLogs1);

        SmsLogs smsLogs2 = new SmsLogs();
        smsLogs2.setMobile("13100000000");
        smsLogs2.setCorpName("盒马鲜生");
        smsLogs2.setCreateDate(new Date());
        smsLogs2.setSendDate(new Date());
        smsLogs2.setIpAddr("10.126.2.9");
        smsLogs2.setLongCode("10660000988");
        smsLogs2.setReplyTotal(15);
        smsLogs2.setState(0);
        smsLogs2.setSmsContent("【盒马】您尾号12345678的订单已开始配送,请在您指定的时间收货不要走开 哦~配送员:" + "刘三,电话:13800000000");
        smsLogs2.setProvince("北京");
        smsLogs2.setOperatorId(2);
        smsLogs2.setFee(5);
        smsLogs2.setId(2l);
        list.add(smsLogs2);

        SmsLogs smsLogs3 = new SmsLogs();
        smsLogs3.setMobile("15300000000");
        smsLogs3.setCorpName("滴滴打车");
        smsLogs3.setCreateDate(new Date());
        smsLogs3.setSendDate(new Date());
        smsLogs3.setIpAddr("10.126.2.8");
        smsLogs3.setLongCode("10660000988");
        smsLogs3.setReplyTotal(50);
        smsLogs3.setState(1);
        smsLogs3.setSmsContent("【滴滴单车平台】专属限时福利!青桔/小蓝月卡立享5折,特惠畅骑30天。" + "戳 https://xxxxxx退订TD");
        smsLogs3.setProvince("上海");
        smsLogs3.setOperatorId(3);
        smsLogs3.setFee(7);
        smsLogs3.setId(3l);
        list.add(smsLogs3);

        SmsLogs smsLogs4 = new SmsLogs();
        smsLogs4.setMobile("13900000000");
        smsLogs4.setCorpName("招商银行");
        smsLogs4.setCreateDate(new Date());
        smsLogs4.setSendDate(new Date());
        smsLogs4.setIpAddr("10.126.2.8");
        smsLogs4.setLongCode("10690000988");
        smsLogs4.setReplyTotal(50);
        smsLogs4.setState(0);
        smsLogs4.setSmsContent("【招商银行】尊贵的李四先生,恭喜您获得华为P30 Pro抽奖资格,还可领100 元打" + "车红包,仅限1天");
        smsLogs4.setProvince("上海");
        smsLogs4.setOperatorId(1);
        smsLogs4.setFee(8);
        smsLogs4.setId(4l);
        list.add(smsLogs4);

        SmsLogs smsLogs5 = new SmsLogs();
        smsLogs5.setMobile("13700000000");
        smsLogs5.setCorpName("中国平安保险有限公司");
        smsLogs5.setCreateDate(new Date());
        smsLogs5.setSendDate(new Date());
        smsLogs5.setIpAddr("10.126.2.8");
        smsLogs5.setLongCode("10690000998");
        smsLogs5.setReplyTotal(18);
        smsLogs5.setState(0);
        smsLogs5.setSmsContent("【中国平安】奋斗的时代,更需要健康的身体。中国平安为您提供多重健康保 障,在奋斗之路上为您保驾护航。退订请回复TD");
        smsLogs5.setProvince("武汉");
        smsLogs5.setOperatorId(1);
        smsLogs5.setFee(5);
        smsLogs5.setId(5l);
        list.add(smsLogs5);

        SmsLogs smsLogs6 = new SmsLogs();
        smsLogs6.setMobile("13600000000");
        smsLogs6.setCorpName("中国移动");
        smsLogs6.setCreateDate(new Date());
        smsLogs6.setSendDate(new Date());
        smsLogs6.setIpAddr("10.126.2.8");
        smsLogs6.setLongCode("10650000998");
        smsLogs6.setReplyTotal(60);
        smsLogs6.setState(0);
        smsLogs6.setSmsContent("【北京移动】尊敬的客户137****0000,5月话费账单已送达您的139邮箱," + "点击查看账单详情 http://y.10086.cn/; " + " 回Q关闭通知,关注“中国移动139邮箱”微信随时查账单【中国移动 139邮箱】");
        smsLogs6.setProvince("武汉");
        smsLogs6.setOperatorId(1);
        smsLogs6.setFee(4);
        smsLogs6.setId(6l);
        list.add(smsLogs6);

        smsLogsRepository.saveAll(list);

    }
}

六、 ElasticSearch的各种查询


6.1 term&terms查询【重点

6.1.1 term查询

term的查询是代表完全匹配,搜索之前不会对你搜索的关键字进行分词,对你的关键字去文档分词库中去匹配内容。

必须是等于,是与分词库比对,不是与值。,若是没有分词库的,就比较值

select * from sms_logs where provinc = ‘北京’ limit 0,2

# term查询
POST /sms-logs-index/sms-logs-type/_search
{
  "from": 0,     # limit ?
  "size": 5,	  # limit x,?
  "query": {
    "term": {
      "province": {
        "value": "北京"
      }
    }
  }
}

代码实现方式

@SpringBootTest
public class SmsLogsQueryTest {

    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    SmsLogsRepository smsLogsRepository;

    @Test
    void term() {

        final TermQueryBuilder termQueryBuilder = new TermQueryBuilder("province","北京");

        final SearchQuery searchQuery = new NativeSearchQuery(termQueryBuilder);
        // index and type
        searchQuery.addIndices("sms-logs-index");
        searchQuery.addTypes("sms-logs-type");

        // 分页参数
        PageRequest pageRequest = PageRequest.of(0,1);
        searchQuery.setPageable(pageRequest);

        // 通用查询方法
        elasticsearchRestTemplate.query(searchQuery, new ResultsExtractor<Void>() {
            @Override
            public Void extract(SearchResponse response) {
                final SearchHits searchHits = response.getHits();
                final SearchHit[] hits = searchHits.getHits();
                for (SearchHit hit : hits) {
                    final Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                    System.out.println(sourceAsMap);
                }
                return null;
            }
        });

    }

    @Test
    void term1() {
        final TermQueryBuilder termQueryBuilder = new TermQueryBuilder("province","北京");

        final SearchQuery searchQuery = new NativeSearchQuery(termQueryBuilder);
        searchQuery.addIndices("sms-logs-index");
        searchQuery.addTypes("sms-logs-type");

        // 分页参数
        PageRequest pageRequest = PageRequest.of(0,1);
        searchQuery.setPageable(pageRequest);

        final List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(searchQuery, SmsLogs.class);
        System.out.println(smsLogs.size());
        System.out.println(smsLogs);
    }

    @Test
    void term2() {
        // 分页参数
        PageRequest pageRequest = PageRequest.of(0,1);
        final List<SmsLogs> data = smsLogsRepository.findByProvince("北京",pageRequest);
        System.out.println(data.size());
        System.out.println(data);
    }
}
6.1.2 terms查询

terms和term的查询机制是一样,都不会将指定的查询关键字进行分词,直接去分词库中匹配,找到相应文档内容。

terms是在针对一个字段包含多个值的时候使用。

term:where province = 北京;

terms:where province = 北京 or province = ?or province = ?

where province in (‘北京’,‘山西’,‘武汉’)

# terms查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "terms": {
      "province": [
        "北京",
        "山西",
        "武汉"
      ]
    }
  }
}

代码实现方式

    @Test
    void terms() {
        StringQuery query = new StringQuery("{\n" +
                "    \"terms\": {\n" +
                "      \"province\": [\n" +
                "        \"北京\",\n" +
                "        \"山西\",\n" +
                "        \"武汉\"\n" +
                "      ]\n" +
                "    }\n" +
                "  }");
        // 分页参数
        PageRequest pageRequest = PageRequest.of(0,1);
        query.setPageable(pageRequest);
        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }

6.2 match查询【重点

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

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

查询全部内容,不指定任何查询条件。

# match_all查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match_all": {}
  }
}

代码实现方式

    @Test
    void matchAll() {
        StringQuery query = new StringQuery("{\n" +
                "    \"match_all\": {}\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.2.2 match查询

指定一个Field作为筛选的条件

查询关键词分词,有一个关键词匹配上内容的分词库,就返回json文档的json

# match查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match": { 
      "smsContent": "收货安装"
    }
  }
}

代码实现方式

    @Test
    void match() {
        StringQuery query = new StringQuery("{\n" +
                "    \"match\": {\n" +
                "      \"smsContent\": \"收货安装\"\n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.2.3 布尔match查询

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

select * from sms_logs where smsContent like ‘%中国%’ and smsContent like ‘%健康%’

# 布尔match查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match": {
      "smsContent": {
        "query": "中国 健康",
        "operator": "and"      # 内容既包含中国也包含健康
      }
    }
  }
}


# 布尔match查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match": {
      "smsContent": {
        "query": "中国 健康",
        "operator": "or"		# 内容包括健康或者包括中国
      }
    }
  }
}

代码实现方式

@Test
    void booleanMatchQuery() {
        StringQuery query = new StringQuery("{\n" +
                "    \"match\": {\n" +
                "      \"smsContent\": {\n" +
                "        \"query\": \"中国 健康\",\n" +
                "        \"operator\": \"or\" "+
                "      }\n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.2.4 multi_match查询

match针对一个field做检索,multi_match针对多个field进行检索,多个field对应一个text。

select * from sms_logs where province = ‘北京’ or smsContent like ‘%北京%’

# multi_match 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "multi_match": {
      "query": "北京",					# 指定text
      "fields": ["province","smsContent"]    # 指定field们
    }
  }
}

代码实现方式

    @Test
    void multiMatchQuery() {
        StringQuery query = new StringQuery("{\n" +
                "    \"multi_match\": {\n" +
                "      \"query\": \"北京\",\t\t\t\t\t\n" +
                "      \"fields\": [\"province\",\"smsContent\"]    \n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }

6.3 其他查询

6.3.1 id查询

根据id查询 where id = ?

# id查询
GET /sms-logs-index/sms-logs-type/1

代码实现方式

    @Test
    void byID() {
        final SmsLogs smsLogs = elasticsearchRestTemplate.queryForObject(GetQuery.getById("1"), SmsLogs.class);
        System.out.println(smsLogs);
    }
6.3.2 ids查询

根据多个id查询,类似MySQL中的where id in(id1,id2,id2…)

# ids查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "ids": {
      "values": ["1","2","3"]
    }
  }
}

代码实现方式

    @Test
    void byIds() {
        StringQuery query = new StringQuery("{\n" +
                "    \"ids\": {\n" +
                "      \"values\": [\"1\",\"2\",\"3\"]\n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.3.3 prefix查询

前缀查询,可以通过一个关键字去指定一个Field的前缀,从而查询到指定的文档。

在这些其他查询,是不会对查询关键词进行分词的

例如:亲爱的王五 就不会拆,只会在词库里找 亲爱的王五为前缀的词汇所对应的json文档

where name like ‘途虎%’

#prefix 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "prefix": {
      "corpName": {
        "value": "途虎"
      }
    }
  }
}

代码实现方式

    @Test
    void findByPrefix() {
        StringQuery query = new StringQuery("{\n" +
                "    \"prefix\": {\n" +
                "      \"corpName\": {\n" +
                "        \"value\": \"途虎\"\n" +
                "      }\n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.3.4 fuzzy查询

模糊查询,我们输入字符的大概,ES就可以去根据输入的内容大概去匹配一下结果。score

# fuzzy查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "fuzzy": {
      "corpName": {
        "value": "盒马先生",
        "prefix_length": 2			# 指定前面几个字符是不允许出现错误的
      }
    }
  }
}

代码实现方式

    @Test
    void fuzzy() {
        StringQuery query = new StringQuery("{\n" +
                "    \"fuzzy\": {\n" +
                "      \"corpName\": {\n" +
                "        \"value\": \"盒马先生\",\n" +
                "        \"prefix_length\": 2\t\t\t\n" +
                "      }\n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.3.5 wildcard查询

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

where name like ‘%中国%’

# wildcard 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "wildcard": {
      "corpName": {
        "value": "中国*"    # 可以使用*和?指定通配符和占位符
      }
    }
  }
}

代码实现方式

    @Test
    void wildCard() {
        StringQuery query = new StringQuery("{\n" +
                "    \"wildcard\": {\n" +
                "      \"corpName\": {\n" +
                "        \"value\": \"中国*\"    " +
                "      }\n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.3.6 range查询

范围查询,只针对数值类型,对某一个Field进行大于或者小于的范围指定

# range 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "range": {
      "fee": {
        "gt": 5,
        "lte": 10
         # 可以使用 gt:>      gte:>=     lt:<     lte:<=
      }
    }
  }
}

代码实现方式


6.3.7 regexp查询

正则查询,通过你编写的正则表达式去匹配内容。

regexp 里 这里例子 mobile它是 keywords类型 所以不会拆词,用正则规定匹配词库,匹配上就成功,返回json文档

Ps:prefix,fuzzy,wildcard和regexp查询效率相对比较低,要求效率比较高时,避免去使用

# regexp 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "regexp": {
      "mobile": "180[0-9]{8}"    # 编写正则
    }
  }
}

代码实现方式


6.4 复合查询

6.4.1 bool查询

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

  • must: 所有的条件,用must组合在一起,表示And的意思
  • must_not:将must_not中的条件,全部都不能匹配,标识Not的意思
  • should:所有的条件,用should组合在一起,表示Or的意思
# smsContent必须中包含中国和平安
# 运营商必须不是联通
# 省份可以为武汉或者北京,但不强求(给结果加分)


POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "smsContent": "中国"
          }
        },
        {
          "match": {
            "smsContent": "平安"
          }
        }
      ],
      "should": [
        {
          "term": {
            "province": {
              "value": "北京"
            }
          }
        },
        {
          "term": {
            "province": {
              "value": "武汉"
            }
          }
        }
      ],
      "must_not": [
        {
          "term": {
            "operatorId": {
              "value": "2"
            }
          }
        }
      ]

    }
  }
}

代码实现方式

 @Test
    void BoolQuery() {
        StringQuery query = new StringQuery("{\n" +
                "    \"bool\": {\n" +
                "      \"must\": [\n" +
                "        {\n" +
                "          \"match\": {\n" +
                "            \"smsContent\": \"中国\"\n" +
                "          }\n" +
                "        },\n" +
                "        {\n" +
                "          \"match\": {\n" +
                "            \"smsContent\": \"平安\"\n" +
                "          }\n" +
                "        }\n" +
                "      ],\n" +
                "      \"should\": [\n" +
                "        {\n" +
                "          \"term\": {\n" +
                "            \"province\": {\n" +
                "              \"value\": \"北京\"\n" +
                "            }\n" +
                "          }\n" +
                "        },\n" +
                "        {\n" +
                "          \"term\": {\n" +
                "            \"province\": {\n" +
                "              \"value\": \"武汉\"\n" +
                "            }\n" +
                "          }\n" +
                "        }\n" +
                "      ],\n" +
                "      \"must_not\": [\n" +
                "        {\n" +
                "          \"term\": {\n" +
                "            \"operatorId\": {\n" +
                "              \"value\": \"2\"\n" +
                "            }\n" +
                "          }\n" +
                "        }\n" +
                "      ]\n" +
                "\n" +
                "    }\n" +
                "  }");

        List<SmsLogs> smsLogs = elasticsearchRestTemplate.queryForList(query, SmsLogs.class);
        smsLogs.forEach(System.out::println);
    }
6.4.2 boosting查询

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

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

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

  • 搜索的关键字在文档中出现的频次越高,分数就越高
  • 指定的文档内容越短,分数就越高
  • 我们在搜索时,指定的关键字也会被分词,这个被分词的内容,被分词库匹配的个数越多,分数越高
# boosting查询  收货安装
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "smsContent": "收货安装"
        }
      },
      "negative": {
        "match": {
          "smsContent": "王五"
        }
      },
      "negative_boost": 0.5
    }
  }
}
# 把包含指定的关键字的文档的分数提高

POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "province": {
              "value": "北京"
            }
          }
        },
        {
          "term": {
            "province": {
              "value": "武汉",
                #这是乘法,乘以原来的分数 1.1631508*3=3.4894524
              "boost": 3
            }
          }
        }
      ]
    }
  }
}

代码实现方式


让京东自营和小米得分增加

POST /user_info/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "remark": "手机"
        }}
      ],
      "should": [
        {"match": {
          "producttype": "京东自营"
        }},
        {
          "match": {
            "producttype": {
              "query": "小米",
              "boost": 20
            }
          }
        }
      ]
    }
  }
}

七、面试题

什么是倒排索引(实现全文检索功能的底层数据结构)

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

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

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

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

举例:对如下数据建立倒排索引。

ES中存储数据,没一条数据,都是一个JSON字符串,{“id”:“1”,“content”:“xxxxxadfaaf”}

把content字段指定为text类型

当向ES中写入一条json数据时,如果某个字段的类型为text,则ES会自动构建该字段内容的倒排索引

每个字段都有词库,keywords就是本身不分词的词库,text 是分词的词库,每个字段分分隔离。

重复的词,不添加地址进词库。

原理:就是拿词去与 所对应字段 的 词库 进行对比,有对应的就返回 json 文档。

你可能感兴趣的:(ElasticSearch,elasticsearch,大数据,搜索引擎)