1 初识ElasticSearch
倒排索引:将文档进行分词,形成词条和id的对应关系即为反向索引。
(倒排索引:将各个文档中的内容,进行分词,形成词条,然后记录词条和数据的唯一标识(id)的对应关系,形成的产物)
以唐诗为例,所处包含“前”的诗句
正向索引:由《静夜思》–>窗前明月光—>“前”字
反向索引:“前”字–>窗前明月光–>《静夜思》
反向索引的实现就是对诗句进行分词,分成单个的词,由词推据,即为反向索引
ES存储和查询的原理
index(索引):相当于mysql的库
映射:相当于mysql 的表结构
document(文档):相当于mysql的表中的数据
数据库查询存在的问题:
- 性能低:使用模糊查询,左边有通配符,不会走索引,会全表扫描,性能低
- 功能弱:如果以”华为手机“作为条件,查询不出来数据Es使用倒排索引,对title 进行分词
1.1 ES概念详解
ElasticSearch是一个基于Lucene的搜索服务器,是一个分布式、高扩展、高实时的搜索与数据分析引擎
ES是基于RESTful web接口实现的查询
Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎
1.2 应用场景
搜索:海量数据的查询
日志数据分析
实时数据分析
1.3 数据同步
如果想实现"mysql"与"elasticSearch"的数据自动同步, 可以使用开源框架"alibaba/canal".
https://github.com/alibaba/canal/
2 ES核心概念
索引(index): 对应数据库中的表
映射(mapping): 表的结构。
文档(document) : 数据库中的一行数据。
类型(type):
\- ES 5.x中一个index可以有多种type。 //那个时候,index相当于数据库,type相当于表
\- ES 6.x中一个index只能有一种type。
\- ES 7.x以后,将逐步移除type这个概念,现在的操作已经不再使用,默认_doc
3 操作ES-脚本
3.1 RESTful风格介绍
在操作ES时,需要使用使用RESTful风格的请求来操作ES。
GET:用来获取资源
POST:用来新建资源(也可以用于更新资源)
PUT:用来更新资源
DELETE:用来删除资源
注意:
ES有多种操作工具:
Postman:操作时需要指定完整路径,如添加索引使用: PUT http://ip:端口/索引名称
kibana: 操作时可以使用简化路径。如添加索引使用: PUT /索引名称
3.2 操作索引
添加索引
PUT http://ip:端口/索引名称
查询索引
GET http://ip:端口/索引名称 #查询单个索引信息
GET http://ip:端口/索引名称1,索引名称2... #查询多个索引信息
GET http://ip:端口/_all #查询所有索引信息在这里插入代码片
删除索引
DELETE http://ip:端口/索引名称
关闭索引
POST http://ip:端口/索引名称/_close
打开索引
POST http://ip:端口/索引名称/_open
3.3 操作映射
3.3.1 操作语法
添加映射
PUT /索引名/_mapping
{
"properties":{
"属性名":{
"type": "属性类型",
"analyzer": "分词类型"
},
"属性名":{
"type": "属性类型"
}
}
}
创建索引并添加映射
PUT /索引名
{
"mappings": {
"properties": {
"属性名": {
"type": "属性类型"
},
"属性名": {
"type": "属性类型"
}
}
}
}
查询索引
GET /索引名/_mapping
添加字段
PUT /索引名/_mapping
{
"properties": {
"属性名": {
"type": "属性类型"
}
}
}
# 添加字段指的是“在映射已经添加完成的情况,对映射增加一个字段”
3.3.2 数据类型
简单数据类型
- 字符串
text:会分词,不支持聚合
keyword:不会分词,将全部内容作为一个词条,支持聚合(类似于可以执行sum之类的统计) - 数值
- 布尔:boolean
- 二进制:binary
- 范围类型:integer_range, float_range, long_range, double_range, date_range
- 日期:date
复杂数据类型
- 数组:[ ] 数组类型的JSON对象
- 对象:{ } 单个JSON对象
3.4 操作文档
添加文档,指定id
POST /索引名/_doc/id号
{
"属性名":"属性值",
"属性名":属性值,
"属性名":"属性值"
}
添加文档,不指定id
#添加文档,不指定id(系统会随机ID)
POST /索引名/_doc/
{
"属性名":"属性值",
"属性名":属性值,
"属性名":"属性值"
}
查询指定文档
GET /索引名/_search/指定ID
查询所有文档
GET /索引名/_search
删除指定id文档
DELETE /索引名/_doc/1
修改文档,指定id
PUT /索引名/_doc/id号
{
"属性名":"属性值",
"属性名":属性值,
"属性名":"属性值"
}
4 分词器
4.1 分词器-介绍
分词器的作用是将一串字符串改为“词”的列表,主要用来建立“倒排索引”,方便后续ES查询。
如“大学生活”,可分为:[大、大学、大学生、学、学生、生、生活、活]。
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包
IKAnalyzer是一个基于Maven构建的项目,具有60万字/秒的高速处理能力,支持用户词典扩展定义
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/archive/v7.4.0.zip
4.2 ik分词器使用
IK分词器有两种分词模式:ik_max_word和ik_smart模式。
4.2.1 ik_max_word
会将文本做最细粒度的拆分,比如会将“乒乓球明年总冠军”拆分为“乒乓球、乒乓、球、明年、总冠军、冠军。
例如:
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "乒乓球明年总冠军"
}
分词器结果:
{
"tokens" : [
{
"token" : "乒乓球",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "乒乓",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "球",
"start_offset" : 2,
"end_offset" : 3,
"type" : "CN_CHAR",
"position" : 2
},
{
"token" : "明年",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "总冠军",
"start_offset" : 5,
"end_offset" : 8,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "冠军",
"start_offset" : 6,
"end_offset" : 8,
"type" : "CN_WORD",
"position" : 5
}
]
}
4.2.2 ik_smart
会做最粗粒度的拆分,比如会将“乒乓球明年总冠军”拆分为乒乓球、明年、总冠军。
例如:
GET /_analyze
{
"analyzer": "ik_smart",
"text": "乒乓球明年总冠军"
}
分词器结果:
{
"tokens" : [
{
"token" : "乒乓球",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "明年",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "总冠军",
"start_offset" : 5,
"end_offset" : 8,
"type" : "CN_WORD",
"position" : 2
}
]
}
4.3 使用IK分词器-查询文档
词条查询:term
词条查询不会分析查询条件,只有当词条和查询字符串完全匹配时才匹配搜索
全文查询:match
全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集
词条查询:term
# 查询person中,address属性包含"北京昌平"词条的数据。
# 注意:address所拆分出来的词条中,必须包含完整的“北京昌平”词条,则数据才能别查询出来
GET /person/_search
{
"query": {
"term": {
"address": {
"value": "北京昌平"
}
}
}
}
全文查询:match
#IK会先把“北京昌平”进行词条拆分,比如拆为“北京”,“昌平”。
#然后分别查询“北京”,“昌平”,然后求并集
GET /person/_search
{
"query": {
"match": {
"address":"北京昌平"
}
}
}
5 操作ES-JavaAPI
5.1 SpringBoot整合ES
①搭建SpringBoot工程
②引入ElasticSearch相关坐标
<!--引入es的坐标-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.4.0</version>
</dependency>
配置端口信息
spring.elasticsearch.rest.uris=http://192.168.111.132:9200
③ 创建ES实体类
@Document(indexName = "user") //索引的名
public class User {
@Id
@Field(type = FieldType.Integer)
private Integer id;
@Field(type = FieldType.Keyword)
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Text ,analyzer = "ik_smart")
private String address;
5.2.3 索引,操作
@SpringBootTest
class EsIndexTests{
@Autowired
private ElasticsearchRestTemplate restTemplate;
/**
* 添加索引
*/
@Test
public void addIndexTest(){
boolean b = restTemplate.indexOps(User.class).create();
System.out.println(b);
}
/**
* 删除索引
*/
@Test
public void deleteIndexTest(){
boolean delete = restTemplate.indexOps(User.class).delete();
System.out.println(delete);
}
}
5.3 映射操作
@SpringBootTest
public class EsMappingTest {
@Autowired
private ElasticsearchRestTemplate restTemplate;
/**
* 添加映射
*/
@Test
public void addMappingTest(){
boolean exists = restTemplate.indexOps(User.class).exists(); //判断是否存在索引
if (!exists){ //若不存在,则创建索引
restTemplate.indexOps(User.class).create();
}
boolean mapping = restTemplate.indexOps(User.class).putMapping(User.class); //创建映射
System.out.println(mapping);
}
/**
* 查询映射
*/
@Test
public void findMappingTest(){
Map<String, Object> mapping = restTemplate.indexOps(User.class).getMapping();
System.out.println(mapping);
}
}
5.4 文档操作
@SpringBootTest
public class EsDocumentTest {
@Autowired //映射注入
private ElasticsearchRestTemplate restTemplate;
/**
* 添加文档信息
* 若修改,则id 相同,内容不同即可进行内容的覆盖
*/
@Test
public void addDocumentTest() {
User user1 = new User(1, "石破天", 23, "侠客岛");
User user2 = new User(2, "郭靖", 23, "襄阳");
User user3 = new User(3, "虚竹", 23, "飘渺峰");
User[] us = {user1, user2, user3};
Iterable<User> users = restTemplate.save(us);
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
/**
* 根据id修改信息
* 若修改,则id 相同,内容不同即可进行内容的覆盖
*/
@Test
public void updateDocumentTest() {
User user = new User(1, "石破天2.0", 23, "侠客岛2.0");
restTemplate.save(user);
}
/**
* 查询
*/
@Test
public void findDocumentTest() {
User user = restTemplate.get("1", User.class);
System.out.println(user);
}
/**
* 根据id删除文档信息
*/
@Test
public void deleteDocumentTest() {
String delete = restTemplate.delete("1", User.class);
System.out.println(delete);
}
}