一、特点
官网文档: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>
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; }
@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; } }
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; } }
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>
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 :查询时候使用的分词器