Elasticsearch 基本概念与用法

  1. 特点

  2. 查询语法
  3. 分词器

  4. spring boot + Elasticsearch 实现搜索框提示
  5. @Document 与 @Field

一、特点

官网文档:https://www.elastic.co/guide/en/elasticsearch/reference/5.5/index.html

常用于描述 es 是: 全文搜索引擎、倒排索引、实时分析、分布式

基本概念

  • Index :一系列文档的集合,类似于 mysql 中数据库的概念
  • type: 在 Index 里面可以定义不同的 type ,type 的概念类似mysql中表的概念,是一系列具有相同特征数据的结合
  • document : 文档的概念类似于mysql 中一条存储记录,为 json 格式
  • shards: 索引分片
  • replica : 副本

分片

Elasticsearch 集群允许系统存储的数据总量超过单机容量,为了实现这个功能,ES 将数据分散到多个物理的 Lucene 索引上,这些 Lunene 索引被称为分片(shard),散步这些分片的过程叫做分片处理(sharding),

ES 会自动完成分片工作,对用户来说更像一个是大是索引。

当然,除了 ES 自动进行进行分片处理外,用户为具体的应用进行参数调优也至关重要,因为分片的数量在索引创建时就被配置好了,之后无法改变,除非创建一个新索引并重新索引全部数据

副本

分片处理允许用户推送超过单机容量的数据到 ES ,副本(replica)则解决了访问压力过大时单机无法处理所有请求的问题。

实现方法是为每个分片创建冗余的副本,处理查询时可以把这些副本当作最初的主分片(primary shard)使用。

如果主分片所在的主机宕机了,ES 会自动从该分片中选出一个当作新的主分片,不会中断索引和搜索服务,并且可以在任意时间节点添加或删除副本,可以随时调整副本的数量

二、查询语法

1._cat :查询当前集群中 index 数量、运行状态、当前集群所在的 ip 

  • health  检查集群的运行状况
  • count  查询集群或者 index 中文档的数量
  • indices  查询集群中index的数据,包括index 的分片书、document的数量、存储所用空间

示例:

http://81.68.206.246:9200/_cat/health?v (?v 的意思是显示列出项的 title)

2.查询API 

  • query
  • term
  • match
  • bool
  • range
  • from
  • size
  • sort
  • _source
  • script_fields
  • ags

3. query DSL

query DSL 是 es 提供的一套完整的基于 json 格式的结构化查询方法,包含两类不同的查询语义

  • Leaf query clauses : 叶子查询语法是在指定的字段中搜索指定的值,有 match 、term 、range
  • Compound query clauses : 复合查询语法包含叶子句法 或者复合句法,作用是为了多重查询,有 bool 、dis_max 

4.分词器选择

ik_smart

ik_max_word

三、ik 分词器

ES 内置很多分词器,但是对中文不友好,使用第三方插件--中文分词器(ik 分词器)

使用默认的分词器查询测试:81.68.206.246:9200/_analyze?pretty=true

ES 分词器使用的都是将中文分成单个的词,因此需要导入第三方分词器

ES IK分词器下载:https://github.com/medcl/elasticsearch-analysis-ik/releases

ES 常用分词器如下:

StandardAnalyzer 单字分词
CJKAnalyzer 二分法
IKAnalyzer 词库分词

其中 IK  分词器是第三方插件,下载需要找与 ES 对应的版本

将下载的 zip 分词包上传到服务器, 并且在 ES 目录的 plugins 目录下创建空文件夹,将zip 解压到内容复制到该文件夹下,重启ES

分词器参考文档:

https://www.cnblogs.com/xiaobaozi-95/p/9328948.html

https://blog.csdn.net/weixin_44723434/article/details/89888489

 四、spring boot 与 ES 实现搜索框提示

1.maven pom.xml 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.supoman</groupId>
    <artifactId>springboot-elasticsearch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-elasticsearch</name>
    <description>elasticsearch for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--集合工具包-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
View Code

2.项目结构

 3.定义实体 

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "school",type = "_doc")
public class Notice {


    @JsonProperty("id")
    private String id;

    @JsonProperty("title")
    private String title;

    @JsonProperty("originCreateTime")
    private String originCreateTime;

    @JsonProperty("msg")
    private String msg;

    @JsonProperty("readCount")
    private Integer readCount;

}
View Code
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultVO<T>{

    private String msg ;
    private  T t;


    public static<T>  ResultVO  success(T t){
        ResultVO resultVO = new ResultVO();
        resultVO.setT(t);
        resultVO.setMsg("success");
       return resultVO;
    }
}
View Code

4.Repository

@Component
public interface NoticeRepository extends ElasticsearchRepository<Notice,String> {
}

5.控制层接口

@Controller
public class HomeController {

    @GetMapping({"/","/index","/home"})
    public String index(){
        return "index";
    }
}
@RestController
@RequestMapping("/api")
public class NoticeController {

    @Autowired
    private NoticeRepository noticeRepository;

    @GetMapping("/save")
    public ResultVO save(String id, String title){
        Notice notice = new Notice();
        notice.setId(id);
        notice.setReadCount(10);
        notice.setTitle(title);
        noticeRepository.save(notice);
        return ResultVO.success(notice);
    }

    @GetMapping("/search")
    public List<Notice> search(String keyword, @PageableDefault(page = 0,value = 10)Pageable pageable){
        //按标题进行搜索
        QueryBuilder queryBuilder = QueryBuilders.matchQuery("title",keyword);

        //如果实体和数据的名称对应,就自动封装
        Iterable<Notice> listIt =  noticeRepository.search(queryBuilder,pageable);

        List<Notice> list = Lists.newArrayList(listIt);

        return list;
    }

}
View Code

6.html 展示案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
#header_search_suggest{
    position: absolute;
    width: calc(100% - 10px);
    left: 4px;
    border: solid 0px #ccc;
    background-color: white;
    text-align: left;
    z-index: 101;
    display: block;
}
#header_search_suggest li{
    font-size: 14px;
    border-bottom: 0px solid #eeeeee;
    padding-left:460px;
    list-style-type:none;
}
#header_search_suggest li a{
    padding:0.5em 1em;
    color:#333333;
    display: block;
    text-decoration: none;
}
#header_search_suggest li a:hover{
    background-color: #EDF0F2;
    color:#2F7EC4;
}
#header_search_suggest li a em{
    font-style: italic;
    color:#999;
    font-size:0.8em;
}
.btn{width: 7em;}

    </style>


<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>

</head>
<body>
   
<div  class="container" style="margin-top: 3em;">

    <form method="GET" action="/api/search" id="header_search" class="form-inline">
        <div class="form-group">
           <label for="exampleInputName2">输入搜索关键词试试</label>
           <input type="text" class="form-control" id="keyword" name="keyword" value="" autocomplete="off" />
        </div>
        <div class="form-group">
            <input type="submit" value="搜索"  class="btn btn-success"/>
        </div>
      
    </form>
    <ul id="header_search_suggest"></ul>

</div>

<!--  js部分,这部分控制,输入框输入时,进行及时提示的功能  -->
<script>
var xhr = null;
$('#keyword').bind('input propertychange', function () {
    if (xhr) {
        xhr.abort();//如果存在ajax的请求,就放弃请求
    }

    var inputText = $.trim(this.value);
    if (inputText != "") { //检测键盘输入的内容是否为空,为空就不发出请求
        xhr = $.ajax({
            type: 'GET',
            url: '/api/search',//注意这里输入框输入进行及时提示的方法与action方法不同
            cache: false,//不从浏览器缓存中加载请求信息
            data: "keyword=" + inputText,
            //data: {keyword: inputText},
            dataType: 'json',
            success: function (json) {
                if (json.length != 0) {
                    //检测返回的结果是否为空
                    var lists = "";   
                    for(var index=0;index<json.length;index++){
                        //标题
                        var searchContent = json[index].title;
                        var suggestItem = '';
                        if(searchContent.toLowerCase().indexOf(inputText.toLowerCase()) > -1 ){
                             var searchRegExp = new RegExp('(' + inputText + ')', "gi");
                             suggestItem = searchContent.replace(searchRegExp, ("<strong>$1</strong>"));
                        }
                        suggestItem = suggestItem + "<em> - " + json[index].title + ' * ' + json[index].id + "</em>";
                        lists += "<li class='listName' ><a href='/api/search?id=" + json[index].id + "&key=" + json[index].msg+"'>" +suggestItem+ "</a></li>";
                    }
                    $("#header_search_suggest").html(lists).show();//将搜索到的结果展示出来
                } else {
                    $("#header_search_suggest").hide();
                }
                //记录搜索历史记录
                // $.post('/index.php/index/index/savesearchlog',{keyword: inputText,count: json.count});
            }
        });
    } else {
        $("#header_search_suggest").hide();//没有查询结果就隐藏搜索框
    }
}).blur(function () {
    setTimeout('$("#header_search_suggest").hide()',500);//输入框失去焦点的时候就隐藏搜索框,为了防止隐藏过快无法点击,设置延迟0.5秒隐藏
});
</script>



</body>
</html>
View Code

7.配置文件

server:
  port: 9000

spring:
  data:
    elasticsearch:
      repositories:
        enabled: true
      cluster-nodes: 81.68.206.246:9300

8.最终效果

 Github地址:https://github.com/baizhuang/springboot-elasticsearch

可能会报错 :超时 127.0.0.1:9200

因为spring boot 中 es会有健康检,使用了默认的 ip,修改配置文件

spring:
  data:
    elasticsearch:
      repositories:
        enabled: true
      cluster-nodes: 81.68.206.246:9300
  elasticsearch:
    rest:
      uris: ["http://81.68.206.246:9200"]

参考:http://blog.joylau.cn/2019/01/16/SpringBoot-Elasticsearch-HealthCheck/

 五、Elasticsearch 注解

在 spring boot 使用ES中定义了一些注解,如 @Document 、@Field

@Data
@Document(indexName = "tradingAccount")
public class TradingAccount {

    @Field(analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private Long userId;

    @Field(analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private Long accountId;

    private String userAvatar;

    private String username;
}

这里主要有 @Document 和 @Field 两个注解

@Document

@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {
    String indexName();

    String type() default "";

    boolean useServerConfiguration() default false;

    short shards() default 5;

    short replicas() default 1;

    String refreshInterval() default "1s";

    String indexStoreType() default "fs";

    boolean createIndex() default true;

    VersionType versionType() default VersionType.EXTERNAL;
}

indexName 对应的是 elasticsearch 中的 index . createIndex 默认值是True ,所以当 elasticsearch 中没有对应的索引时会创建索引

@Field

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface Field {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    FieldType type() default FieldType.Auto;

    boolean index() default true;

    DateFormat format() default DateFormat.none;

    String pattern() default "";

    boolean store() default false;

    boolean fielddata() default false;

    String searchAnalyzer() default "";

    String analyzer() default "";

    String normalizer() default "";

    String[] ignoreFields() default {};

    boolean includeInParent() default false;

    String[] copyTo() default {};
}

type : 返回值是 FieldType 类型,指定数据类型

public enum FieldType {
    text, Integer, Long, Date, Float, Double, Boolean, Object, Auto, Nested, Ip, Attachment, keyword
}

analyzer : 指定分词器

searchAnalyzer :查询时候使用的分词器

参考:https://my.oschina.net/kipeng/blog/1799827

原文地址:https://www.cnblogs.com/baizhuang/p/13808659.html