ElasticSearch(后续简称 ES)在企业中的使用可以说是非常广泛了,那么 ES 到底是什么呢?我们学习 ES 能做到哪些事情呢?接下来我将用几篇文章详细聊一聊 ES。
ES 是一款高性能的分布式搜索引擎,当然里面出现的高性能、分布式已经是见怪不怪了,因此我们的重点是在搜索引擎上面。提到搜索引擎肯定不陌生,像百度、谷歌,它们都提供了自己的搜索引擎,我们每天都会在上面查找各种各样的信息。
因此:通过输入指定的关键字(关键词)来获取与之相关的信息,这个过程称之为搜索。并且搜索是不分场合的,除了百度、谷歌提供的搜索引擎之外,我们还可以在各种 App 上搜索,比如你在京东 app 上输入小提琴,那么点击确认之后会给你返回与小提琴有关的商品信息,这也是搜索。
而支持搜索的工具便是搜索引擎,它负责根据用户输入的关键字匹配出与之相关的信息,然后返回给用户,所以搜索引擎就是支持用户搜索的一个工具。
那么都有哪些工具支持搜索呢,其实说白了只要是支持字符串匹配的都可以,但能否满足不同的业务场景、以及保证高级别的搜索效率就两说了。
显然数据库是支持搜索的,毕竟它是专门用来存储数据的,其中也包含了数据分析。比如数据库中有一张表负责存储商品信息,我要查询里面所有名字包含 "洗发水" 的商品对应的 id,那么就可以这么做:
SELECT product_id FROM product
WHERE product_name LIKE '%洗发水%';
很明显这么做是正确的,但是要拿数据库来做搜索引擎则是不合适的。因为由于业务场景的不同,会带来两个问题:
因此用数据库实现搜索是不靠谱的,性能会非常差。
既然数据库不适合专门用于搜索,那什么工具适合呢?当然是我们要聊的 ES。只不过在具体介绍 ES 之前,我们需要先说一下什么是全文检索,以及 Lucene。
首先全文检索(或者说全文搜索)也是一种搜索,只不过它和数据库中使用 like 不同,全文检索使用了倒排索引的技术,它分为两步:
直接说的话不容易理解,我们举例说明,假设数据库中有一张表 game。
图片
假设要搜寻 type 字段包含 "校园" 或者 "爱情" 的记录,这个时候显然需要全表扫描。如果库里面有上千万条记录,那么就需要扫描上千万次,且每次扫描的范围都是全部的字符。此外这里指定了多个关键词,每个关键词都要模糊匹配一遍。
SELECT id, name FROM game
WHERE type LIKE '%校园%' OR type LIKE '%爱情%';
很明显这种 SQL 在 type 字段比较大的时候,其性能会非常差。
因此我们需要建立倒排索引,由于这里要基于 type 字段做查询,那么就对 type 字段的每一个文本进行拆分,得到多个关键词,然后再建立关键词到 id 的映射。
图片
通过对文本进行拆分,我们看到 type 字段包含 "亲情" 的有 id 为 1、2、3 的记录,包含 "夏日" 的有 id 为 2、3 的记录,所以每一个关键词都和包含该关键词的记录的 id 做了一个映射。
搜索的时候,同样也会对关键词、或者说要搜索内容进行拆分,得到更多的关键词,然后去匹配。假设我们想要根据 "校园爱情" 进行查找,那么会拆分成 "校园" 和 "爱情",然后直接就能得到 1、3、4、5,再根据 id 查找就可以了。
之前是逐行遍历去确定记录,现在是先根据关键词来确定 id,而构建的关键词到 id 的映射便是倒排索引。所以我们也可以发现,并不是说使用了 ES 之后就不需要数据库了。因为数据库表的字段可能非常多,我们不会对每一个字段都建立倒排索引,而是只针对那些需要通过关键词匹配的字段,将该字段的每一行都拆分成一个个的关键词,然后再把所有的关键词组合起来,建立它们到 id 之间的映射(倒排索引)。
因此在建立倒排索引后,行数反而会增多(如果大部分词都不一样的话),比如原来的数据有 100 万行,但是拆分出来的关键词有 200 万个,那么在建立倒排索引之后也会有 200 万行。但我们不可能真的搜索 200 万次,有可能我们搜索一次就找到对应的 id 了,因为在倒排索引中匹配的是关键词。
当然搜索一次是理想情况,也可能是十次、一百次,因此就需要设计一个好的搜索算法以及合适的数据组织结构来使得查询次数最小化,而算法如何设计显然不是我们需要操心的。并且在倒排索引中进行关键词匹配也和数据库的 like 不一样,前者只需要匹配单词即可,效率要比后者高很多。
以上便是全文检索以及倒排索引,还是很好理解的。然后再来说说 Lucene,其实 Lucene 就是一个 Jar 包,里面封装了很多建立倒排索引、以及搜索相关的算法。如果你使用 JAVA 语言的话,那么只需要引入这个 Jar 包,然后基于 Lucene 提供的 API 进行开发即可。通过 Lucene 我们就可以对已有的数据建立索引,Lucene 会在本地磁盘上面组织数据的索引结构。
了解了上面的内容之后,再来看 ES 就简单多了。我们说 Lucene 它封装了类似于搜索引擎的功能,但它是部署在单机上面的,如果数据量非常大、需要多机存储的话该怎么办呢。首先我们能想到的是把数据分散存储在多机上,然后每台机器各有一个 Lucene。
上面的做法看似解决了数据量的问题,但其实背后还有很多缺陷,比如:
显然上述这几点都是问题,都要在考虑的范围内。因为任何框架,如果需要多机部署,那么之间就应该具备相互通信的功能,相互协调,彼此作为一个整体、像单机一样对外提供服务。
所以 ES 就应运而生,它是基于 Lucene 实现的一个搜索引擎,同样使用 Java 语言编写。但是通过 ES 可以让全文搜索变得更加简单,因为 Lucene 需要你有比较深的检索相关的知识,比较复杂,而 ES 将这种复杂隐藏了起来,让用户可以通过 RESTful API 进行查询。
不仅如此,ES 不仅仅是为了检索方便而封装的 Lucene,它还解决了分布式的问题。因为 Lucene 只是一个库,如果想支持多机部署,那么你需要额外做很多的工作。而 ES 把这些全部解决了,比如:
因此什么是 ES 我们就说完了,说白了 ES 就是一个基于 Lucene 实现的搜索引擎,并且支持高可用、可伸缩、分布式。每个节点之上都部署一个 ES,多个节点共同对外提供服务,至于节点之间如何协调 ES 内部已经帮我们做好了,无需我们关心。
此外,虽然我们一直说 ES 是一个搜索引擎,但其实 ES 不仅可以用来搜索,还可以用来做数据分析。比如电商网站通过 ES 选取 "百褶裙" 销量最高的十个商家,新闻网站通过 ES 选取访问量最高的几篇文章等等,显然此时在获取数据的同时也伴随着数据分析。因此 ES 是一个分布式的搜索和数据分析引擎,能够进行全文检索、结构化检索、数据分析,以及对海量数据进行接近实时的处理。
当然相信很多人都听过 ELK,是用来搭建日志分析平台的。其中 E 就是这里的 ElasticSearch,L 是 Logstash,K 是 Kibana。
我们后面也会涉及到 ELK。下面总结一下 ES 的特点:
关于 ES,有几个专业术语,我们需要提前了解一下。
Cluster:集群,包含多个节点,当然也可以只包含一个节点。
Node:集群中的一个节点,每个节点都有一个名称(默认随机分配),节点的名称还是比较重要的,尤其是在执行运维管理操作的时候。
Index:索引,对应 MySQL 的数据库。
Type:类型,对应 MySQL 的表。
Document:文档,对应 MySQL 表中的一条记录,ES 的一个 Document 就类似于一条 JSON 数据。当然每条 JSON 数据可以有多个字段,然后字段在 ES 中被称为 Field,对应 MySQL 中的 Column。
shard:单台机器无法存储大量数据,ES 可以将一个索引中的数据切分为多个 shard,分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多的数据,让搜索和操作分布到多台服务器上去执行,提升吞吐性能。每个 shard 都是一个 Lucene Index,说白了就是 Index 的一个切片。
replica:任何一个服务器都有可能因为故障而宕机,造成 shard 丢失,因此可以为每一个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,此外多个 replica 还可以提升搜索操作的吞吐量和性能。
默认情况下,每个 Index 会被切分成 5 个 shard(建立索引时设置,设置后不能修改),被称为 primary shard。每个 primary shard 默认会有一个 replica shard(可以随时修改)。简单说的话,每个 Index 默认会被分成 5 个 shard,每个 shard 会有一个 replica。
当然啦,在 7.x 之前每个 Index 默认有 5 个 shard,但从 7.x 开始每个 Index 默认只有 1 个 shard。
因此在概念上,ES 和关系型数据库还是有一些共同之处的。
图片
需要注意的是,随着 ES 的发展,Type 的概念逐渐在弱化,因为全文索引的目的是建立关键词到 id 的映射,所以 Type 和全文索引的概念是冲突的。在 ES 6.x 中,已经规定一个 Index 下只能包含一个 Type,而到 ES 7.x 时,Type 的概念就被完全移除了。
下面来安装 ES,这里我使用的是云服务器,操作系统是 centos 7。由于 ES 是基于 Java 语言编写的,所以理论上在安装 ES 之前要先安装 JDK,但 ES 从 8.x 开始已经自带 JDK 了,因此我们就不需要再单独安装了。
然后去 ES 官网下载相应的安装包,这里我下载的是最新版 8.11.3,然后上传到服务器,并解压到 /opt 目录中。当然,如果你的节点上安装了 Docker,那么也可以基于容器启动。
图片
安装成功,然后看一下 ES 的主目录,是不是很熟悉呢。所有 Java 编写的大数据组件都是类似的,每个目录作用如下:
然后里面还有一个 jdk 目录,也就是 Java 环境,所以即使当前的系统没有安装,也是没关系的。
下面我们启动 ES,不过启动之前需要修改一下配置文件 config/elasticsearch.yml。
# ES 默认只允许本机访问,将其修改为 0.0.0.0
network.host: 0.0.0.0
# 端口默认为 9200
http.port: 9200
然后再创建用户,因为 ES 要求不能以 root 用户启动,因此我们要创建一个用户,并赋予它相关权限。
# 创建一个组 es
groupadd es
# 创建一个用户 es,并关联到组 es 中
useradd es -g es
# 赋予它 ES 目录的操作权限
chown es:es /opt/elasticsearch-8.11.3/ -R
下面切换用户,进入 ES 目录中,输入 bin/elasticsearch 启动 ES。如果你配置了环境变量,那么直接输入 elasticsearch 就行。
但如果你启动时发现报了下面这个错,那么说明空间不足。
图片
此时应该修改 config/jvm.options 配置文件。
# 设置 JVM 的初始内存为 1G,此值可以与 -Xmx 相同
# 避免每次垃圾回收完成后 JVM 重新分配内存
-Xms1g
# 设置 JVM 最大可用内存为 1G
-Xmx1g
然后再来启动 ES,默认是以前台启动的。但如果你发现输出一堆日志信息后,进程又退出了,并且最后输出了 ERROR: Elasticsearch exited unexpectedly, with exit code 78。那么你需要切换回 root 用户,然后执行如下命令:
sysctl -w vm.max_map_count=262144
然后再打开 /etc/security/limits.conf,并在里面追加如下内容。
es hard nofile 65536
es soft nofile 65536
这里的 es 就是刚才创建的用户,如果你创建的用户不叫 es,那么记得修改。
完事之后,再切换回 es 用户,再次启动,会发现启动成功。然后我们测试一下,浏览器中输入 http://ip:9200 ,看看能否返回内容。
然而很不幸,会发现无法访问,并且 ES 会输出如下内容:
图片
这是因为 ES 默认只允许通过 HTTPS 访问,如果想支持 HTTP,那么需要再次修改配置文件。
打开 config/elasticsearch.yml,在里面配置如下内容:
# 是否需要用户名密码,这里改成 false
xpack.security.enabled: false
# 是否开启 SSL 认证,这里将 enabled 给改成 false
# 否则只允许 https 请求,而 http 请求会被拒绝
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
然后重新启动 ES,此时再访问 ip:9200 就没有问题了,会返回如下内容。
图片
返回了一条 JSON,我们说 ES 的 Document(文档)就类似于一条 JSON,其字段就是 Field。然后里面的 name 字段表示节点名称,cluster_name 表示集群名称,这些都可以通过配置文件 elasticsearch.yml 进行修改,至于其它字段就见名知意了。
到目前为止,整个 ES 算是启动成功了,但目前是前台启动,我们需要改成后台启动。
bin/elasticsearch -d
只需要在结尾加一个 -d 即可。
到目前为止,我们就介绍了什么是 ES,以及它解决了什么问题。然后了解了它的核心概念,以及安装方式。