Elasticsearch学习参考
文章目录
一、Elasticsearch相关概述
1.什么是Elasticsearch
The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。
Elaticsearch,简称为 ES, ES 是一个开源的高扩展的分布式全文搜索引擎, 是整个 ElasticStack 技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。
2.数据结构
结构化数据:结构化的数据一般是指可以使用关系型数据库表示和存储,可以用二维表来逻辑表达实现的数据
非结构化数据 非结构化数据就是没有固定结构的数据。包括所有格式的办公文档、Word、PPT、文本、图片、各类报表、图像和音频/视频信息等等。对非结构化的数据,一般以二进制的形式直接整体进行存储。
半结构化数据:半结构化数据就是介于完全结构化数据和完全非结构化的数据之间的数据,它并无明确的数据模型结构,但包含相关标记定义可用来分隔语义元素以及对记录和字段进行分层。
3.全文搜索引擎
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
一般传统数据库,全文检索都实现的很没用,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。
基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的:
- 搜索的数据对象是大量的非结构化的文本数据。
- 文件记录量达到数十万或数百万个甚至更多。
- 支持大量基于交互式文本的查询。
- 需求非常灵活的全文搜索查询。
- 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
- 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎 。
全文搜索引擎指的是目前广泛应用的主流搜索引擎:它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。
二、Elasticsearch入门
1.Elasticsearch环境安装
官方网址 官方文档 Elasticsearch 7.8.0下载页面(其他高版本对jdk1.8存在版本不兼容/不支持)
下载完成后解压目录的目录结构为:
目录 | 含义 |
---|---|
bin | 可执行脚本目录 |
config | 配置目录 |
jdk | 内置 JDK 目录 |
lib | 类库 |
logs | 日志目录 |
modules | 模块目录 |
plugins | 插件目录 |
进入bin目录下,执行 elasticsearch.bat 即可启动es服务
默认情况下存在两个端口:9300 为 Elasticsearch 集群间组件的通信端口。
9200 为浏览器访问的 http协议 RESTful 端口。
使用浏览器,输入地址: http://localhost:9200,测试返回结果
{
"name" : "LAPTOP-EBP22FU7",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "V2fg2C8xTnCGUyyrq60RIA",
"version" : {
"number" : "7.8.0",
"build_flavor" : "default",
"build_type" : "zip",
"build_hash" : "757314695644ea9a1dc2fecd26d1a43856725e65",
"build_date" : "2020-06-14T19:35:50.234439Z",
"build_snapshot" : false,
"lucene_version" : "8.5.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
2.倒排索引
id | content |
---|---|
1001 | my name is zhang san |
1002 | my name is li si |
一般来说传统数据库的表数据结构是这种正排索引,我们可以通过id快去获取到这一条数据的所有信息,但是当我们出现模糊查询这种不确定的概念的时候,模糊查询不仅是效率和性能会差了许多,准确性也差强人意,比如:搜索Zhangsan,那么zhangsan就查询不出来
keyword | id |
---|---|
name | 1001, 1002 |
zhang | 1001 |
此时就需要换一种查询方式来让索引和数据进行关联,就比如上面这个例子:此时通过关键词就可以快速的查询到符合要求的内容id,此时关联另一张表的主键id就可以快速地查询出来符合要求的内容,这就是倒排索引,个人理解为 空间换时间
Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。 为了方便大家理解,我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比
ES 里的 Index 可以看做一个库,而 Types 相当于表, Documents 则相当于表的行。这里 Types 的概念已经被逐渐弱化, Elasticsearch 6.X 中,一个 index 下已经只能包含一个type, Elasticsearch 7.X 中, Type 的概念已经被删除了。
3.索引 - 索引
对比关系型数据库,创建索引就等同于创建数据库。
在 Postman 中,向 ES 服务器发 PUT(这里不允许使用POST请求) 请求 : http://127.0.0.1:9200/course course表示索引名称
请求后,服务器返回响应:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "course"
}
如果重复添加,会返回错误信息:
{
"error": {
"root_cause": [
{
"type": "resource_already_exists_exception",
"reason": "index [shopping/J0WlEhh4R7aDrfIc3AkwWQ] already exists",
"index_uuid": "J0WlEhh4R7aDrfIc3AkwWQ",
"index": "course"
}
],
"type": "resource_already_exists_exception",
"reason": "index [shopping/J0WlEhh4R7aDrfIc3AkwWQ] already exists",
"index_uuid": "J0WlEhh4R7aDrfIc3AkwWQ",
"index": "shopping"
},
"status": 400
}
4.索引 - 查询&删除
查询单个索引,发 GET 请求 : http://127.0.0.1:9200/course
{
"course": {//索引名
"aliases": {},//别名
"mappings": {},//映射
"settings": {//设置
"index": {//设置 - 索引
"creation_date": "1617861426847",//设置 - 索引 - 创建时间
"number_of_shards": "1",//设置 - 索引 - 主分片数量
"number_of_replicas": "1",//设置 - 索引 - 副分片数量
"uuid": "J0WlEhh4R7aDrfIc3AkwWQ",//设置 - 索引 - UUID
"version": {//设置 - 索引 - 版本
"created": "7080099"
},
"provided_name": "shopping"//设置 - 索引 - 主分片数量
}
}
}
}
查询所有索引,发 GET 请求 : http://127.0.0.1:9200/_cat/indices?v
_cat 表示查看的意思 indices 表示索引
所以整体含义就是查看当前 ES服务器中的所有索引,就好像 MySQL 中的 show tables 的感觉
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open course N1nLmoerTCyploJGEm1h9Q 1 1 0 0 208b 208b
表头 | 含义 |
---|---|
health | 当前服务器健康状态: green(集群完整) yellow(单点正常、集群不完整) red(单点不正常) |
status | 索引打开、关闭状态 |
index | 索引名 |
uuid | 索引统一编号 |
pri | 主分片数量 |
rep | 副本数量 |
docs.count | 可用文档数量 |
docs.deleted | 文档删除状态(逻辑删除) |
store.size | 主分片和副分片整体占空间大小 |
pri.store.size | 主分片占空间大小 |
删除索引,向 ES 服务器发 DELETE 请求 : http://127.0.0.1:9200/course
{
"acknowledged": true
}
5.文档 - 创建
这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式
在 Postman 中,向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/course/_doc,
_doc 表示文档数据,请求体JSON内容为:
{
"title":"小米手机",
"category":"小米",
"images":"https://www.baidu.com/",
"price":3999.00
}
返回结果:
{
"_index": "shopping",//索引
"_type": "_doc",//类型-文档
"_id": "ANQqsHgBaKNfVnMbhZYU",//唯一标识,可以类比为 MySQL 中的主键,随机生成也可以指定
"_version": 1,//版本
"result": "created",//结果,这里的 create 表示创建成功
"_shards": {//
"total": 2,//分片 - 总数
"successful": 1,
"failed":
},
"_seq_no": 0,
"_primary_term": 1
}
指定唯一标识id: http://127.0.0.1:9200/shopping/_doc/1
在 _doc 后面加上 /1001 即可指定id为1001,如果增加数据时明确数据主键,那么请求方式也可以为 PUT。
6.文档 - 查询
需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询
在 Postman 中,向 ES 服务器发 GET 请求 : http://127.0.0.1:9200/course/_doc/1001
{
"_index": "course",
"_type": "_doc",
"_id": "1001",
"_version": 1,
"_seq_no": 1,
"_primary_term": 1,
"found": true, //查询不到位false
"_source": { //查询的数据结果
"title": "小米手机",
"category": "小米",
"images": "https://www.baidu.com/",
"price": 3999.00
}
}
查看索引下所有数据,向 ES 服务器发 GET 请求 : http://127.0.0.1:9200/course/_search
{
"took": 40,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": { //响应的数据
"total": {
"value": 2, //总条数
"relation": "eq"
},
"max_score": 1.0,
"hits": [ //数据结果
{
"_index": "course",
"_type": "_doc",
"_id": "08T8BocBfJlfNxTywnaz",
"_score": 1.0,
"_source": {
"title": "小米手机",
"category": "小米",
"images": "https://www.baidu.com/",
"price": 3999.00
}
},
{
"_index": "course",
"_type": "_doc",
"_id": "1001",
"_score": 1.0,
"_source": {
"title": "小米手机",
"category": "小米",
"images": "https://www.baidu.com/",
"price": 3999.00
}
}
]
}
}
7.文档 - 修改
全量修改,和新增文档一样,输入相同的 URL 地址请求,如果请求体变化,会将原有的数据内容覆盖
在 Postman 中,附带 json 数据向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/course/_doc/1001
{
"title":"???",
"category":"000",
"images":"11111",
"price":0
}
响应结果
{
"_index": "course",
"_type": "_doc",
"_id": "1001",//被修改的id
"_version": 3,
"result": "updated",//修改
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
局部修改,修改数据时,也可以只修改某一给条数据的局部信息
在 Postman 中,附带部分 json 数据向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/course/_update/1001
只会修改对应键的值
{
"doc": {
"title":"小米手机",
"category":"小米"
}
}
8.文档 - 删除
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。
在 Postman 中,向 ES 服务器发 DELETE 请求 : http://127.0.0.1:9200/course/_doc/1001
9.文档 - 条件查 分页查 排序
条件查寻
URL带参查询
查找category为小米的文档,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search?q=category:小米
这种URL带参数形式查询,这很容易让不善者心怀恶意(mysql中的字符串拼接),或者参数值出现中文会出现乱码情况。为了避免这些情况,我们可用使用带JSON请求体请求进行查询。
请求体带参查询
查找category为小米的文档,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search
附带JSON体如下:
{
"query":{
"match":{
"category":"小米"
}
}
}
查询指定字段
查询指定字段,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search
附带JSON体如下:
{
"query":{
"match_all":{}
},
"_source":["title"]
}
分页查询
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search
附带JSON体如下:
{
"query":{
"match_all":{}
},
"from":0,
"size":2
}
排序
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search
附带JSON体如下:
{
"query":{
"match_all":{}
},
"sort":{
"price":{
"order":"desc"
}
}
}
10.文档 - 多条件查询 范围查询
多条件查询
找出苹果牌子,价格为99999元的。( must相当于数据库的&&, should相当于数据库的|| )
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search
附带JSON体如下:
{
"query":{
"bool":{
"must":[{
"match":{
"category":"苹果"
}
},{
"match":{
"price":99999.00
}
}]
}
}
}
范围查询
小米和华为的牌子,价格大于8888元的手机。
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search
附带JSON体如下:
{
"query":{
"bool":{
"filter":{
"range":{
"price":{
"gt":8888
}
}
}
}
}
}
11.文档 - 全文检索 & 高亮查询
全文检索
这功能像搜索引擎那样,如品牌输入“小华”,返回结果带回品牌有 小米 和 华为 的。
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/course/_search
附带JSON体如下:
{
"query":{
"match":{
"category" : "小华"
}
}
}
高亮查询
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
附带JSON体如下:
{
"query":{
"match_phrase":{
"category" : "为"
}
},
"highlight":{
"fields":{
"category":{}//<----高亮这字段
}
}
}
三、Java中操作Elasticsearch
1.准备环境
elasticsearch 使用到的依赖
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.8.0</version>
</dependency>
<!-- elasticsearch 的客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.8.0</version>
</dependency>
四、Elasticsearch环境
1.环境 - 单机 & 集群概念
单台 Elasticsearch 服务器提供服务,往往都有最大的负载能力,超过这个阈值,服务器性能就会大大降低甚至不可用,所以生产环境,一般都是运行在指定服务器集群中。
除了负载能力,单点服务器也存在其他问题:
- 单台机器存储容量有限
- 单服务器容易出现单点故障,无法实现高可用
- 单服务的并发处理能力有限
配置服务器集群时,集群中节点数量没有限制,大于等于 2 个节点就可以看做是集群了。一
般出于高性能及高可用方面来考虑集群中节点数量都是 3 个以上
总之,集群能提高性能,增加容错。
· 集群 Cluster
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个的数据,并一起提供索引和搜索功能。一个 Elasticsearch 集群有一个唯一的名字标识,这个名字默认就是”elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
· 节点 Node
集群中包含很多服务器, 一个节点就是其中的一个服务器。 作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于 Elasticsearch 集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何 Elasticsearch 节点,这时启动一个节点会默认创建并加入一个叫做“elasticsearch”的集群。
2.环境 - Windows集群部署
启动集群
复制三个 elasticsearch 出来
修改集群文件目录中每个节点的 config/elasticsearch.yml 配置文件,主要是端口和节点名称不同,其他配置一样
如果加入不了集群,删除目录中的data和log文件夹
node1
#节点 1 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1001
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 9201
#tcp 监听端口
transport.tcp.port: 9301
#discovery.seed_hosts: ["localhost:9301", "localhost:9302","localhost:9303"]
#discovery.zen.fd.ping_timeout: 1m
#discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
node2
#节点 2 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1002
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 9202
#tcp 监听端口
transport.tcp.port: 9302
discovery.seed_hosts: ["localhost:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
node3
#节点 3 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1003
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 9203
#tcp 监听端口
transport.tcp.port: 9303
#候选主节点的地址,在开启服务后可以被选为主节点
discovery.seed_hosts: ["localhost:9301", "localhost:9302"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
启动集群依次启动各个节点即可
发送GET请求:http://127.0.0.1:9201/_cluster/health 查看集群状态
返回的数据如下:
{
"cluster_name": "my-application",
"status": "green",
"timed_out": false,
"number_of_nodes": 3,
"number_of_data_nodes": 3,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100.0
}
status字段指示着当前集群在总体上是否工作正常。它的三种颜色含义如下:
- green:所有的主分片和副本分片都正常运行。
- yellow:所有的主分片都正常运行,但不是所有的副本分片都正常运行。
- red:有主分片没能正常运行。
测试集群
在 node1节点 里面发送PUT请求增加一个索引:http://127.0.0.1:9201/user
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "user"
}
在 node2节点 里面发送GET请求获取索引:http://127.0.0.1:9203/user
{
"user": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1679485088101",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "4gyoGy_hTNODRRpkqI9l-g",
"version": {
"created": "7080099"
},
"provided_name": "user"
}
}
}
}
以上可以发现我们成功的从 node2 中获取到了 node1 中添加的索引,这就是集群的能力
3.环境 - Linux单节点部署
一、下载软件
二、解压软件
# 解压缩
tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz -C /opt/module
# 改名
mv elasticsearch-7.8.0 es
三、创建用户
因为安全问题, Elasticsearch 不允许 root 用户直接运行,所以要创建新用户,在 root 用户中创建新用户。
useradd es #新增 es 用户
passwd es #为 es 用户设置密码
userdel -r es #如果错了,可以删除再加
chown -R es:es /opt/module/es #文件夹所有者
四、修改配置文件
修改/opt/module/es/config/elasticsearch.yml文件。
# 加入如下配置
cluster.name: elasticsearch
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
cluster.initial_master_nodes: ["node-1"]
修改/etc/security/limits.conf
# 在文件末尾中增加下面内容
# 每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
修改/etc/security/limits.d/20-nproc.conf
# 在文件末尾中增加下面内容
# 每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
# 操作系统级别对每个用户创建的进程数的限制
* hard nproc 4096
# 注: * 带表 Linux 所有用户名称
修改/etc/sysctl.conf
# 在文件中增加下面内容
# 一个进程可以拥有的 VMA(虚拟内存区域)的数量,默认值为 65536
vm.max_map_count=655360
重新加载
sysctl -p
五、启动软件
使用 ES 用户启动
cd /opt/module/es/
#启动
bin/elasticsearch
#后台启动
bin/elasticsearch -d
4.环境 - Linux集群部署
五、倒排索引
传统的数据库每个字段存储单个值,但这对全文检索并不够。文本字段中的每个单词需要被搜索,对数据库意味着需要单个字段有索引多值的能力。最好的支持是一个字段多个值需求的数据结构是倒排索引。
1.倒排索引原理
Elasticsearch使用一种称为倒排索引的结构,它适用于快速的全文搜索。
见其名,知其意,有倒排索引,肯定会对应有正向索引。正向索引(forward index),反向索引(inverted index)更熟悉的名字是倒排索引。
所谓的正向索引,就是搜索引擎会将待搜索的文件都对应一个文件ID,搜索时将这个ID和搜索关键字进行对应,形成K-V对,然后对关键字进行统计计数。(就是mysql中拿着id去找对应的一条数据)
但是互联网上收录在搜索引擎中的文档的数目是个天文数字,这样的索引结构根本无法满足实时返回排名结果的要求。所以,搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。(有个关键词,每个关键词都对应着与之有关的文档ID)
2.倒排索引的例子
一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。例如,假设我们有两个文档,每个文档的content域包含如下内容:
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
为了创建倒排索引,我们首先将每个文档的content域拆分成单独的词(我们称它为词条或tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:
现在,如果我们想搜索 quick
brown
,我们只需要查找包含每个词条的文档:
两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单相似性算法,那么我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。
但是,我们目前的倒排索引有一些问题:
Quick
和quick
以独立的词条出现,然而用户可能认为它们是相同的词。fox
和foxes
非常相似,就像dog
和dogs
;他们有相同的词根。jumped
和leap
,尽管没有相同的词根,但他们的意思很相近。他们是同义词。
使用前面的索引搜索+Quick
+fox
不会得到任何匹配文档。(记住,+前缀表明这个词必须存在)。
只有同时出现Quick
和fox
的文档才满足这个查询条件,但是第一个文档包含quick
fox
,第二个文档包含Quick
foxes
。
我们的用户可以合理的期望两个文档与查询匹配。我们可以做的更好。
如果我们将词条规范为标准模式,那么我们可以找到与用户搜索的词条不完全一致,但具有足够相关性的文档。例如:
Quick
可以小写化为quick
。foxes
可以词干提取变为词根的格式为fox
。类似的,dogs
可以为提取为dog
。jumped
和leap
是同义词,可以索引为相同的单词jump
。
现在索引看上去像这样:
这还远远不够。我们搜索+Quick
+fox
仍然会失败,因为在我们的索引中,已经没有Quick
了。但是,如果我们对搜索的字符串使用与content域相同的标准化规则,会变成查询+quick
+fox
,这样两个文档都会匹配!分词和标准化的过程称为分析,这非常重要。你只能搜索在索引中出现的词条,所以索引文本和查询字符串必须标准化为相同的格式。
3.不可改变的倒排索引
早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。
倒排索引被写入磁盘后是不可改变的:它永远不会修改。
- 不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
- 一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
- 其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
- 写入单个大的倒排索引允许数据被压缩,减少磁盘IO和需要被缓存到内存的索引的使用量。
当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。
4.动态更新索引
用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。
Elasticsearch基于Lucene,这个java库引入了按段搜索的概念。每一段本身都是一个倒排索引,但索引在 Lucene 中除表示所有段的集合外,还增加了提交点的概念—一个列出了所有已知段的文件。
六、文档分析
分析包含下面的过程:
- 将一块文本分成适合于倒排索引的独立的词条。
- 将这些词条统一化为标准格式以提高它们的“可搜索性”,或者recall。
分析器执行上面的工作。分析器实际上是将三个功能封装到了一个包里:
- 字符过滤器:首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉 HTML,或者将 & 转化成 and。
- 分词器:其次,字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
- Token 过滤器:最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像jump和leap这种同义词)
1.内置分析器
以下是es中内置的几种分析器,以下结果均是分析 “Set the shape to semi-transparent by calling set_trans(5)” 得出:
- 标准分析器
Elasticsearch 默认使用的分析器。它是分析各种语言文本最常用的选择。它根据Unicode 联盟定义的单词边界划分文本。删除绝大部分标点。最后,将词条小写。它会产生:
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
- 简单分析器
简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生:
set, the, shape, to, semi, transparent, by, calling, set, trans
- 空格分析器
空格分析器在空格的地方划分文本。它会产生:
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
- 语言分析器
特定语言分析器可用于很多语言。它们可以考虑指定语言的特点。例如,英语分析器附带了一组英语无用词(常用单词,例如and或者the ,它们对相关性没有多少影响),它们会被删除。由于理解英语语法的规则,这个分词器可以提取英语单词的词干。
英语分词器会产生下面的词条,此时 transparent、calling和 set_trans已经变为词根格式。:
set, shape, semi, transpar, call, set_tran, 5
2.测试分析器
有些时候很难理解分词的过程和实际被存储到索引中的词条,特别是刚接触Elasticsearch。为了理解发生了什么,你可以使用analyze API来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本。
发送GET请求到 http://localhost:9200/_analyze
附带JSON体如下:
{
"analyzer": "standard",//standard 标准分析器
"text": "Text to analyze"//被分析的文本
}
结果中每个元素代表一个单独的词条:
- oken是实际存储到索引中的词条。
- start_ offset 和end_ offset指明字符在原始字符串中的位置。
- position指明词条在原始文本中出现的位置。
{
"tokens": [
{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 3
}
]
}
3.IK分词器
使用上面的分析器分析中文就会发现,中文文本的每一个汉字都会被拆解出来,这样肯定是不符合要求的
因此中文的分词就要使用到 ES 对应版本的中文分词器 IK分词器
将解压后的后的文件夹放入 ES 根目录下的 plugins 目录下,重启 ES 即可使用。
发送GET请求到 http://localhost:9200/_analyze
此时这里有个两个可选参数
- ik_max_word:会将文本做最细粒度的拆分。
- ik_smart:会将文本做最粗粒度的拆分。
附带JSON体如下:
{
"text":"测试单词",
"analyzer":"ik_max_word"
}
此时响应的结果为:
{
"tokens": [
{
"token": "测试",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "单词",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
}
]
}
4.拓展词汇
如果作用域比较特殊的场景,比如游戏查询,此时可能会出现一些专有名词,这些词汇明显不存在什么联系,此时即使使用IK分词,也是会把每一个字给拆出来,此时我们就需要进行扩展词汇:
- 进入 ES 根目录中的 plugins 文件夹下的 ik 文件夹,进入 config 目录,创建 custom.dic文件,写入“测试文本”。
- 同时打开 IKAnalyzer.cfg.xml 文件,将新建的 custom.dic 配置其中。
- 重启 ES 服务器 。
<?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">custom.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
此时再次进行测试:
//http://localhost:9200/_analyze
{
"text":"测试文本",
"analyzer":"ik_max_word"
}
相应的结果为:
{
"tokens": [
{
"token": "测试文本",
"start_offset": 0,
"end_offset": 5,
"type": "CN_WORD",
"position": 0
}
]
}
5.自定义分析器
虽然Elasticsearch带有一些现成的分析器,然而在分析器上Elasticsearch真正的强大之处在于,你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单元过滤器来创建自定义的分析器。在分析与分析器我们说过,一个分析器就是在一个包里面组合了三种函数的一个包装器,三种函数按照顺序被执行:
字符过滤器
字符过滤器用来整理一个尚未被分词的字符串。例如,如果我们的文本是HTML格式的,它会包含像<p>
或者<div>
这样的HTML标签,这些标签是我们不想索引的。我们可以使用html清除字符过滤器来移除掉所有的HTML标签,并且像把Á
转换为相对应的Unicode字符Á 这样,转换HTML实体。一个分析器可能有0个或者多个字符过滤器。
分词器
一个分析器必须有一个唯一的分词器。分词器把字符串分解成单个词条或者词汇单元。标准分析器里使用的标准分词器把一个字符串根据单词边界分解成单个词条,并且移除掉大部分的标点符号,然而还有其他不同行为的分词器存在。
例如,关键词分词器完整地输出接收到的同样的字符串,并不做任何分词。空格分词器只根据空格分割文本。正则分词器根据匹配正则表达式来分割文本。
词单元过滤器
经过分词,作为结果的词单元流会按照指定的顺序通过指定的词单元过滤器。词单元过滤器可以修改、添加或者移除词单元。我们已经提到过lowercase和stop词过滤器,但是在Elasticsearch 里面还有很多可供选择的词单元过滤器。词干过滤器把单词遏制为词干。ascii_folding过滤器移除变音符,把一个像"très”这样的词转换为“tres”。
ngram和 edge_ngram词单元过滤器可以产生适合用于部分匹配或者自动补全的词单元。
6.自定义分析器例子
向 ES 服务器发 GET请求 : http://localhost:9200/my_index
附带JSON体如下:
{
"settings": {
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [
"&=> and "
]
}
},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": [
"the",
"a"
]
}
},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": [
"html_strip",
"&_to_and"
],
"tokenizer": "standard",
"filter": [
"lowercase",
"my_stopwords"
]
}
}
}
}
}
索引被创建以后,使用 analyze API 来 测试这个新的分析器:
向 ES 服务器发 GET请求 :http://127.0.0.1:9200/my_index/_analyze
附带JSON体如下:
{
"text":"The quick & brown fox",
"analyzer": "my_analyzer"
}
响应结果为:
{
"tokens": [
{
"token": "quick",
"start_offset": 4,
"end_offset": 9,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "and",
"start_offset": 10,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "fox",
"start_offset": 18,
"end_offset": 21,
"type": "<ALPHANUM>",
"position": 4
}
]
}
七、Kibana
Kibana是一个免费且开放的用户界面,能够让你对Elasticsearch 数据进行可视化,并让你在Elastic Stack 中进行导航。你可以进行各种操作,从跟踪查询负载,到理解请求如何流经你的整个应用,都能轻松完成。
一、解压缩下载的 zip 文件。
二、修改 config/kibana.yml 文件。
# 默认端口
server.port: 5601
# ES 服务器的地址
elasticsearch.hosts: ["http://localhost:9200"]
# 索引名
kibana.index: ".kibana"
# 支持中文
i18n.locale: "zh-CN"
三、Windows 环境下执行 bin/kibana.bat 文件。(首次启动有点耗时)
四、通过浏览器访问:http://localhost:5601