搜索引擎是指一个庞大的互联网资源数据库,如网页、新闻组、程序、图像等。它有助于在万维网上定位信息。用户可以通过以关键字或者短语的形式将查询传递到搜索引擎中来搜索信息。然后搜索引擎搜索其数据库并向用户返回相关链接。
一般来说,搜索引擎有三个基本组件
Elasticesearch是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎Apache Lucene™基础上的搜索引擎,当然ElsticeSearch并不仅仅是Lucene那么简单,它不仅包括了全文搜索功能,还可以进行一下工作。
先说ElasticSearch的文件存储,ElasticSearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式,比如下面这条数据
{
"name" : "John",
"sex" : "Male",
"age" : 25,
"birthDate": "1990/05/01",
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
用MySQL这样的数据库存储就会创建一张表,然后字段。在ElasticSearch里就是一个文档,而这个文档就会属于一种类型,各种各样的类型存在于一个索引当中。用一份简易的将ElasticSearch和关系型数据术语对照表:
关系型数据库 | ElasticSearch |
---|---|
数据库 | 索引(Index) |
表 | 类型(Type) |
行 | 文档(Docments) |
列 | 字段(Fields) |
一个ElaseicSearch集群可以包含多个索引(数据库),也就是说其中包含了很多类型(表),这些类型包含了很多的文档(行),然后每个文档中又包含了很多的字段(列)。ElasticSearch的交互可以使用JavaAPI,也可以直接使用HTTP的REstfulAPI方式,比如我们也可以直接使用HTTP的RestfulApi方式,比如我们打算插入一条数据可以简单发送个HTTP的请求。具体操作指引
es做关键的就是提供了强大的索引能力了,这边文章就详细的说明了索引的密码了
ES索引的精髓就一句话
一切设计都是为了提高搜索的性能
另一层意思:为了提高搜索的性能,难免会牺牲某些方面,比如插入/更新,否则其他数据库不用混了。在ES插入一条数据,就是PUT一个Json的对象,这个对象有fields,就是很多字段,那么再插入这些数据到ES的同时,ES还默默的为这些字段建立索引–倒排索引,因为ES最核心功能是搜索。
上面讲到的那篇文章说es使用的索引比关系型数据库的B-Tree索引快呢,
什么是B-Tree索引呢?
二叉树查找效率是logN,同时插入新的节点不必移动全部节点,索引使用树形结构存储索引,能同时兼顾插入和查询的性能。因此在这个基础上,在结合磁盘的读取特性(顺序读/随机读),传统关系型数据库采用了B-Tree/B+Tree这种数据结构。
为了提高查询效率,减少磁盘寻道次数,将多个值作为一个数组通过连续区间存放,一次寻道读取多个数据,同时也降低树的高度。
什么是倒排索引,太多了具体开这篇文章
ES索引思路
将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数(同时也可用磁盘顺序读特性),结合各种奇技淫巧的压缩算法,用法及其苛刻的态度使用内存。
所以在使用ES进行索引时需要注意:
分词器和es的版本必须要一样才行
version: '3.1'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.10.1
restart: always
container_name: elasticsearch
ports:
- 9200:9200
environment:
discovery.type: single-node
volumes:
- ./plugins/:/usr/share/elasticsearch/plugins/
在plugins目录创建一个目录下分词器解压放进去
其他日志或者配置文件选择性做映射持久化
分词器下载地址 提取码:necr
http://localhost:9200/company/_doc/_search
{
"query":{
"match":{
"firstName":"吗?"
}
}
}
{
"query": {
"term": {
"firstName":"小明"
}
}
}
{
"query": {
"bool": {
"must": [
{"term":{"firstName":"小明"}}
]
}
}
}
{
"query": {
"bool": {
"should": [
{"match":{"firstName":"小明"}},
{"match": {"lastName": "小明"}}
]
}
}
}
{
"query": {
"bool": {
"must_not": [
{"match":{"firstName":"沈瑶"}}
]
}
}
}
{
"query": {
"bool": {
"should": [
{"match":{"firstName":"小明"}},
{"match": {"lastName": "小明"}}
]
}
},
"sort": {
"firstName": {
"order": "desc"
}
}
}
{
"query": {
"bool": {
"must_not": [
{"match":{"firstName":"沈瑶"}}
]
}
},
"from": 0,
"size": 10
}
pre_tags 关键字前打标签。
post_tags 关键字结尾标签。
在这里,pre_tags和post_tags 可以放到字段里,也可以放到highlisht里。一个等于在在子类范围内,一个等于在父类范围内,优先使用子类的。
{
"query": {
"bool": {
"must_not": [
{"match":{"firstName":"沈瑶"}}
]
}
},
"highlight": {
"pre_tags": "",
"post_tags": "",
"fields": {
"firstName": {
"pre_tags": "",
"post_tags": "
"
}
},
"fields": {
"lastName": {
}
}
},
"from": 0,
"size": 10
}
url/_analyze
{
"analyzer":"ik_max_word",
"text":"小明是个坏蛋"
}
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
//new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 9200, "http")));
return client;
}
使用SpringBoot操作es的主要的两种方法,使用9200端口的是通过rest风格的方式操作es。用9300的是TCP连接的端口,是使用TransportClient直接操作es节点。
org.springframework.boot
spring-boot-starter-data-elasticsearch
package com.es.example.entity;
import lombok.Data;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* @author sy
* @date Created in 2020.6.27 15:21
* @description
*/
@Document(indexName = "test_index", shards = 1,replicas = 0)
@Data
public class Employee {
/**
* index 是否创建索引,默认创建
*/
@Field(index = false,type = FieldType.Text)
private String id;
/**
* analyzer 存入时是否使用分词
* type 在es里的类型。不指定可自动
* store 是否独立存储
* searchAnalyzer 查询分词
*/
@Field(analyzer = "ik_max_word",type = FieldType.Text,store = true,searchAnalyzer = "ik_max_word")
private String firstName;
@Field(analyzer = "ik_max_word",type = FieldType.Text,store = true,searchAnalyzer = "ik_max_word")
private String lastName;
@Field
private Integer age = 0;
@Field(analyzer = "ik_max_word",type = FieldType.Text,store = true,searchAnalyzer = "ik_max_word")
private String about;
}
type:字段类型,取值是枚举:FieldType
index:是否索引,布尔类型,默认是true
store:是否存储,布尔类型,默认是false
analyzer:分词器名称:ik_max_word
searchAnalyzer : 查询使用分词
indexName:对应索引库名称
type:对应在索引库中的类型
shards:分片数量,默认5
replicas:副本数量,默认1
package com.es.example.dto;
import com.es.example.entity.Employee;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
/**
* @author sy
* @date Created in 2020.6.27 15:20
* @description
*/
public interface EmployeeRepository extends ElasticsearchRepository {
/**
* 根据ID查询信息
* @param id
* @return
*/
Employee queryEmployeeById(String id);
/**
*
* @param firstName
* @param lastName
* @return
*/
Page findByFirstNameOrLastName(String firstName, String lastName, Pageable pageable);
/**
* 根据范围
* @param age
* @param age2
* @return
*/
Page findByAgeBetween(double age, double age2,Pageable pageable);
}
Page findByNameAndPrice(String name, Integer price);
上面会编译成
{ "bool" :
{ "must" :
[
{ "field" : {"name" : "?"} },
{ "field" : {"price" : "?"} }
]
}
}
@Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
Page findByName(String name,Pageable pageable);
Pageable 是一定需要的,不然报错!
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.misc
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | {“bool” : {“must” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}} |
Or | findByNameOrPrice | {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}} |
Is | findByName | {“bool” : {“must” : {“field” : {“name” : “?”}}}} |
Not | findByNameNot | {“bool” : {“must_not” : {“field” : {“name” : “?”}}}} |
Between | findByPriceBetween | {“bool” : {“must” : {“range” : {“price” : {“from” : ?,“to” : ?,“include_lower” : true,“include_upper” : true}}}}} |
LessThanEqual | findByPriceLessThan | {“bool” : {“must” : {“range” : {“price” : {“from” : null,“to” : ?,“include_lower” : true,“include_upper” : true}}}}} |
GreaterThanEqual | findByPriceGreaterThan | {“bool” : {“must” : {“range” : {“price” : {“from” : ?,“to” : null,“include_lower” : true,“include_upper” : true}}}}} |
Before | findByPriceBefore | {“bool” : {“must” : {“range” : {“price” : {“from” : null,“to” : ?,“include_lower” : true,“include_upper” : true}}}}} |
After | findByPriceAfter | {“bool” : {“must” : {“range” : {“price” : {“from” : ?,“to” : null,“include_lower” : true,“include_upper” : true}}}}} |
Like | findByNameLike | {“bool” : {“must” : {“field” : {“name” : {“query” : “?*”,“analyze_wildcard” : true}}}}} |
StartingWith | findByNameStartingWith | {“bool” : {“must” : {“field” : {“name” : {“query” : “?*”,“analyze_wildcard” : true}}}}} |
EndingWith | findByNameEndingWith | {“bool” : {“must” : {“field” : {“name” : {“query” : “*?”,“analyze_wildcard” : true}}}}} |
Contains/Containing | findByNameContaining | {“bool” : {“must” : {“field” : {“name” : {“query” : “?”,“analyze_wildcard” : true}}}}} |
In | findByNameIn(Collectionnames) | {“bool” : {“must” : {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“name” : “?”}} ]}}}} |
NotIn | findByNameNotIn(Collectionnames) | {“bool” : {“must_not” : {“bool” : {“should” : {“field” : {“name” : “?”}}}}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | {“bool” : {“must” : {“field” : {“available” : true}}}} |
False | findByAvailableFalse | {“bool” : {“must” : {“field” : {“available” : false}}}} |
OrderBy | findByAvailableTrueOrderByNameDesc | {“sort” : [{ “name” : {“order” : “desc”} }],“bool” : {“must” : {“field” : {“available” : true}}}} |
SearchRequest searchRequest = new SearchRequest("test_index");
// SearchSourceBuilder 是构建搜索条件的盒子。
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//设置高亮的字段,也可以不设置。
HighlightBuilder firstName = new HighlightBuilder().field("firstName");
firstName.preTags("");
firstName.postTags("");
sourceBuilder.highlighter(firstName);
//设置分页。
sourceBuilder.from(0);
sourceBuilder.size(10);
/**
* 常用的查询方式有:
* .matchQuery() 这个是模糊查询
* .matchAllQuery() 查询全部
* .termQuery() 精确查询。
*/
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("firstName", "小明");
//设置响应超时时间
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
sourceBuilder.query(matchQueryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//这个就是查询到的数据。
//searchResponse.getHits().getHits();
ArrayList