初识 ElasticSearch
ElasticSearch(后续简称 ES)在企业中的使用也是比较广泛的,那么 ES 到底是什么呢?我们学习 ES 能做到哪些事情呢?下面来了解一下。
ES 是一款高性能的分布式搜索引擎,当然里面出现的高性能、分布式已经是见怪不怪了,因此我们的重点是在 "搜索引擎" 上面。提到搜索引擎肯定不陌生,像百度、谷歌,它们都提供了自己的搜索引擎,我们每天都会在上面查找各种各样的信息。因此:通过输入指定的关键字(关键词)来获取与之相关的信息,这个过程称之为 "搜索"。并且搜索是不分场合的,除了百度、谷歌提供的搜索引擎之外,我们还可以在各种 app 上搜索,比如你在京东 app 上输入小提琴,那么点击确认之后会给你返回与小提琴有关的商品信息,这也是搜索;而支持搜索的工具便是搜索引擎,它负责根据用户输入的关键字匹配出与之相关的信息,然后返回给用户,所以搜索引擎就是支持用户搜索的一个工具。
而支持搜索的工具有很多种,其实说白了只要是支持字符串匹配的都可以,但能否满足不同的业务场景、以及保证高级别的搜索效率就两说了。
使用数据库支持搜索怎么样?
显然数据库是支持搜索的,毕竟它是专门用来存储数据的,其中也包含了数据分析。比如数据库中有一张表是存储商品信息的,我要查询里面所有名字包含 "凉鞋" 的商品对应的 id,那么就可以这么做:
select product_id from product where product_name like \'%凉鞋%\'
很明显这么做是正确的,但是要拿数据库来做搜索引擎则是不合适的。因为由于业务场景的不同,会带来两个问题:
1. 假设一个商品的名称不是 "...凉鞋...",而是 "...凉拖鞋...",这个时候该商品就选不到了,但它也是需要被选出来的;再比如用户想搜索 "榨汁机",但是不小心输入成了 "榨汁鸡",这个时候也没办法搜索的。所以这种情况下,无法通过对关键词进行切分,来获取更多的结果2. 如果我们不是按商品名称、而是按商品描述进行搜索,这个时候还会产生效率上的问题。因为某些字段的内容会非常长,数千甚至上万个是字符也是常见的,这个时候要查询内部是否包含关键词所需要扫描的文本量就会非常大,并且该字段的每一行记录都需要扫描。如果一张表里面有千万条记录,那么这个耗时是非常恐怖的
因此用数据库实现搜索是不靠谱的,性能会非常差。
什么是全文检索和 Lucene?
我们说数据库不适合专门用于搜索,那么我们应该选择谁呢?不用想,肯定是 ES,否则就没有必要介绍了。只不过在具体介绍 ES 之前,我们还需要做一些前戏。
全文检索
首先全文检索(或者说全文搜索)也是一种搜索,只不过它和数据库中使用 like 不同,全文检索使用了倒排索引的技术。所以全文检索分为两步:
索引创建:从数据中提取信息,建立倒排索引搜索索引:根据用户的查询去搜索索引,然后返回索引对应的结果
直接说的话不容易理解,直接举例说明吧,假设数据库中有一张表 game:
我想搜寻 type 中包含 "校园" 或者 "爱情" 的记录,这个时候显然需要全表扫描,假设库里面有一千万条记录,那么我们就需要扫描一千万次,且每次扫描的范围都是全部的字符。如果用户指定了多个关键词,还不能拆分、对单独的部分检索。
那么倒排索引是如何做到的呢?首先我们这里查询的是 type 字段,那么就对 type 字段的每一个文本进行拆分,得到多个关键词,然后再获取包含指定关键词的记录的 id。光说不好理解,我们直接基于上面的 type 字段建立倒排索引,看看结果如何吧。
通过对文本进行拆分,我们看到 type 字段包含 "亲情" 的有 id 为 1、2、3 的记录,包含 "夏日" 的有 id 为 2、3 的记录,所以每一个关键词都和包含该关键词的记录的 id 做了一个映射。然后搜索的时候,同样也会对关键词、或者说要搜索内容进行拆分,得到更多的关键词,然后去匹配。假设我们想要根据 "校园爱情" 进行查找,那么会拆分成 "校园" 和 "爱情",然后直接就能得到 1、3、4、5,根据 id 直接查找就可以了。
所以我们之前都是根据 id 去确定记录,现在是先根据记录的关键词来确定 id,而构建的关键词到 id 之间的映射便是倒排索引。所以我们也可以发现,并不是说使用了 ES 之后就不需要数据库了,因为数据库表的字段可能非常多,我们不会对每一个字段都建立倒排索引。而是只针对那些需要通过关键词匹配的字段,将该字段的每一行都进行拆分成一个个的关键词,然后再把所有的关键词组合起来,建立它们到 id 之间的映射(倒排索引)。
因此在建立倒排索引之后,行数反而会增多(如果大部分词都不一样的话),比如原来的数据有 100 万行,但是拆分出来的关键词有 200 万个,那么在建立倒排索引之后也会有 200 万行,但是我们不可能真的会搜索 200 万次。有可能我们搜索一次就能找到对应的 id 了,因为在倒排索引匹配的是关键词,当然也可能是十次、一百次,因此就需要设计一个好的搜索算法以及合适的数据组织结构来使得查询次数最小化,而算法如何设计显然不是我们需要操心的。并且在倒排索引中进行关键词匹配也和数据库中的 like 不一样,前者只需要匹配单词即可,效率要比后者高很多。
以上便是全文检索以及倒排索引,还是很好理解的。
Lucene
然后再来说说 Lucene,Lucene 就是一个 Jar 包,里面封装了很多建立倒排索引、以及搜索相关的算法。如果你使用 Java 语言的话,那么只需要引入这个 Jar、然后基于 Lucene 提供的 api 进行开发即可。通过 Lucene 我们就可以对已有的数据建立索引,Lucene 会在本地磁盘上面给我们组织数据的索引结构。
什么是 ElasticSearch
了解了上面的内容之后,再来了解 ES 就简单多了。我们说 Lucene 它封装了类似于搜索引擎的功能,但它是部署在单机上面的,如果我们的数据量非常大、需要多机存储的话该怎么办呢。首先我们能想到的是就把数据分散开存储在多机上,然后每台机器各有一个 Lucene。
上面的做法看似解决了数据量的问题,但其实背后还有很多缺陷。首先数据分散在多台机器上,这些数据要怎么切分?当我们在搜索的时候,如果数据存在多台机器上,那么是不是每台机器都需要访问?这么做是不是很麻烦呢?数据一旦分散在多台机器上,那么如何保证建立高性能的索引?以及数据的不丢失要如何保证,系统的高可用性要如何保证。因此任何框架,如果需要多机部署,那么之间就应该具备相互通信的功能,相互协调,彼此作为一个整体、就像单机一样对外提供服务。
所以 ES 就应运而生,它是基于 Lucene 实现的一个搜索引擎,同样使用 Java 语言编写。但是通过 ES 可以让全文搜索变得更加简单,因为 Lucene 需要你有比较深的检索相关的知识,比较复杂,而 ES 将这种复杂隐藏了起来,让用户可以通过 RESTful API 进行查询。不仅如此,ES 不仅仅是为了检索方便而封装的 Lucene,它还解决了分布式的问题。因为 Lucene 只是一个库,如果想支持多机部署,那么你需要额外做很多的工作。而 ES 把这些全部解决了,比如:
ES 具有分布式的文件存储,每个字段都可以被索引、被搜索自动维护数据在多个节点之间的分布,以及索引的建立、搜索请求的执行自动维护数据的冗余副本,一个节点宕掉了,不会造成数据的丢失可以轻松的扩展到上百台服务器,处理 PB 级结构化或非结构化数据除了 Lucene 的检索功能,ES 还封装了更多的高级功能,比如聚合分析、基于地理位置的搜索等等,可以让我们快速的开发应用,如果要基于原生的 Lucene 实现是很困难的
因此 ES 的概念我们就说完了,说白了 ES 就是一个基于 Lucene 实现的搜索引擎,并且支持高可用、可伸缩、分布式。每个节点之上都部署一个 ES,多个节点共同对外提供服务,至于节点之间如何协调 ES 内部已经帮我们做好了,无需我们关心。
此外,虽然我们一直说 ES 是一个搜索引擎,但其实 ES 不仅可以用来搜索,还可以用来做数据分析。比如电商网站通过 ES 选取 "百褶裙" 销量最高的十个商家,新闻网站通过 ES 选取访问量最高的几篇文章等等,显然此时在获取数据的同时也伴随着数据分析。因此 ES 是一个分布式的搜索和数据分析引擎,能够进行全文检索、结构化检索、数据分析,以及对海量数据进行接近实时的处理。
当然相信很多人都听过 ELK,专门用来搭建日志分析平台,其中 E 就是我们这里的 ElasticSearch,L 是 Logstash,K 是 Kibana。Logstash 是用来做数据采集的,ElasticSearch 负责数据分析,Kibana 负责数据可视化,我们后面也会涉及到 ELK。
ElasticSearch 的特点
1. 可以组成大型分布式集群,处理 PB 级数据,服务大公司;在可以运行在单机或者组成小型分布式集群,服务小公司。
2. ES 不是什么新技术,主要是将全文检索、数据分析以及分布式这些现有的技术合并在了一起,才形成了独一无二的 ES。
3. 对用户而言是开箱即用的,非常简单,作为中小型的应用,直接三分钟部署一下 ES 就可以作为生产环境的系统来用了。
4. 数据库的功能面对很多领域是不够用的,一些特殊的功能,像全文检索、同义词处理、相关度排名,复杂数据分析,海量数据的近实时处理等等,这些数据库是不支持的,而 ES 作为一个补充提供了数据库不具备的功能。
ElasticSearch 核心概念
1. Near Realtime(NRT):近实时,从写入数据到数据可以被搜索,整个过程所需要的时间非常短(大概 1 秒),并且基于 ES 执行搜索和分析同样可以达到秒级。
2. Cluster:集群,包含多个节点,当然也可以只包含一个节点。
3. Node:集群中的一个节点,每个节点都有一个名称(默认随机分配),节点的名称还是比较重要的,尤其是在执行运维管理操作的时候。
4. Index:索引,对应 MySQL 的数据库。
5. Type:类型,对应 MySQL 数据库的一张表。
6. Document:文档,对应 MySQL 数据库表中的一条记录,ES 的一个 Document 就类似于一条 JSON 数据。当然每条 JSON 数据可以有多个字段,每个字段在 ES 中就称为 Field,对应 MySQL 中的 Column。显然一个 Type 下的 Document,都有着相同的 Field。
7. shard:单台机器无法存储大量数据,ES 可以将一个索引中的数据切分为多个 shard,分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多的数据,让搜索和操作分布到多台服务器上去执行,提升吞吐性能,每个 shard 都是一个 Lucene Index,说白了就是 Index 的一个切片。
8. replica:任何一个服务器都有可能因为故障而宕机,此时 shard 可能会丢失,因此可以为每一个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,此外多个 replica 还可以提升搜索操作的吞吐量和性能。默认情况下,每个 Index 会被分成 5 个 shard(建立索引时设置,设置之后不能修改),被称为 primary shard,每个 primary shard 默认会有一个 replica shard(可以随时修改)。简单说的话,每个 Index 会被分成 5 个 shard,每个 shard 会有一个 replica。
安装 Elastic Search
下面就来安装 ES,这里我采用的是阿里云服务器,操作系统是 CentOS 7。ES 是基于 Java 语言编写的,所以我们在安装 ES 之前首先要安装 Jdk,而 Jdk 的安装比较简单,这里我就不演示了,总之版本不低于 1.8 即可。
我们直接去官网 https://www.elastic.co/cn/downloads/elasticsearch 下载即可,这里我下载的是 ES 6.4.2。下载完之后丢到服务器上解压,这里我解压到了 /opt 目录中。
然后将 bin 目录配置到环境变量中即可,单机的 ES 就安装完了,所以说非常的简单。然后看 ES 的目录是不是很熟悉呢,所有 Java 编写大数据组件都是类似的,bin 目录放一些启动脚本、以及用于命令行操作的脚本;config 目录放一些配置文件;lib 目录存放程序依赖的 jar 包;logs 目录负责存放日志文件;modules 目录存放功能模块;plugins ,目录存放一些插件。
然后我们启动 ES,不过启动之前我们需要修改一下 ES 安装目录的配置文件 config/elasticsearch.yml,去掉 network.host 的注释,并将它的值改成 0.0.0.0,因为 ES 默认只允许本机访问。
默认监听端口 9200,这里端口我们就不改了,但是注意此时仍然无法启动 ES,因为 ES 要求不能以 root 用户启动。
groupadd es
useradd es -g es
chown es:es /opt/elasticsearch-6.4.2/ -R
创建一个组 es,再创建一个用户 es,并赋予 ES 安装目录的操作权限。然后切换用户,接下来就可以启动 ES 了,直接在命令行输入 elasticsearch 即可。如果没有配置环境变量,那么需要在 ES 的安装目录下输入 bin/elasticsearch。
默认是以前台方式启动的,也就是会阻塞住,但如果你在启动时候发现进程退出了,并发现控制台打印了如下内容:
那么对于 ERROR [1],我们需要切换到 root 身份打开 /etc/security/limits.conf,然后在里面追加如下内容:
es hard nofile 65536
es soft nofile 65536
开头的 es 就是刚才创建的用户,如果你的用户不是 es,那么要换成你指定的用户。针对 ERROR [2],直接以 root 身份运行如下命令:
sysctl -w vm.max_map_count=262144
然后再启动 ES,发现一切正常,由于是前台启动,所以进程会阻塞。然后我们测试一下,浏览器中输入 http://ip:9200 ,看看能否返回内容。
返回了一条 JSON,我们说 ES 中的 Document(文档)就类似于一条 JSON,里面的字段就是 Field。然后一个 Type 下有多个 Document,并且这些 Document 具有相同的 Field。
里面 name 是节点名称,cluster_name 是集群名称,这些可以通过配置文件 config/elasticsearch.yml 进行修改,其它的字段显然就见名知意了。目前整个 ES 是启动成功了,但是前台启动的话,后续再想操作就要启动新的终端了,所以我们改成后台启动,启动方式是在 elasticsearch 后面加上一个 -d。
另外我们学习的时候直接在浏览器上输入肯定是不太方便的,所以我们还需要另一个工具 Kibana,我们可以通过 Kibana 去 ES 上数据。同样去官网 https://www.elastic.co/cn/downloads/kibana 进行下载即可,另外 Kibana 的版本和 ES 版本比较类似,我们下载的 Kibana 也是 6.4.2,同样解压到 /opt 目录下。
然后修改配置文件 config/kibana.yml,改如下几个配置:
# 将 localhost 改成 0.0.0.0,否则只能本机访问
server.host: "0.0.0.0"
# 被注释掉的,默认监听 5601,
#server.port: 5601
# 被注释掉的,指定 ES 进程的 ip 和 端口
# 由于 kibana 和 ES 在同一个节点上,并且 ES 监听端口是 9200,所以这里不需要改
#elasticsearch.url: "http://localhost:9200"
然后启动 kibana,kibana 的启动可以是 root 用户,这里我们就以 root 启动了。但 kibana 没有提供后台启动方式,所以这里使用 nohup bin/kibana >> /dev.null & 进行启动。
输入 http://ip:5601 进行查看:
点击 Dev Tools 进入控制台,查询 / 即可获取我们之前在浏览器上输入 http://ip:9200 所返回的结果,并且 kibana 控制台还有自动提示的功能,是不是很方便呢?
通过 GET _cluster/health 可以查看整个集群的状态:
后续我们就通过 kibana 来学习 ES,因为非常的方便。
ES 相关操作
然后我们来创建一个索引,那么要如何创建呢?
# 没有安装 kibana,那么就需要手动发送 PUT 请求了
PUT /girls
然后我们来查看索引信息,把 PUT 换成 GET 即可。
{
"girls": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1625857114072",
"number_of_shards": "5",
"number_of_replicas": "1",
"uuid": "ryfHPWPLSKaoSc9QKdLhuw",
"version": {
"created": "6040299"
},
"provided_name": "girls"
}
}
}
}
如果想查看当前的 ES 中都有哪些索引呢?
GET /_cat/indices?v
获取索引、创建索引我们知道了,那么如何删除索引呢?
DELETE /girls
通过 DELETE 即可删除索引,这里我们删完了再重新创建一下。
索引我们知道如何创建了,那么如何在索引下面创建一个 Type 呢?
POST /girls/hololive
{
"name": "夏色祭",
"age": "16",
"nickname": "夏哥,祭妹",
"grade": "一期生"
}
创建一个 Type 相当于在数据库中创建一张表,在数据库创建表的时候可以只有字段没有数据。但是在 ES 中创建 Type 的时候必须指定 Document,并且 URL 是 /Index/Type,然后 Document 是一个 JSON。注意:创建 Type 的时候,需要使用 POST 请求。
{
"_index": "girls",
"_type": "hololive",
"_id": "9iOsjHoB7xwjaPN8tQ3t",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
以上是返回的内容,我们注意里面的 _id 字段,这是插入的 Document 的唯一标识,后续我们便可以通过这个 _id 进行查询。但是这个 _id 很明显不好记,因此我们可以在创建数据的时候自己指定。
以后我们就可以通过 matsuri 进行查询了。
那么如何进行查询呢?GET /Index/Type/Id。
然后我们看一下返回 JSON 的 found 字段,如果为 true 表示 id 对应的记录存在;如果不存在,那么为 false,假设我们查找一个不存在的 id。
{
"_index": "girls",
"_type": "hololive",
"_id": "matsuri1",
"found": false
}
突然间不是太想写了,过于粗糙了。。。。。