一、Elasticsearch介绍
1.1 全文检索索引
Elasticsearch是一个全文检索服务器,全文检索是一种非结构化数据的搜索方式。
那么什么是结构化数据和非结构化数据呢?
- 结构化数据:指具有固定格式固定长度的数据,如数据库中的字段。
- 非结构化数据:指格式和长度不固定的数据,如电商网站的商品详情,每种商品的详情都是不相同的。
结构化数据一般存入数据库,使用sql语句即可快速查询。但由于非结构化数据的数据量大且格式不固定,我们需要采用全文检索的方式进行搜索。全文检索通过建立倒排索引加快搜索效率。
1.2 倒排索引
什么是索引?
将数据中的一部分信息提取出来,重新组织成一定的数据结构,我们可以根据该结构进行快速搜索,这样的结构称之为索引。
索引即目录,例如字典会将字的拼音提取出来做成目录,通过目录即可快速找到字的位置。
索引分为正排索引和倒排索引。
正排索引(正向索引)
将文档id建立为索引,通过id快速可以快速查找数据。如数据库中的主键就会创建正排索引。
倒排索引(反向索引)
非结构化数据中我们往往会根据关键词查询数据。此时我们将数据中的关键词建立为索引,指向文档数据,这样的索引称为倒排索引。
创建倒排索引流程
1.3 Elasticsearch的出现
多年前,一个刚结婚的名叫Shay的失业开发者,跟着妻子去了伦敦,他的妻子在那里学习厨师。Shay使用全文检索工具——lucene,给他的妻子做一个食谱搜索引擎。
但Lucene的操作非常复杂,且Lucene是一个单机软件,不支持联网访问。因此 Shay基于Lucene开发了开源项目 Elasticsearch。Elasticsearch本质是一个java语言开发的web项目,我们可以通过RESTful风格的接口访问该项目内部的Lucene,从而让全文搜索变得简单。
从此以后,Elasticsearch成为了Github上最活跃的项目之一, Elastic公司围绕Elasticsearch提供商业服务,并开发新的特性。Elasticsearch将永远开源并对所有人可用。
1.4 Elasticsearch应用场景
- 2013年初,GitHub抛弃了Solr,采取Elasticsearch来做PB级的搜索。GitHub使用Elasticsearch搜索20TB 的数据,包括13亿文件和1300亿行代码。
- 维基百科:以Elasticsearch为基础的核心搜索架构。
- 百度:百度目前广泛使用Elasticsearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据
- 新浪使用ES分析处理32亿条实时日志。
- 阿里使用ES构建自己的日志采集和分析体系。
- 我们可以使用Elasticsearch实现全站搜索,线上商城系统的搜索,分析日志等功能。
1.5 Elasticsearch数据结构
文档(Document):文档是可被查询的最小数据单元,一个 Document 就是一条数据。类似于关系型数据库中的记录的概念。
类型(Type):具有一组共同字段的文档定义成一个类型,类似于关系型数据库中的数据表的概念。
索引(Index):索引是多种类型文档的集合,类似于关系型数据库中的库的概念。
域(Fied):文档由多个域组成,类似于关系型数据库中的字段的概念。
Elasticsearch跟关系型数据库中概念的对比:
JAVA | 项目 | 实体类 | 对象 | 属性 |
---|---|---|---|---|
ES | Index | Type | Document | Filed |
Mysql | Database | Table | Row | Column |
注:ES7.X之后删除了type的概念,一个索引不会代表一个库,而是代表一张表。所以目前的ES中概念对比为
JAVA | 项目 | 实体类 | 对象 | 属性 |
---|---|---|---|---|
ES | 无此概念 | Index | Document | Filed |
Mysql | Database | Table | Row | Column |
二、Elasticsearch安装
2.1 Linux安装ES服务
准备工作
1.准备一台搭载有CentOS7系统的虚拟机,使用XShell连接虚拟机
2.关闭防火墙,方便访问ES
#关闭防火墙:
systemctl stop firewalld.service
#禁止防火墙自启动:
systemctl disable firewalld.service
3.配置最大可创建文件数大小,因为在安装ES的时候会创建大量的文件,但是linux系统对创建文件的数量是有限制的。
#打开系统文件:
vim /etc/sysctl.conf
#添加以下配置:
vm.max_map_count=655360
#配置生效:
sysctl -p
4.由于ES不能以root用户运行,我们需要创建一个非root用户,此处创建一个名为es的用户:
#创建用户:
useradd es
安装服务
1.使用xftp将linux版的ES上传至虚拟机
2.解压ES
#解压:
tar -zxvf elasticsearch-7.17.0-linux-x86_64.tar.gz
#重命名:
mv elasticsearch-7.17.0 elasticsearch1
#移动文件夹:
mv elasticsearch1 /usr/local/
#es用户取得该文件夹权限:
chown -R es:es /usr/local/elasticsearch1
3.启动ES服务
#切换为es用户:
su es
#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/
#启动ES服务:
./elasticsearch
#查询ES服务是否启动成功
curl 127.0.0.1:9200
2.2 安装kibana
Kibana是一款开源的数据分析和可视化平台,设计用于和Elasticsearch协作。我们可以使用Kibana对Elasticsearch索引中的数据进行搜索、查看、交互操作。
1.使用xftp工具将Kibana压缩文件上传到Linux虚拟机
注意,Kibana的版本一定要和Elasticsearch的版本一致。
2.解压到usr/local下
tar -zxvf kibana-7.17.0-linux-x86_64.tar.gz -C /usr/local/
3.修改配置
# 进入Kibana解压路径
cd /usr/local/kibana-7.17.0-linux-x86_64/config
# 修改配置文件
vim kibana.yml
# 加入以下内容
# kibana主机IP
server.host: "虚拟机IP"
# Elasticsearch路径
elasticsearch.hosts: ["http://127.0.0.1:9200"]
4.启动:
kibana也不能以root用户运行,我们给es用户设置kibana目录的权限,并使用es用户运行kibana
# 给es用户设置kibana目录权限
chown -R es:es /usr/local/kibana-7.17.0-linux-x86_64/
# 切换为es用户
su es
# 启动kibana
cd /usr/local/kibana-7.17.0-linux-x86_64/bin/
./kibana
需要注意的是,不要忘了启动es,可以开两个窗口,一个启动es另一个启动kibana
5.访问kibana:http://192.168.66.100:5601
6.点击Management
=>Stack Management=>
Index Management
可以查看es索引信息。
2.3 Docker安装ES和Kibana
安装Elasticsearch
1.在Centos7中安装docker
# 安装Docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
# 启动docker
systemctl start docker
2.拉取ES镜像
docker pull elasticsearch:7.17.0
3.启动容器
# docker容器间建立通信
docker network create elastic
# 创建es容器
docker run --restart=always -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name='elasticsearch' --net elastic --cpuset-cpus="1" -m 1G -d elasticsearch:7.17.0
参数:
run :运行容器
--restart=always:开机自动启动
-p 9200:9200 -p 9300:9300:外部端口是9200,内部端口是9300
-e "discovery.type=single-node":单节点模式
-e ES_JAVA_OPTS="-Xms512m -Xmx512m":java环境运行占用的最大内存空间是512m
--name:容器的名字
--net elastic:使用的网关是elastic
--cpuset-cpus="1" -m 1G:使用1个cpu占用1G内存
-d elasticsearch:7.17.0:容器使用的镜像叫elasticsearch:7.17.0
安装Kibana
1.拉取镜像
docker pull kibana:7.17.0
2.启动容器
docker run --name kibana --net elastic --link elasticsearch:elasticsearch -p 5601:5601 -d kibana:7.17.0
三、Elasticsearch常用操作
Elasticsearch是使用RESTful风格的http请求访问操作的,请求参数和返回值都是Json格式的,我们可以使用kibana发送http请求操作ES。
3.1 索引操作
创建无结构的索引
路径:ip地址:端口号/索引名
注:在kibana中所有的请求都会省略
ip地址:端口号
,之后的路径我们省略写ip地址:端口号。创建索引相当于是创建一张表。
在kibana中创建一个索引/student,Management—>DevTools
查看刚刚创建的索引,Management—>StackManagement—>IndexManagement
创建有结构的索引
POST /索引名/_mapping
{
"properties":{
"域名1":{
"type":域的类型,
"store":是否存储,
"index":是否创建索引,
"analyzer":分词器
},
"域名2":{
...
}
}
}
这里的域相当于是表中的字段/属性。
也可以在创建索引的时候直接为索引添加结构
PUT /索引名
{
"mappings":{
"properties":{
"域名1":{
"type":域的类型,
"store":是否单独存储,
"index":是否创建索引,
"analyzer":分词器
},
"域名2":{
...
}
}
}
}
删除索引
DELETE /索引名
3.2 文档操作
文档操作就是为索引添加、删除、修改、查询内容
新增/修改文档
POST /索引/_doc/[id值]
{
"field名":field值
}
注:id值不写时自动生成文档id,id和已有id重复时修改文档。这个id相当于是文档的主键,而域则是文档的属性。
POST /student/_doc/1
{
"id":1,
"name":"zj",
"age":10
}
根据id查询文档
GET /索引/_doc/id值
例如,查询student索引中1的数据:
GET /student/_doc/1
注:查询的数据也是json格式的。
删除文档
DELETE /索引/_doc/id值
根据文档id批量查询某个索引的文档
GET /索引/_mget
{
"docs":[
{"_id":id值},
{"_id":id值}
]
}
例如,查询student索引中id为1和id为2的索引
GET /student/_mget
{
"docs":[
{"_id":1},
{"_id":2}
]
}
查询索引中所有文档
相当于是MySQL中的记录。
GET /索引/_search
{
"query": {
"match_all": {}
}
}
例如,查询student索引中的全部文档
GET /student/_search
{
"query": {
"match_all": {}
}
}
修改文档部分字段
POST /索引/_doc/id值/_update
{
"doc":{
域名:值
}
}
例如,修改文档id为1的记录的id和age属性
POST /student/_doc/1/_update
{
"doc":{
"id":123,
"age":100
}
}
注:
Elasticsearch执行删除操作时,ES先标记文档为deleted状态,而不是直接物理删除。当ES存储空间不足或工作空闲时,才会执行物理删除操作。
Elasticsearch执行修改操作时,ES不会真的修改Document中的数据,而是标记ES中原有的文档为deleted状态,再创建一个新的文档来存储数据。
3.3 域的属性
index属性
该域是否创建倒排索引。只有值设置为true,才能根据该域的关键词查询文档。
// 根据关键词查询文档
GET /索引名/_search
{
"query":{
"term":{
搜索字段: 关键字
}
}
}
演示
#创建索引student1,设置index为true
PUT /student1
{
"mappings": {
"properties": {
"name":{
"type": "text",
"index": true
}
}
}
}
#创建索引student2,设置index为false
PUT /student2
{
"mappings": {
"properties": {
"name":{
"type": "text",
"index": false
}
}
}
}
#为student1索引添加一条文档
POST /student1/_doc/1
{
"name":"HELLO ES"
}
#为student2索引添加一条文档
POST /student2/_doc/1
{
"name":"HELLO ES"
}
#根据关键字查询数据
GET /student1/_search
{
"query":{
"term":{
"name":"HELLO"
}
}
}
GET /student2/_search
{
"query":{
"term":{
"name":"HELLO"
}
}
}
发现。建立索引的student1 能通过关键字搜索到,但是没有建立索引的student2不能通过关键字查询到。
type属性
域的类型
核心类型 | 具体类型 |
---|---|
字符串类型 | text |
整数类型 | long, integer, short, byte |
浮点类型 | double, float |
日期类型 | date |
布尔类型 | boolean |
数组类型 | array |
对象类型 | object |
不分词的字符串 | keyword |
不分词的字符串和字符串类型的区别是:字符串类型是能分词的,就是能将该字符串分为多个部分来检索,但是不分词的字符串不能拆分,只能作为一个整体来检索。
store域的属性
是否单独存储。如果设置为true,则该域能够单独查询出来,没有其他域的影响。
// 单独查询某个域:
GET /索引名/_search
{
"stored_fields": ["域名"]
}
演示
#创建索引student1,store设置为true
PUT /student1
{
"mappings": {
"properties": {
"name":{
"type": "text",
"index": true,
"store": true
},
"age":{
"type": "integer"
}
}
}
}
#创建索引student2,store设置为false
PUT /student2
{
"mappings": {
"properties": {
"name":{
"type": "text",
"index": true,
"store": false
},
"age":{
"type": "integer"
}
}
}
}
#为student1索引添加一条文档
POST /student1/_doc/1
{
"name":"HELLO ES",
"age":10
}
#为student2索引添加一条文档
POST /student2/_doc/1
{
"name":"HELLO ES",
"age":20
}
# 单独查询student1某个域:
GET /student1/_search
{
"stored_fields": ["name"]
}
# 单独查询student2某个域:
GET /student2/_search
{
"stored_fields": ["name"]
}
四、分词器
4.1 默认分词器
ES文档的数据拆分成一个个有完整含义的关键词,并将关键词与文档对应,这样就可以通过关键词查询文档。要想正确的分词,需要选择合适的分词器。
standard :Elasticsearch默认分词器,根据空格和标点符号对英文进行分词,会进行单词的大小写转换。
默认分词器是英文分词器,对中文的分词是一字一词。也就是说对中文的支持不好
GET /_analyze
{
"text":测试语句,
"analyzer":分词器
}
实例
4.2 IK分词器
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:
-
ik_smart:最少切分
-
ik_max_word:最细粒度划分
安装IK分词器
1.关闭es服务
#查找es进程
ps -ef | grep elastic
#杀死进程
kill -9 PID
2.使用文件上传工具将ik分词器上传至虚拟机
注:ik分词器的版本要和es版本保持一致。
3.解压ik分词器到elasticsearch的plugins目录下
unzip elasticsearch-analysis-ik-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-ik
4.启动ES服务
su es
#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/
#启动ES服务:
./elasticsearch -d
测试分词器效果
GET /_analyze
{
"text":"测试语句",
"analyzer":"ik_smart/ik_max_word"
}
IK分词器词典
IK分词器根据词典进行分词,词典文件在IK分词器的config目录中。
-
main.dic:IK中内置的词典。记录了IK统计的所有中文单词。
-
IKAnalyzer.cfg.xml:用于配置自定义词库。
/usr/local/elasticsearch1/plugins/analysis-ik/config
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext_dict.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">ext_stopwords.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
注,配置完成后需要重新启动es和kibana。
4.3 拼音分词器
拼音分词器可以将中文分成对应的全拼,全拼首字母等。
安装拼音分词器
1.关闭es服务
#查找es进程
ps -ef | grep elastic
#杀死进程
kill -9 PID
2.使用文件上传工具将拼音分词器上传至虚拟机
注:拼音分词器的版本要和es版本保持一致。
3.解压拼音分词器到elasticsearch的plugins目录下
unzip elasticsearch-analysis-pinyin-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-pinyin
4.启动ES服务
su es
#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/
#启动ES服务:
./elasticsearch
测试分词效果
4.4 自定义分词器
真实开发中我们往往需要对一段内容既进行文字分词,又进行拼音分词,此时我们需要自定义ik+pinyin分词器。
创建自定义分词器
1.在创建索引时自定义分词器
PUT /索引名
{
"settings" : {
"analysis" : {
"analyzer" : {
"ik_pinyin" : { //自定义分词器名
"tokenizer":"ik_max_word", // 基本分词器
"filter":"pinyin_filter" // 配置分词器过滤
}
},
"filter" : { // 分词器过滤时配置另一个分词器,相当于同时使用两个分词器
"pinyin_filter" : {
"type" : "pinyin", // 另一个分词器
// 拼音分词器的配置
"keep_separate_first_letter" : false, // 是否分词每个字的首字母
"keep_full_pinyin" : true, // 是否分词全拼
"keep_original" : true, // 是否保留原始输入
"remove_duplicated_term" : true // 是否删除重复项
}
}
}
},
"mappings":{
"properties":{
"域名1":{
"type":域的类型,
"store":是否单独存储,
"index":是否创建索引,
"analyzer":分词器
},
"域名2":{
...
}
}
}
}
测试自定义分词器
GET /索引/_analyze
{
"text": "你好ES",
"analyzer": "ik_pinyin"
}
五、Elasticsearch搜索文档
5.1 准备工作
Elasticsearch提供了全面的文档搜索方式,在学习前我们添加一些文档数据
#创建索引
PUT /students
{
"mappings": {
"properties": {
"id": {
"type": "integer",
"index": true
},
"name": {
"type": "text",
"store": true,
"index": true,
"analyzer": "ik_smart"
},
"info": {
"type": "text",
"store": true,
"index": true,
"analyzer": "ik_smart"
}
}
}
}
#为索引添加数据
POST /students/_doc/
{
"id":1,
"name":"Java",
"info":"I love Java"
}
POST /students/_doc/
{
"id":2,
"name":"美羊羊",
"info":"美羊羊是羊村最漂亮的人"
}
POST /students/_doc/
{
"id":3,
"name":"懒羊羊",
"info":"懒羊羊的成绩不是很好"
}
POST /students/_doc/
{
"id":4,
"name":"小灰灰",
"info":"小灰灰的年纪比较小"
}
POST /students/_doc/
{
"id":5,
"name":"沸羊羊",
"info":"沸羊羊喜欢美羊羊"
}
POST /students/_doc/
{
"id":6,
"name":"灰太狼",
"info":"灰太狼是小灰灰的父亲,每次都会说我一定会回来的"
}
搜索文档
GET /索引/_search
{
"query":{
搜索方式:搜索参数
}
}
5.2 搜索方式
match_all:查询所有文档
#查询全部文档
GET /students/_search
{
"query": {
"match_all": {}
}
}
match:全文检索。将查询条件分词后再进行搜索。
格式
{
"query":{
"match":{
搜索字段(域):搜索条件
}
}
}
实例
#查询info域中带🐏的文档
GET /students/_search
{
"query": {
"match": {
"info": "羊"
}
}
}
注:在搜索时关键词有可能会输入错误,ES搜索提供了自动纠错功能,即ES的模糊查询。使用match方式可以实现模糊查询。模糊查询对中文的支持效果一般,我们使用英文数据测试模糊查询。
格式
{ "query": { "match": { "域名": { "query": 搜索条件, "fuzziness": 最多错误字符数,不能超过2 } } } }
实例
GET /students/_search { "query": { "match": { "info": { "query": "lova", "fuzziness": 1 } } } }
range:范围搜索。对数字类型的字段进行范围搜索
格式
{
"query":{
"range":{
搜索字段:{
"gte":最小值,
"lte":最大值
}
}
}
}
gt/lt:大于/小于
gte/lte:大于等于/小于等于
实例
#查询id从1到3的文档
GET /students/_search
{
"query": {
"range": {
"id": {
"gte": 1,
"lte": 3
}
}
}
}
match_phrase:短语检索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配。
格式
{
"query":{
"match_phrase":{
搜索字段:搜索条件
}
}
}
实例
#查询info域是"I love Java"的文档
GET /students/_search
{
"query": {
"match_phrase": {
"info": "I love Java"
}
}
}
term/terms:单词/词组搜索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配
格式
{
"query":{
"term":{
搜索字段: 搜索条件
}
}
}
{
"query":{
"terms":{
搜索字段: [搜索条件1,搜索条件2]
}
}
}
实例
GET /students/_search
{
"query": {
"term": {
"info": {
"value": "成绩"
}
}
}
}
GET /students/_search
{
"query": {
"terms": {
"info": [
"成绩",
"羊羊"
]
}
}
}
5.3 复合搜索
格式
GET /索引/_search
{
"query": {
"bool": {
// 必须满足的条件 类似sql中的and
"must": [
搜索方式:搜索参数,
搜索方式:搜索参数
],
// 多个条件有任意一个满足即可类似or
"should": [
搜索方式:搜索参数,
搜索方式:搜索参数
],
// 必须不满足的条件
"must_not":[
搜索方式:搜索参数,
搜索方式:搜索参数
]
}
}
}
实例
#查询info域中没有成绩的和id范围不在1-3之间的文档
GET /students/_search
{
"query": {
"bool": {
"must_not": [
{
"term": {
"info": {
"value": "成绩"
}
}
},
{
"range": {
"id": {
"gte": 1,
"lte": 3
}
}
}
]
}
}
}
5.4 结果排序
ES中默认使用相关度分数实现排序,当然不想使用默认的排序方法可以通过搜索语法定制化排序。
格式
GET /索引/_search
{
"query": 搜索条件,
"sort": [
{
"字段1":{
"order":"asc"
}
},
{
"字段2":{
"order":"desc"
}
}
]
}
示例
#查询info域是"成绩"的文档,按照id降序排序
GET /students/_search
{
"query": {
"match": {
"info": "成绩"
}
},
"sort": [
{
"id": {
"order": "desc"
}
}
]
}
由于ES对text类型字段数据会做分词处理,使用哪一个单词做排序都是不合理的,所以 ES中默认不允许对text类型的字段做排序。如果需要使用字符串做结果排序,可以使用 keyword类型的字段作为排序依据,因为keyword字段不做分词处理。
5.5 分页查询
格式
GET /索引/_search
{
"query": 搜索条件,
"from": 起始下标,
"size": 查询记录数
}
示例
#查询info域是"成绩"的文档,并且查询第一页的1条数据
GET /students/_search
{
"query": {
"match": {
"info": "成绩"
}
},
"from": 0,
"size": 1
}
5.6 高亮查询
在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。
我们可以在关键字左右加入标签字符串,数据传入前端即可完成高亮显示,ES可以对查询出的内容中关键字部分进行标签和样式的设置。
格式
GET /索引/_search
{
"query":搜索条件,
"highlight":{
"fields": {
"高亮显示的字段名": {
// 返回高亮数据的最大长度
"fragment_size":100,
// 返回结果最多可以包含几段不连续的文字
"number_of_fragments":5
}
},
"pre_tags":["前缀"],
"post_tags":["后缀"]
}
}
示例
GET /students/_search
{
"query": {
"match": {
"info": "羊"
}
},
"highlight": {
"fields": {
"info": {
"fragment_size": 20,
"number_of_fragments": 5
}
}
}
}
5.7 SQL查询
在ES7之后,支持SQL语句查询文档
GET /_sql?format=txt
{
"query": SQL语句
}
开源版本的ES并不支持通过Java操作SQL进行查询,如果需要操作 SQL查询,则需要氪金(购买白金版)