关系型数据库搜索出现的问题
要实现类似百度的复杂搜索,或者京东的商品搜索,如果使用传统的数据库存储数据,那么会存在一系列的问题:
- 性能瓶颈:当数据量越来越大时,数据库搜索的性能会有明显下降。虽然可以通过分库分表来解决存储问题,但是性能问题并不能彻底解决,而且系统复杂度会提高、可用性下降。
- 复杂业务:百度或京东的搜索往往需要复杂的查询功能,例如:拼音搜索、错字的模糊搜索等。这些功能用数据库搜索难以实现,或者实现复杂度较高
- 并发能力:数据库是磁盘存储,虽然也有缓存方案,但是并不实用。因此数据库的读写并发能力较差,难以应对高并发场景
但是,并不是说数据库就一无是处。在一些对业务有强数据一致性需求,事物需求的情况下,数据库是不可替代的。
只是在海量数据的搜索方面,需要有新的技术来解决,就是我们今天要学习的倒排索引技术。
什么是倒排索引?倒排索引的原理:
倒排索引的数据存储方式与数据库类似,但检索方式不同。
全文检索主要是通过倒排索引的方式实现,倒排索引主要是分成两大步骤完成:
- 数据按照规则(分词)处理完成后存储到索引库
- 用户输入关键词按照规则处理完成后在索引库中检索
首先,倒排索引需要把文档数据逐个编号(从0递增),存储到文档表中。并且给每一个编号创建索引,这样根据编号检索文档的速度会非常快。
然后,对文档中的数据按照算法做分词,得到一个个的词条,记录词条和词条出现的文档的编号、位置、频率信息
然后给词条创建索引,这样根据词条匹配和检索的速度就非常快。
检索数据过程
1)用户输入条件"谷歌创始人跳槽"
进行搜索。
2)对用户输入内容分词,得到词条:谷歌
、创始人
、跳槽
。
3)拿着词条到词条列表中查找,可以得到包含词条的文档编号:0、1、2、3、4。
4)拿着词条的编号到文档列表中查找具体文档。
虽然搜索会在两张表进行,但是每次都是根据索引查找,因此速度比传统搜索时的全表扫描速度要快的多。
其实用白话来说就是,将一段话按照词语的方式打散,然后一个词一个词的放入倒排索引库。查找的时候会根据词条查找出对应的文档编号,然后将符合的文档数据筛选出来。
ElasticSearch:
Elasticsearch是一个基于Lucene的搜索web服务,对外提供了一系列的Rest风格的API接口。因此任何语言的客户端都可以通过发送Http请求来实现ElasticSearch的操作。
ES的主要优势特点如下:
- 速度快:Elasticsearch 很快,快到不可思议。我们通过有限状态转换器实现了用于全文检索的倒排索引,实现了用于存储数值数据和地理位置数据的 BKD 树,以及用于分析的列存储。而且由于每个数据都被编入了索引,因此您再也不用因为某些数据没有索引而烦心。您可以用快到令人惊叹的速度使用和访问您的所有数据。实现近实时搜索,海量数据更新在Elasticsearch中几乎是完全同步的。
- 扩展性高:可以在笔记本电脑上运行,也可以在承载了 PB 级数据的成百上千台服务器上运行。原型环境和生产环境可无缝切换;无论 Elasticsearch 是在一个节点上运行,还是在一个包含 300 个节点的集群上运行,您都能够以相同的方式与 Elasticsearch 进行通信。它能够水平扩展,每秒钟可处理海量事件,同时能够自动管理索引和查询在集群中的分布方式,以实现极其流畅的操作。天生的分布式设计,很容易搭建大型的分布式集群(solr使用Zookeeper作为注册中心来实现分布式集群)
- 强大的查询和分析:通过 Elasticsearch,您能够执行及合并多种类型的搜索(结构化数据、非结构化数据、地理位置、指标),搜索方式随心而变。先从一个简单的问题出发,试试看能够从中发现些什么。找到与查询最匹配的 10 个文档是一回事。但如果面对的是十亿行日志,又该如何解读呢?Elasticsearch 聚合让您能够从大处着眼,探索数据的趋势和模式。如全文检索,同义词处理,相关度排名,复杂数据分析。
- 操作简单:客户端API支持Restful风格,简单容易上手。
ElasticSearch及相关组件在虚拟机中安装
安装命令:
docker run -id --name elasticsearch \
-e "cluster.name=elastic-cluster" \
-e "http.host=0.0.0.0" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-e http.cors.enabled=true \
-e http.cors.allow-origin="*" \
-e http.cors.allow-headers=X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization \
-e http.cors.allow-credentials=true \
-v es-data:/usr/share/elasticsearch/data \
-v es-logs:/usr/share/elasticsearch/logs \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--restart=always \
--hostname elasticsearch \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.4.2
命令解释:
-e "cluster.name=es-docker-cluster"
:设置集群名称-e "http.host=0.0.0.0"
:监听的地址,可以外网访问-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:内存大小-e "discovery.type=single-node"
:非集群模式-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定es的数据目录-v es-logs:/usr/share/elasticsearch/logs
:挂载逻辑卷,绑定es的日志目录-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定es的插件目录--privileged
:授予逻辑卷访问权-p 9200:9200
:端口映射配置
如果已经启动的容器项目,则使用update更新:
docker update --restart=always 容器名/ID
防火墙配置(可选):
# 开放端口 9200 9300 5601
firewall-cmd --zone=public --add-port=9200/tcp --permanent
# 重启防火墙
firewall-cmd --reload
# 查看放行端口
firewall-cmd --list-ports
Kibana:
Kibana是一个基于Node.js的Elasticsearch索引库数据统计工具,可以利用Elasticsearch的聚合功能,生成各种图表,如柱形图,线状图,饼图等。
而且还提供了操作Elasticsearch索引数据的控制台,并且提供了一定的API提示。
安装Kibana
docker run -di --name kibana -p 5601:5601 -e ELASTICSEARCH_HOSTS=http://YourIP:9200 kibana:7.4.2
安装完毕之后
输入地址访问:http://YourIP:5601
分词器
ElasticSearch 内置分词器
- Standard Analyzer - 默认分词器,按词切分,小写处理
- Simple Analyzer - 按照非字母切分(符号被过滤), 小写处理
- Stop Analyzer - 小写处理,停用词过滤(the,a,is)
- Whitespace Analyzer - 按照空格切分,不转小写
- Keyword Analyzer - 不分词,直接将输入当作输出
- Patter Analyzer - 正则表达式,默认\W+(非字符分割)
- Language - 提供了30多种常见语言的分词器
但是默认的分词器并不能处理中文,所以一般会用第三方分词器
IK分词器
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包,是一个基于Maven构建的项目,具有60万字/秒的高速处理能力,支持用户词典扩展定义。
IK分词器的 地址:https://github.com/medcl/elasticsearch-analysis-ik, 安装非常简单。
IK分词器可以用ik_max_word
和ik_smart
两种方式,分词粒度不同。
安装IK分词器
1、进入/var/lib/docker/volumes/es-plugins/_data/
cd /var/lib/docker/volumes/es-plugins/_data/
2、新建文件目录 ik 并且进入
mkdir ik
cd ik
3、解压elasticsearch-analysis-ik-7.4.2.zip
yum -y install unzip
unzip elasticsearch-analysis-ik-7.4.2.zip
4、重启容器
docker restart elasticsearch
5、重启 Kibana
docker restart kibana
测试后发现后
-
ik_smart
:最小切分 -
ik_max_word
:最细切分
扩展词词典
1)打开IK分词器config目录:
2)IKAnalyzer.cfg.xml配置文件内容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
3)新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改,里面放入自定义的扩展词
4)重启elasticsearch
docker restart elasticsearch
docker restart kibana
# 查看 日志
docker logs -f elasticsearch
注意当前文件的编码必须是 UTF-8 格式
停用词词典
1)IKAnalyzer.cfg.xml配置文件内容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典-->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典-->
<entry key="ext_stopwords">stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
3)在 stopword.dic 添加停用词
以下步骤同上
Elasticsearch对比MySQL
因此,我们对ES的操作,就是对索引库、类型映射、文档数据的操作:
- 索引库操作:主要包含创建索引库、查询索引库、删除索引库等
- 类型映射操作:主要是创建类型映射、查看类型映射
- 文档操作:文档的新增、修改、删除、查询
Elasticsearch的索引库操作
创建索引库
创建索引库的请求格式:
-
请求方式:PUT
-
请求路径:/索引库名
-
请求参数:格式:
查询索引库
-
请求方式:GET
-
请求路径:/索引库名
-
请求参数:无
格式:
GET /索引库名
删除索引库
-
请求方式:DELETE
-
请求路径:/索引库名
-
请求参数:无
格式:
DELETE /索引库名
mapping映射
MySQL中有表,并且表中有对字段的约束,对应到elasticsearch中就是类型映射mapping
.
elasticsearch字段的映射属性该怎么选,除了字段名称外,我们一般要考虑这样几个问题:
-
1)数据的类型是什么?
- 这个比较简单,根据字段的含义即可知道,可以通过
type
属性来指定
- 这个比较简单,根据字段的含义即可知道,可以通过
-
2)数据是否参与搜索? 是否索引
- 参与搜索的字段将来需要创建倒排索引,作为搜索字段。可以通过
index
属性来指定是否参与搜索,默认为true,也就是每个字段都参与搜索
- 参与搜索的字段将来需要创建倒排索引,作为搜索字段。可以通过
-
3)数据是否需要分词? 是否分词
-
一个字段的内容如果不是一个不可分割的整体,例如国家,一般都需要分词存储。
-
如果是身份证号则不需要分词
-
如果分词的话用什么分词器?
-
分词器类型很多,中文一般选择IK分词器
-
指定分词器类型可以通过
analyzer
属性指定
注意:在同一个域上 分词和搜索时 建议使用同一个分词器
-
-
-
-
4)数据是否存储到es库中 是否存储
- 是否存储取决于用户是否需要将当前字段展示给用户查看
**elasticsearch字段的映射属性是重点,直接决定了你的字段创立是否正确
数据类型
-
string类型,又分两种:
- text:可分词,存储到elasticsearch时会根据分词器分成多个词条
- keyword:不可分词,数据会完整的作为一个词条
-
Numerical:数值类型,分两类
- 基本数据类型:long、interger、short、byte、double、float、half_float
- 浮点数的高精度类型:scaled_float
- 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
-
Date:日期类型
-
Object:对象,对象不便于搜索。因此ES会把对象数据扁平化处理再存储。
创建类型映射(索引库已存在)
- 语法
请求方式依然是PUT
PUT /索引库名/_mapping
{
"properties": {
"字段名1": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
"字段名2": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
...
}
}
类型名称:就是前面将的type的概念,类似于数据库中的表
字段名:任意填写,下面指定许多属性,例如:
- type:类型,可以是text、long、short、date、integer、object等
- index:是否参与搜索,默认为true
- analyzer:分词器
创建类型映射(索引库不存在)
PUT /索引库名
{
"mappings":{
"properties": {
"字段名1": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
"字段名2": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
...
}
}
}
查看映射关系
- 语法:
GET /索引库名/_mapping
Document文档的操作
新增文档
- 语法:
POST /{索引库名}/_doc
{
"key":"value"
}
- 示例:
# 新增文档数据
POST /heima/_doc
{
"title":"小米手机",
"images":"http://image.leyou.com/12479122.jpg",
"price":2699.00
}
- 响应:
{
"_index" : "xxx",
"_type" : "_doc",
"_id" : "rGFGbm8BR8Fh6kyTbuq8",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
结果解释:
_index
:新增到了哪一个索引库。_id
:这条文档数据的唯一标示
,文档的增删改查都依赖这个id作为唯一标示。此处是由ES随即生成的,我们也可以指定使用某个IDresult
:执行结果,可以看到结果显示为:created
,说明文档创建成功。
新增并指定id
- 语法:
POST /{索引库名}/_doc/{id}
{
"key":"value"
}
查询文档,根据ID查询
语法:
GET /{索引库名称}/_doc/{id}
词条查询
词条查询不会分析查询条件,只有当词条和查询字符串完全匹配时才匹配搜索。
语法:
GET /{索引库}/_search
{
"query": {
"term": {
"field字段": {
"value": "查询的关键词"
}
}
}
}
全文查询
全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集
语法:
GET /{索引库}/_search
{
"query": {
"match": {
"查询的字段":"查询关键词"
}
}
}
查询所有文档
语法:
# 查询所有
GET /heima/_search
{
"query": {
"match_all": {}
}
}
修改文档
把刚才新增的请求方式改为PUT,就是修改了。不过修改必须指定id,
- id对应文档存在,则修改
- id对应文档不存在,则新增
删除文档
删除使用DELETE请求,同样,需要根据id进行删除:
- 语法:
DELETE /索引库名/类型名/id值