赞
踩
Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
Lucence 比较老牌,API比较繁琐
Solr 基于Lucence 封装而来
Elaticsearch ES 分布式全文检索引擎
当单纯的对已有数据进行搜索时,Solr更快。
当实时建立索引时, Solr会产生io阻塞,查询性能较差, Elasticsearch具有明显的优势。
随着数据量的增加,Solr的搜索效率会变得更低,而Elasticsearch却没有明显的变化。
综上所述,Solr的架构不适合实时搜索的应用。
实际生产环境测试,下图为将搜索引擎从Solr转到Elasticsearch以后的平均查询速度有了50倍的提升。
ElasticSearch vs Solr 总结
(1)二者安装都很简单。
(2)Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能。
(3)Solr 支持更多格式的数据,比如JSON、XML、CSV,而 Elasticsearch 仅支持json文件格式。
(4)Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供
(5)Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch。
(6)Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用。
ElasticSearch分为Linux和Window版本,基于我们主要学习的是ElasticSearch的Java客户端的使用,所以我们课程中使用的是安装较为简便的Window版本,项目上线后,公司的运维人员会安装Linux版的ES供我们连接使用。
ElasticSearch的官方地址: https://www.elastic.co/products/elasticsearch
在资料中已经提供了下载好的5.6.8的压缩包:
Window版的ElasticSearch的安装很简单,类似Window版的Tomcat,解压开即安装完毕,解压后的ElasticSearch
的目录结构如下:
修改elasticsearch配置文件:config/elasticsearch.yml,增加以下两句命令:
http.cors.enabled: true
http.cors.allow-origin: "*"
此步为允许elasticsearch跨越访问,如果不安装后面的elasticsearch-head是可以不修改,直接启动。
点击ElasticSearch下的bin目录下的elasticsearch.bat启动,控制台显示的日志信息如下:
注意:9300是tcp通讯端口,集群间和TCPClient都执行该端口,9200是http协议的RESTful接口 。
通过浏览器访问ElasticSearch服务器,看到如下返回的json信息,代表服务启动成功:
注意:ElasticSearch是使用java开发的,且本版本的es需要的jdk版本要是1.8以上,所以安装ElasticSearch之前保证JDK1.8+安装完毕,并正确的配置好JDK环境变量,否则启动ElasticSearch失败。
ElasticSearch不同于Solr自带图形化界面,我们可以通过安装ElasticSearch的head插件,完成图形化界面的效果,完成索引数据的查看。安装插件的方式有两种,在线安装和本地安装。本文档采用本地安装方式进行head插件的安装。elasticsearch-5-*以上版本安装head需要安装node和grunt
1)下载head插件:https://github.com/mobz/elasticsearch-head
在资料中已经提供了elasticsearch-head-master插件压缩包:
2)将elasticsearch-head-master压缩包解压到任意目录,但是要和elasticsearch的安装目录区别开
3)下载nodejs:https://nodejs.org/en/download/
在资料中已经提供了nodejs安装程序:
双击安装程序,步骤截图如下:
安装完毕,可以通过cmd控制台输入:node -v 查看版本
5)将grunt安装为全局命令 ,Grunt是基于Node.js的项目构建工具
在cmd控制台中输入如下执行命令:
npm install -g grunt -cli
执行结果如下图:
6)进入elasticsearch-head-master目录启动head,在命令提示符下输入命令:
npm install
grunt server
7)打开浏览器,输入 http://localhost:9100,看到如下页面:
如果不能成功连接到es服务,需要修改ElasticSearch的config目录下的配置文件:config/elasticsearch.yml,增加
以下两句命令:
http.cors.enabled: true
http.cors.allow-origin: "*"
然后重新启动ElasticSearch服务,重新连接。
Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。Elasticsearch比传统关系型数据库如下:
Relational DB(Mysql) ‐> Databases(数据库) ‐> Tables(表) ‐> Rows(行) ‐> Columns(列)
Elasticsearch ‐> Indices(索引库) ‐> Types(类型) ‐> Documents(文档) ‐> Fields(字段)
一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。
相当于是数据表的字段,对文档数据根据不同属性进行的分类标识。
mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。
在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主要有两方面的原因:
1)允许你水平分割/扩展你的内容容量。
2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制之所以重要,有两个主要原因: 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
实际开发中,主要有三种方式可以作为elasticsearch服务的客户端:
第一种,elasticsearch-head插件
第二种,使用elasticsearch提供的Restful接口直接访问第三种,使用elasticsearch提供的API进行访问
Postman中文版是postman这款强大网页调试工具的windows客户端,提供功能强大的Web API & HTTP 请求调试。软件功能非常强大,界面简洁明晰、操作方便快捷,设计得很人性化。Postman中文版能够发送任何类型的HTTP 请求 (GET, HEAD, POST, PUT…),且可以附带任何数量的参数。
Postman官网:https://www.getpostman.com
课程资料中已经提供了安装包
curl ‐X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' ‐d '<BODY>
其中:
参数 | 解释 |
---|---|
VERB | 适当的 HTTP 方法 或 谓词 : GET 、 POST 、 PUT 、 HEAD 或者 DELETE 。 |
PROTOCOL | http 或者 https (如果你在 Elasticsearch 前面有一个 https 代理) |
HOST | Elasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。 |
PORT | 运行 Elasticsearch HTTP 服务的端口号,默认是 9200 。 |
PATH | API 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如:_cluster/stats 和 _nodes/stats/jvm 。 |
QUERY_STRING | 任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读) |
BODY | 一个 JSON 格式的请求体 (如果请求需要的话) |
请求url:
PUT localhost:9200/blog1
请求体:
{ "mappings": { "article": { "properties": { "id": { "type": "long", "store": true, "index":"not_analyzed" }, "title": { "type": "text", # 数据类型 "store": true, # 是否存储 "index":"analyzed", # 是否索引 "analyzer":"standard" # 是否分词 }, "content": { "type": "text", "store": true, "index":"analyzed", "analyzer":"standard" } } } } }
postman截图:
elasticsearch-head查看:
我们可以在创建索引时设置mapping信息,当然也可以先创建索引然后再设置mapping。
在上一个步骤中不设置maping信息,直接使用put方法创建一个索引,然后设置mapping信息。
请求的url:
POST http://127.0.0.1:9200/blog2/hello/_mapping
请求体:
{ "hello": { "properties": { "id":{ "type":"long", "store":true }, "title":{ "type":"text", "store":true, "index":true, "analyzer":"standard" }, "content":{ "type":"text", "store":true, "index":true, "analyzer":"standard" } } } }
PostMan截图
请求url:
DELETE localhost:9200/blog1
postman截图:
elasticsearch-head查看:
请求url:
POST localhost:9200/blog1/article/1
请求体:
{
"id":1,
"title":"ElasticSearch是一个基于Lucene的搜索服务器",
"content":"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
postman截图:
elasticsearch-head查看:
请求url:
POST localhost:9200/blog1/article/1
请求体:
{
"id":1,
"title":"【修改】ElasticSearch是一个基于Lucene的搜索服务器",
"content":"【修改】它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
postman截图:
elasticsearch-head查看:
请求url:
DELETE localhost:9200/blog1/article/1
postman截图:
elasticsearch-head查看:
请求url:
GET localhost:9200/blog1/article/1
postman截图:
请求url:
POST localhost:9200/blog1/article/_search
请求体:
{
"query": {
"query_string": {
"default_field": "title",
"query": "搜索服务器"
}
}
}
postman截图:
注意:
将搜索内容"搜索服务器"修改为"钢索",同样也能搜索到文档,该原因会在下面讲解中得到答案
{
"query": {
"query_string": {
"default_field": "title",
"query": "钢索"
}
}
}
请求url:
POST localhost:9200/blog1/article/_search
请求体:
{
"query": {
"term": {
"title": "搜索"
}
}
}
postman截图:
在进行字符串查询时,我们发现去搜索"搜索服务器"和"钢索"都可以搜索到数据;
而在进行词条查询时,我们搜索"搜索"却没有搜索到数据;
究其原因是ElasticSearch的标准分词器导致的,当我们创建索引时,字段使用的是标准分词器:
{ "mappings": { "article": { "properties": { "id": { "type": "long", "store": true, "index":"not_analyzed" }, "title": { "type": "text", "store": true, "index":"analyzed", //是否要分词 "analyzer":"standard" //使用 标准分词器 }, "content": { "type": "text", "store": true, "index":"analyzed", "analyzer":"standard" //标准分词器 } } } } }
例如对 “我是程序员” 进行分词
标准分词器分词效果测试:
http://127.0.0.1:9200/_analyze?analyzer=standard&pretty=true&text=我是程序员
分词结果:
{ "tokens" : [ { "token" : "我", "start_offset" : 0, "end_offset" : 1, "type" : "<IDEOGRAPHIC>", "position" : 0 }, { "token" : "是", "start_offset" : 1, "end_offset" : 2, "type" : "<IDEOGRAPHIC>", "position" : 1 }, { "token" : "程", "start_offset" : 2, "end_offset" : 3, "type" : "<IDEOGRAPHIC>", "position" : 2 }, { "token" : "序", "start_offset" : 3, "end_offset" : 4, "type" : "<IDEOGRAPHIC>", "position" : 3 }, { "token" : "员", "start_offset" : 4, "end_offset" : 5, "type" : "<IDEOGRAPHIC>", "position" : 4 } ] }
而我们需要的分词效果是:我、是、程序、程序员
这样的话就需要对中文支持良好的分析器的支持,支持中文分词的分词器有很多,word分词器、庖丁解牛、盘古分词、Ansj分词等,但我们常用的还是下面要介绍的IK分词器。
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出 了3个大版本。最初,它是以开源项目Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为 面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。
IK分词器3.0的特性如下:
1)采用了特有的“正向迭代最细粒度切分算法“,具有60万字/秒的高速处理能力。
2)采用了多子处理器分析模式,支持:英文字母(IP地址、Email、URL)、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。
3)对中英联合支持不是很好,在这方面的处理比较麻烦.需再做一次查询,同时是支持个人词条的优化的词典存储,更小的内存占用。
4)支持用户词典扩展定义。
5)针对Lucene全文检索优化的查询分析器IKQueryParser;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。
1)下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
课程资料也提供了IK分词器的压缩包:
2)解压,将解压后的elasticsearch文件夹拷贝到elasticsearch-5.6.8\plugins下,并重命名文件夹为analysis-ik
3)重新启动ElasticSearch,即可加载IK分词器
IK提供了两个分词算法ik_smart 和 ik_max_word
其中 ik_smart 为最少切分,ik_max_word为最细粒度划分
我们分别来试一下
1)最小切分:在浏览器地址栏输入地址
http://127.0.0.1:9200/_analyze?analyzer=ik_smart&pretty=true&text=我是程序员
输出的结果为:
{ "tokens" : [ { "token" : "我", "start_offset" : 0, "end_offset" : 1, "type" : "CN_CHAR", "position" : 0 }, { "token" : "是", "start_offset" : 1, "end_offset" : 2, "type" : "CN_CHAR", "position" : 1 }, { "token" : "程序员", "start_offset" : 2, "end_offset" : 5, "type" : "CN_WORD", "position" : 2 } ] }
2)最细切分:在浏览器地址栏输入地址
http://127.0.0.1:9200/_analyze?analyzer=ik_max_word&pretty=true&text=我是程序员
输出的结果为:
{ "tokens" : [ { "token" : "我", "start_offset" : 0, "end_offset" : 1, "type" : "CN_CHAR", "position" : 0 }, { "token" : "是", "start_offset" : 1, "end_offset" : 2, "type" : "CN_CHAR", "position" : 1 }, { "token" : "程序员", "start_offset" : 2, "end_offset" : 5, "type" : "CN_WORD", "position" : 2 }, { "token" : "程序", "start_offset" : 2, "end_offset" : 4, "type" : "CN_WORD", "position" : 3 }, { "token" : "员", "start_offset" : 4, "end_offset" : 5, "type" : "CN_CHAR", "position" : 4 } ] }
删除原有blog1索引
DELETE localhost:9200/blog1
创建blog1索引,此时分词器使用ik_max_word
PUT localhost:9200/blog1
{ "mappings": { "article": { "properties": { "id": { "type": "long", "store": true, "index":"not_analyzed" }, "title": { "type": "text", "store": true, "index":"analyzed", "analyzer":"ik_max_word" }, "content": { "type": "text", "store": true, "index":"analyzed", "analyzer":"ik_max_word" } } } } }
创建文档
POST localhost:9200/blog1/article/1
{
"id":1,
"title":"ElasticSearch是一个基于Lucene的搜索服务器",
"content":"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
请求url:
POST localhost:9200/blog1/article/_search
请求体:
{
"query": {
"query_string": {
"default_field": "title",
"query": "搜索服务器"
}
}
}
postman截图:
将请求体搜索字符串修改为"钢索",再次查询:
{
"query": {
"query_string": {
"default_field": "title",
"query": "钢索"
}
}
}
postman截图:
请求url:
POST localhost:9200/blog1/article/_search
请求体:
{
"query": {
"term": {
"title": "搜索"
}
}
}
postman截图:
ES集群是一个 P2P类型(使用 gossip 协议)的分布式系统,除了集群状态管理以外,其他所有的请求都可以发送到集群内任意一台节点上,这个节点可以自己找到需要转发给哪些节点,并且直接跟这些节点通信。所以,从网络架构及服务配置上来说,构建集群所需要的配置极其简单。在Elasticsearch 2.0 之前,无阻碍的网络下,所有配置了相同 cluster.name 的节点都自动归属到一个集群中。
2.0 版本之后,基于安全的考虑避免开发环境过于随便造成的麻烦,从 2.0 版本开始,默认的自动发现方式改为了单播(unicast)方式。配置里提供几台节点的地址,ES 将其视作gossip router 角色,借以完成集群的发现。由于这只是 ES 内一个很小的功能,所以 gossip router 角色并不需要单独配置,每个 ES 节点都可以担任。所以,采用单播方式的集群,各节点都配置相同的几个节点列表作为 router即可。
集群中节点数量没有限制,一般大于等于2个节点就可以看做是集群了。一般处于高性能及高可用方面来考虑一般集群中的节点数量都是3个及3个以上。
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主要有两方面的原因:
1)允许你水平分割/扩展你的内容容量。
2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制之所以重要,有两个主要原因: 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
创建elasticsearch-cluster文件夹,在内部复制三个elasticsearch服务
修改elasticsearch-cluster\node*\config\elasticsearch.yml配置文件
#节点1的配置信息:
#集群名称,保证唯一
cluster.name: my-elasticsearch
#节点名称,必须不一样
node.name: node-1
#必须为本机的ip地址
network.host: 127.0.0.1
#服务端口号,在同一机器下必须不一样
http.port: 9200
#集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9300
#设置集群自动发现机器ip集合
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#节点2的配置信息:
#集群名称,保证唯一
cluster.name: my-elasticsearch
#节点名称,必须不一样
node.name: node-2
#必须为本机的ip地址
network.host: 127.0.0.1
#服务端口号,在同一机器下必须不一样
http.port: 9201
#集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9301
#设置集群自动发现机器ip集合
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#节点3的配置信息:
#集群名称,保证唯一
cluster.name: my-elasticsearch
#节点名称,必须不一样
node.name: node-3
#必须为本机的ip地址
network.host: 127.0.0.1
#服务端口号,在同一机器下必须不一样
http.port: 9202
#集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9302
#设置集群自动发现机器ip集合
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
双击elasticsearch-cluster\node*\bin\elasticsearch.bat
PUT localhost:9200/blog1
{ "mappings": { "article": { "properties": { "id": { "type": "long", "store": true, "index":"not_analyzed" }, "title": { "type": "text", "store": true, "index":"analyzed", "analyzer":"standard" }, "content": { "type": "text", "store": true, "index":"analyzed", "analyzer":"standard" } } } } }
POST localhost:9200/blog1/article/1
{
"id":1,
"title":"ElasticSearch是一个基于Lucene的搜索服务器",
"content":"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
注意配置跨域访问:
http.cors.enabled: true
http.cors.allow-origin: “*”注意:window10搭建集群,文件名字要简单和不能出现空格,要不然会出现文件加载不成功的情况
elasticsearch 集群无法启动出现如下提示 failed to send join request to master(删除data下面的数据)
Spring Data Elasticsearch是Spring Data项目下的一个子模块。
查看 Spring Data的官网:http://projects.spring.io/spring-data/
Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端API 进行封装 。Spring Data为Elasticsearch项目提供集成搜索引擎。Spring Data Elasticsearch POJO的关键功能区域为中心的模型与Elastichsearch交互文档和轻松地编写一个存储库数据访问层。
官方网站:http://projects.spring.io/spring-data-elasticsearch
###1.SpringData ES说明
在后期项目开发中,XML整合方式已经落伍了,目前市面上的项目都是拥抱SpringBoot框架,来实现项目的构建,那么课程就使用SpringBoot完成Spring Data ES的整合。就不用管传统applicationContext.xml整合方式
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>easygo</artifactId> <groupId>com.bruce.easygo</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>easygo-search-service</artifactId> <!--导入项目中的依赖--> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--引入实体层--> <dependency> <groupId>com.bruce.easygo</groupId> <artifactId>easygo-pojo</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency> <!-- Eureka客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>com.bruce.easygo</groupId> <artifactId>easygo-commons</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
server.port=9009
# 配置Eureka信息
# 服务的名字,注册到注册中心的名字,后期消费者来根据名字调用服务 可以重复
spring.application.name=easygo-search-service
# EurekaServer地址
eureka.client.service-url.defaultZone=http://127.0.0.1:9001/eureka
# 当调用getHostname获取实例的hostname时,返回ip而不是host名称
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
# 执行当前服务的应用ID 不可以重复 标识的是每一个具体的的服务
eureka.instance.instance-id=easygo-search-service-9009
<!--导入Spring Data ES依赖-->
<!--spring-data elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
# Spring Data elasticsearch配置
spring.data.elasticsearch.cluster-name=elasticsearch
# 连接地址
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
#设置连接超时时间
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
package com.easygo.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; y @RunWith(SpringRunner.class) @SpringBootTest public class TestES { //如果要操作Redis,需要获取一个 RedisTemplate //如果要操作ES,需要获取 ElasticsearchTemplate,天下我有! @Resource ElasticsearchTemplate elasticsearchTemplate; /** * 01-测试工具类 */ @Test public void testConn(){ System.out.println("ES操作的工具类:"+elasticsearchTemplate); } /** *02-创建一个索引库? Goods索引库,实际的项目搜索的是商品数据! */ @Test public void testCreateIndex(){ //创建索库 elasticsearchTemplate.createIndex("goodsindexs"); System.out.println("测试创建索引库"); } }
package com.easygo.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor public class Goods implements Serializable { private static final long serialVersionUID = 8972263575352384971L; private Integer id; private String seller_id; //卖家ID private String goods_name; //商品的标题 private Integer default_item_id; //默认上架商品ID private String audit_status; //当前状态 private String is_marketable; //是否上架 private Integer brand_id; //商品的品牌ID private String caption; //商品的副标题 private Integer category1_id; private Integer category2_id; private Integer category3_id; private String small_pic; //商品的小图 private Double price; //商品的价格 private Integer type_template_id; private String is_enable_spec; private String is_delete; }
<!--导入Spring Data ES依赖 把它导入到实体类中-->
<!--spring-data elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
package com.easygo.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor //文档对象是(索引库名,类型) @Document(indexName = "goodsindex",type = "goods") public class Goods implements Serializable { private static final long serialVersionUID = 8972263575352384971L; //字段的配置 类型 是否索引 是否存储 @Field(store = true,index = false,type = FieldType.Integer) private Integer id; private String seller_id; //卖家ID @Field(store = true,analyzer = "ik_max_word",index = true,searchAnalyzer ="ik_max_word",type = FieldType.Text) private String goods_name; //商品的标题 private Integer default_item_id; //默认上架商品ID private String audit_status; //当前状态 private String is_marketable; //是否上架 @Field(store = true,index = false,type = FieldType.Integer) private Integer brand_id; //商品的品牌ID @Field(store = true,analyzer = "ik_max_word",index = true,searchAnalyzer ="ik_max_word",type = FieldType.Text) private String caption; //商品的副标题 private Integer category1_id; private Integer category2_id; private Integer category3_id; @Field(store = true,index = false,type = FieldType.Text) private String small_pic; //商品的小图 @Field(store = true,index = false,type = FieldType.Double) private Double price; //商品的价格 private Integer type_template_id; private String is_enable_spec; private String is_delete; }
package com.easygo.test; import com.easygo.client.GoodsClient; import com.easygo.pojo.Goods; import com.easygo.service.GoodsService; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.SearchResultMapper; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class TestES { //如果要操作Redis,需要获取一个 RedisTemplate //如果要操作ES,需要获取 ElasticsearchTemplate,天下我有! @Resource ElasticsearchTemplate elasticsearchTemplate; @Resource GoodsClient goodsClient; @Autowired GoodsService goodsService; /** * 01-测试工具类 */ @Test public void testConn(){ System.out.println("ES操作的工具类:"+elasticsearchTemplate); } /** *02-创建一个索引库? Goods索引库,实际的项目搜索的是商品数据! */ @Test public void testCreateIndex(){ //创建索库,索引库的名字是?? elasticsearchTemplate.createIndex(Goods.class); System.out.println("测试创建索引库成功~"); elasticsearchTemplate.putMapping(Goods.class);//因为类上面有注解 System.out.println("创建Goods的Mapping完成"); } /** * 新增数据库中的数据到ES索引库中 */ @Test public void testAddDocument(){ List<Goods> goodsList = goodsClient.getGoods(1); for (Goods goods : goodsList) { System.out.println("正在导入:"+goods); } goodsService.saveDocuments(goodsList); System.out.println("批量新增索引库数据成功......"); } /** * 根据ID查询 */ @Test public void testgetDocumentById(){ Goods goods = goodsService.getDocumentById(149187842868047L); System.out.println("查询的对象是:"+goods); } /** * 根据ID更新 */ @Test public void testUpdateById(){ Goods goods = goodsService.getDocumentById(149187842867986L); System.out.println("查询的原对象是:"+goods); goods.setGoods_name("阿玛尼装逼神器"); goods.setCaption("阿玛尼装逼神器,泡妞必备,值得哟拥有"); goods.setPrice(250.0); goodsService.updateDocumentById(goods); System.out.println("更新document"); } /** * 根据ID删除 */ @Test public void testDeleteByid(){ goodsService.deleteDocumentById(149187842867914L); System.out.println("删除成功"); } /** * 删除所有 */ @Test public void testDeleteAll(){ goodsService.deleteAllDocument(); System.out.println("全部干光了"); } /** * 测试关键词查询01 条件查询分页 */ @Test public void testQuery1(){ int pageIndex=4; //当前页码 int pageSize=3; //页码大小 String keywords="手机"; //用户输入的关键词 //构建一个查询对象 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() //设置查询条件,可以构建多个条件 .withQuery(QueryBuilders.queryStringQuery(keywords).defaultField("goods_name")) //设置分页的信息,页码从0开始计算 .withPageable(PageRequest.of(pageIndex - 1, pageSize)).build(); //条件查询分页,返回分页对象 AggregatedPage<Goods> page = elasticsearchTemplate.queryForPage(searchQuery, Goods.class); System.out.println("当前页码:"+pageIndex); System.out.println("页面大小:"+pageSize); System.out.println("总页数:"+page.getTotalPages()); System.out.println("总条数:"+page.getTotalElements()); System.out.println("每页到的数据是:"); List<Goods> goodsList = page.getContent(); for (Goods goods : goodsList) { System.out.println(goods); } } /** * 多条件搜索 分页 */ @Test public void testtestQuery2(){ int pageIndex=1; //当前页码 int pageSize=3; //页码大小 String keywords="火爆"; //用户输入的关键词 NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); if(keywords!=null&&!"".equals(keywords)){ builder.withQuery(QueryBuilders.multiMatchQuery(keywords, "goods_name", "caption")); } //设置分页的信息,页码从0开始计算 builder.withPageable(PageRequest.of(pageIndex - 1, pageSize)); NativeSearchQuery searchQuery = builder.build(); //条件查询分页,返回分页对象 AggregatedPage<Goods> page = elasticsearchTemplate.queryForPage(searchQuery, Goods.class); System.out.println("当前页码:"+pageIndex); System.out.println("页面大小:"+pageSize); System.out.println("总页数:"+page.getTotalPages()); System.out.println("总条数:"+page.getTotalElements()); System.out.println("每页到的数据是:"); List<Goods> goodsList = page.getContent(); for (Goods goods : goodsList) { System.out.println(goods); } }
接下来准备讲解商品搜索,商品搜索用到了ES,所以本文先讲解在Docker下搭建ES与ES的可视化工具Kibana
注意:
PS:跑起ES和ElasticSearch占内存3.4G,如下:
调高JVM线程数限制数量(一定要先设置!)
vim /etc/sysctl.conf
# 添加这个
vm.max_map_count=262144
# 保存后执行这个命令
sysctl -p
在使用docker 拉去最新的镜像时,会提示如下错误:
这里错误的意思是docker需要我们指定下载镜像的版本号。
但是我们想下载最新的版本号,该如何得知最新的版本号呢?
我们可以登录docker hub:https://hub.docker.com/u/library,搜索自己想要下载的镜像名:
点击搜索出来列表里的镜像,进入详情页面,点击Tags,第一个镜像就是最新的,可以看出,最新的镜像版本号为7.6.1:
重新使用docker执行版本号拉取,可以看到正在下载:
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -d elasticsearch:7.6.1
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -v /soft/es/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /soft/es/data:/usr/share/elasticsearch/data -v /root/elasticsearch/plugins:/usr/share/elasticsearch/plugins -d elasticsearch:7.6.1
浏览器访问http://你的ip:9200/
,可以看到访问成功。
docker exec -it elasticsearch /bin/bash
cd /usr/share/elasticsearch/config/
vi elasticsearch.yml
在elasticsearch.yml的文件末尾加上:
http.cors.enabled: true
http.cors.allow-origin: "*"
network.host: 0.0.0.0
exit
docker restart elasticsearch
elasticsearch
的版本和ik分词器的版本需要保持一致,不然在重启的时候会失败。ik分词器地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
docker exec -it elasticsearch /bin/bash
cd /usr/share/elasticsearch/plugins/
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.1/elasticsearch-analysis-ik-7.6.1.zip
exit
docker restart elasticsearch
exit
docker restart elasticsearch
根据一个搜索词,检索出所有包含该词的数据
例如:用户在搜索框输入一个词,客户端软件发送一个请求到后台,后台通过sql语句从数据库中找出相关条目(数据库会一条一条的对比),这就是一个最简单搜索原型
select * from product where title like ‘%关键词%’;
1, 当数据量很大时,假如500G, 效率低。从用户角度,从点击搜索按钮到看到搜索结果可能要很长时间,1小时?2小时?用户疯掉
2, 当数据量达到1T,一台电脑已经放不下了,这时候就需要多台,这就是分布式。这时候数据就在不同的服务器了,一个客户端不可能去请求每台机器,所以就需要一个管理员角色,负责把客户端请求分发到每台机器,同时汇总结果返回给客户端
ES
Elasticsearch简称ES,是一种以JSON格式进行数据存储的分布式搜索引擎
在一个电商平台项目中,会有非常多的商品,如果使用以往的MySQL进行模糊查询,需要比较长的等待时间,对数据库压力比较大,ES中使用倒排索引,查询效率非常高,可以减轻MySQL压力,而且还能进行分词查询和高亮处理,MySQL是做不到的。
不是所有的数据都会存在ES中,像商品规格表这种不常修改,且不会去模糊查询的放到缓存中就好了。如果要同步多张表到ES中,就要在Logstath中配置多个mysql的配置文件,对应到mysql的每张表
倒排索引
:将文档中的关键字和文档ID进行记录,在搜索关键词的时候,直接到指定的文档ID中查找数据
分词查询
:比如去搜索腾讯课堂,ES有自带的分词器会将腾讯课堂分词为腾讯和课堂,然后通过倒排索引去查找指定的文档id。
ES中有一个常用的英文分词器analyze,它只能够识别英文,对英文进行分词,对中文是每个字符都是独立的。如果要对中文进行分词识别,可以安装ik中文分词器插件。
盘古分词、庖丁解牛分词 IK分词…
迪丽热巴 蔡徐坤 IK分词可以自定义分词
(1)六个数字类型:byte、short、integer、long、float、double
(2)布尔型:boolean
(3)字符型:string(text分词,keyword不分词)
(4)二进制型:binary
ES集群原理类似Redis分片集群原理,采用分片集群+主从复制+请求转发的方式集群
当第一次启动节点的时候,logstath把MySQL中的数据用JSON格式插入到ES中,并修改MySQL中的updatetime字段为系统时间,之后每隔一分钟去MySQL中查updatetime大于上次同步时间的数据,增量同步到ES中。
延迟!!
强一致性:上架的时候商品数据同步过去,下架的时候也同步!
同步操作 操作MySql数据库之后 再同步ES索引库! 一致性~
ELK是Elasticsearch+Logstath+Kibana,Logstath用来收集日志,并且把日志以Json格式输出到ES中,通过kibana操作数据
增:POST /mymayikt/user/1,不指定ID会随机分配一个ID
删:DELETE /mymayikt/user/1
改:PUT /mymayikt/user/1
查:GET /mymayikt/user/1
查询数据:_search
查询设置:_setting
查询映射:_mapping
条件查询:GET /mymayikt/user/_search?q=age:21
范围查询:GET /mymayikt/user/_search?q=age[30 TO 60]
降序排列:GET /mymayikt/user/_search?q=age[30 TO 60]&sort=age:desc
分页查询:GET /mymayikt/user/_search?q=age[30 TO 60]&sort=age:desc&from=0&size=2
精确查询:
GET mymayikt/user/_search
{
“query”: {
“term”: {
“name”: “xiaoming”
}
}
}
分词查询:
GET /mymayikt/user/_search
{
“from”: 0,
“size”: 2,
“query”: {
“match”: {
“car”: “奥迪a61”
}
}
}
分词器查询:
http://192.168.212.181:9200/_analyze
{
“analyzer”: “ik_smart”,
“text”: “奥迪”
}
Spring Data ES 和SpringBoot整合
Elasticsearch集群由一个主节点(可以有多个备选主节点)和多个数据节点组成。
其中主节点负责创建、删除索引、分配分片、追踪集群中的节点状态等工作,即调度节点,计算压力较轻;
数据节点负责数据存储和具体操作,如执行搜索、聚合等任务,计算压力较大。
正常情况下,当主节点无法工作时,会从备选主节点中选举一个出来变成新主节点,原主节点回归后变成备选主节点。但有时因为网络抖动等原因,主节点没能及时响应,集群误以为主节点下线了,选举了一个新主节点,此时一个Elasticsearch集群中有了两个主节点,其他节点不知道该听谁的调度,这时就发生了"脑裂"现象,通俗点就是“精神分裂”。
es脑裂:一个大的es集群分裂成了多个小的集群。
比如有 a b c d 四个es
a b c d 之间选取一个master,比如master是a。
若某时刻 c d 访问不到a ,b能访问到a。
c d之间会重新选举一个master。
这样整个a b c d的es集群就会分裂为a b 和 b c 两个集群。
(1)网络抖动。
由于是内网通信、网络通信等问题造成部分节点认为master node挂掉, 然后另选master node的情况可能性较小;可以通过检查Ganglia集群监控,没有发现异常的内网流量, 故此原因可以排除。
而外网的网络出现问题的可能性更大,更有可能造成“脑裂”现象。
(2)节点负载。
如果主节点同时承担数据节点的工作,可能会因为工作负载大而导致对应的Elasticsearch实例停止响应。此外,由于数据节点上的Elasticsearch进程占用的内存较大, 较大规模的内存回收操作(GC)也能造成Elasticsearch进程失去响应。所以,该原因出现“脑裂”现象的可能性更大。
(3)内存回收。
由于数据节点上的Elasticsearch进程占用的内存较大,较大规模的内存回收操作也能造成Elasticsearch进程失去响应。
防止脑裂: 主节点就是只当主节点,不要又当主节点 又当数据节点!
(1)不要把主节点同时设为数据节点,即node.master
和node.data
不要同时为true
。
主节点配置为:
node.master: true 候选主节点(有当主节点的潜质)
node.data: false 不作为工作节点
从节点配置为:
node.master: false
node.data: true
(2)将节点响应超时discovery.zen.ping_timeout
稍稍设置长一些(默认是3秒)。默认情况下, 一个节点会认为, 如果master节点在 3 秒之内没有应答, 那么这个节点就是挂掉了, 而增加这个值, 会增加节点等待响应的时间, 从一定程度上会减少误判。
(3)discovery.zen.minimum_master_nodes
的默认值是1,该参数表示, 一个节点需要看到的具有master节点资格的最小数量, 然后才能在集群中做操作,即重新选举主节点。官方的推荐值是(N/2)+1
,其中 N 是具有 master资格的节点的数量,即只有超过(N/2)+1
个主节点同意,才能重新选举主节点。
实际解决办法
最终考虑到资源有限,解决方案如下:
增加一台物理机,这样,一共有了三台物理机。在这三台物理机上,搭建了6个ES的节点,三个data节点,三个master节点(每台物理机分别起了一个data和一个master),3个master节点,目的是达到(n/2)+1等于2的要求,这样挂掉一台master后(不考虑data),n等于2,满足参数,其他两个master节点都认为master挂掉之后开始重新选举,
master节点上
node.master = true
node.data = false
discovery.zen.minimum_master_nodes = 2
data节点上
node.master = false
node.data = true
方案分析
1.角色分离后,当集群中某一台节点的master进程意外挂掉了,或者因负载过高停止响应,终止掉的master进程很大程度上不会影响到同一台机器上的data进程,即减小了数据丢失的可能性。
2.discovery.zen.minimum_master_nodes
设置成了2(3/2+1)当集群中两台机器都挂了或者并没有挂掉而是处于高负载的假死状态时,仅剩一台备选master节点,小于2无法触发选举行为,集群无法使用,不会造成分片混乱的情况。
而图一,两台节点假死,仅剩一台节点,选举自己为master,当真正的master苏醒后,出现了多个master,并且造成查询不同机器,查到了结果不同的情况。
以上的解决方法只能是减缓这种现象的发生, 并没有从根本上杜绝。
如果发生了脑裂, 如何解决?
所以怎么从脑裂中恢复?
第一个建议是给所有数据重新索引。
第二, 如果脑裂发生了, 要十分小心的重启你的集群。 停掉所有节点并决定哪一个节点第一个启动。 如果需要, 单独启动每个节点并分析它保存的数据。 如果不是有效的, 关掉它, 并删除它数据目录的内容( 删前先做个备份) 。 如果你找到了你想要保存数据的节点, 启动它并且检查日志确保它被选为主节点。 这之后你可以安全的启动你集群里的其他节点了。
Elasticsearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎,可以快速存储、搜索、分析海量的数据。
1、传统数据库模糊查询不走索引,当数据量很大时,查询效率非常低下
2、Es搜索可以对搜索词进行自动分词等处理,更容易查询到相关的内容
简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。
ES是基于Lucene的,提供了更高层次的封装以及分布式等方面的增强与扩展,并且提供了基于JSON的REST API 来更方便地使用Lucene的功能
ELasticsearch中的每个分片都是一个分离的Lucene实例;
ES 5之前默认的算分采用TF-IDF,ES 5之后采用BM 25
BM 25是在TF-DF基础上做了一个收敛,避免了TF无限增长时得分无限增长的问题
倒排索引是一种数据结构
Elasticsearch是通过Lucene的倒排索引技术实现比关系型数据库更快的过滤。
创建索引
(1)把每个文档拆分为独立的词
(2)创建一个包含所有且不重复的词条列表
(3)记录每个词条出现在哪些文档中
搜索时
(1)搜索时,直接根据词,返回对应的文章id
正排索引主要用于实现根据指定字段进行排序和聚合的功能。
假如我们需要对数据做一些聚合操作,比如排序时,lucene会将所有匹配到的文档读入到内存,再进行排序,如果排序数据量巨大的话,非常容易就造成内存溢出。
而正排索引类似于数据库key-value-value,可以只读取id和排序字段到内存进行排序。
es中的正排索引有两种:FieldData和DocValues
FieldData是存在内存中的,DocValues存在磁盘中,DocValues不支持text类型字段的排序
【生成文档】
ES在生成文档时,会分别生成倒排索引和正排索引
【搜索】
Es先用倒排索引找到文档,然后用正排索引聚合排序等操作
默认是根据算分排序,自定义排序需要用到正排索引
DocValues是默认开启的,所以可以直接使用,但是不支持对text类型字段的排序。
FieldData是默认关闭的,需要设置mapping开启
DocValues会占用更多磁盘空间,也会影响搜索速度,确定该字段不需要聚合、排序以及脚本操作可以通过mapping设置doc_values:false,改回true需要重建索引
更新操作
可以通过版本号使用乐观并发控制
每个文档都有一个
_version
版本号,对其更新会默认携带version=_version+1
,如果_version+1
小于更新时的_version
,说明已经被更新了,会更新失败
写操作
支持3种一致性级别,默认是:只有大多数分片可用时才允许写操作。
如果大多数可用,进行了写操作,写入失败的副本分片会在一个不同的节点上重建。
读操作
默认是主分片和副本分片上更新/写的操作都完成之后的文档才会被查到
可以修改参数,只要主分片更新/写的操作完成的文档即可被查到
【index】
文档不存在:新增
文档存在:删除旧的、新增
【create】
文档不存在:新增
文档存在:报错
【index】
文档不存在:新增
文档存在:删除旧的、新增
【update】
文档不存在:报错
文档存在:直接更新
keyword类型的字段不会分词,只能精确匹配,区分大小写的
text类型会被分词并且转为小写
query查询会计算相关度分数,进行排序
filter查询不进行分数计算,并且查询结果会被缓存,性能较高
【集群高可用架构】
es提供了分布式集群(Cluster)的方式,保证高可用
es将索引细分为分片(主分片、副本分片),一个索引被拆分为多个主分片,分布在不同节点上,用于解决存储空间水平扩容问题。副本分片是对主分片的拷贝,用于解决数据高可用问题(保证一个节点挂了,剩余节点主分片+副本分片能组成完整的数据,保证服务可用)。
【master节点】
master节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;
主节点(Master Node):也叫作主节点,主节点负责创建索引、删除索引、分配分片、追踪集群中的节点状态等工作。 Elasticsearch 中的主节点的工作量相对较轻。 用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,而并不需要经过主节点转发。
【分片特性】
主分片在创建索引时指定,后续不允许修改
副本分片数可以动态调整。
Elasticsearch 的选主是 ZenDiscovery 模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和 Unicast(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分;
1、【在elasticsearch.yml 配置文件中确定当master节点最少票数阈值】确认候选主节点的最少投票通过数量,elasticsearch.yml 设置的值
discovery.zen.minimum_master_nodes
;2、【每个节点给能ping通的NodeId最小的节点投票】对所有候选 master 的节点(
node.master: true
)根据 nodeId 字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。3、【选举投票达到阈值且自己选举了自己的节点为master节点】如果对某个节点的投票数达到阈值,并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。
【脑裂问题】
当网络出现问题,Node1是master节点和其他Node无法连接
Node1还是作为Master节点组成一个集群,其他节点重新选举出了Master节点,这样就形成了两个master节点,当网络回复,集群无法恢复
【解决脑裂】
7.0之前
设置quorum(仲裁)参数,只有集群中可被选举为master的节点数大于该参数,才能选举master
7.0开始
es做了优化,不会发生脑裂
为了保证集群的高可用,分片是要被分配到不同节点上的
Yellow或red原因是分片没有被合理分配,可以通过_cluster/allocation/explain查看具体原因
因为es在存储文档时,会根据主分片数和文档id,通过hash算法,计算出文档应该分布在哪个分片上,确保了文档可以均匀的分布在各个分片中。
如果修改了主分片数,那计算规则就变了,会导致文档不能均匀的分布在各个分片中。
Ps:保存文档时,可以传_routing参数,自定义存储到指定的分片上
由于倒排索引一旦生成,不可改变,所以每次新建文档(到某个分片),就会生成一个新的Segment,用来存储该分片上新的倒排索引。
- 【Refresh】
1、新建文档时,数据会分别写入Index Buffer和Transaction Log
2、Index Buffer每1s/存满(默认是jvm的10%),数据会刷新到Segment
- 【Transaction Log】
3、每次写入请求Transaction Log都会自动落盘
- 【Flush】
5、ES会自动做flush操作(每30分钟/Transaction log满(默认512MB)),会将Index Buffer刷新到Segment,并将Segment写入磁盘,清空Transaction log
- 【Merge】
6、Segment会越来越多,ES会自动进行Merge操作,减少Segment数量,也可以手动merge(POST my_index/_forcemerge)。
7、删除的文档数据会存到.log文件中,merge会真正删除.log文件。
【近实时】
ES默认新建文档1s后被搜到
【原因】
创建文档时,会先存储到Index Buffer中,每1s/Index Buffer存满(默认是jvm的10%)刷新存入Segment,查询是查的Segment,所以存入Segment之后才会被搜索到
答:创建文档时,存储到Index Buffer的同时,会存储到Transaction log中,Transaction log默认落盘,当断电恢复后,会根据Transaction log做数据恢复。
补充:每30分钟/Transaction log满(默认512MB),会进行一次Flush,将Index Buffer刷新到Segment,并将Segment写入磁盘,清空Transaction log。
被删除的文档,会存到.log文件中,ES在自动进行merge时,会清理.log文件,此时才会释放空间
1、客户端向Node1节点发送新建/删除索引的请求
2、Node1节点根据文档id,通过Hash算法确定属于分片0,会将请求转发到Node3
3、Node3上主分片执行请求(1、对需要分词的字段进行分词、转成小写;2、存储为正排索引和倒排索引),如果成功了,将请求转发到Node1、Node2的副本分片上。一旦所有副本都报告成功,Node3向Node1节点报告成功,Node1节点向客户端报告成功
搜索执行阶段过程分俩个部分,我们称之为 Query Then Fetch。
Query查询阶段
1、客户端向Node3发送了一个search请求,Node3会创建一个大小为from+size的空队列
2、Node3将查询请求转发到索引的每个主分片/副本分片中。每个分片在本地执行查询(1、对需要分词的字段的搜索时,先分词、再转为小写,根据倒排索引匹配;2、对不需要分词的字段搜索时,直接精确匹配、自定义排序和聚合时使用正排索引)并添加结果到大小为 from + size 的本地有序优先队列中
3、每个分片返回各自优先队列中所有文档的 ID 和排序值给 Node 3 ,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
fetch - 读取阶段 / 取回阶段
1、协调节点根据ID,辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。
2、每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。
3、一旦所有的文档都被取回了,协调节点返回结果给客户端。
比如取100-110的数据,每个节点上的100-110并不是真正的100-110,所以需要把每个节点的前110都拿过来放一起排序,那么:
每个集群需要查询文档数 = from + size
协调节点需要处理文档数 = 分片数 * (from + size)
如果深度分页,from是1万,那协调点处理的数据量就会非常大,ES默认限定最多为1万个文档。
【解决】
1、使用滚动搜索,记录上一次搜索id,往下翻页从id之后开始取,但是只支持往后翻,不支持指定页数。会创建一个有时间限制的快照,新增数据快照不会更新。
2、业务上处理:每次少量翻页,让用户没有翻到很大页数的兴趣
【算分不准】
默认算分都是根据自己分片上的数据进行的算分,会导致各个分片上算分标准不一
【解决】
1、数据量少,主分片设置为1;数据量足够大时,保证文档均匀分布到各分片上
2、搜索时指定参数“_search?search_type=dfs_query_then_fetch”,会收集各个分片上的TF和IDF,合并算分。会耗费跟很多的CPU和内存
ELK是一套日志采集、监控的解决方案
Elasticsearch:负责日志检索和储存
Logstash:负责日志的收集、处理
Kibana:负责日志的可视化
对于日志类型的,可以基于时间序列的设置索引,通过通配符的方式匹配查找
对于旧的数据处理:直接删除文档是不会立即释放资源的,但是直接删除索引会立即释放索引,时间序列的索引也方便做冷热分离
ES集群的索引写入及查询速度比较依赖于磁盘的IO速度,冷热数据分离的关键点为使用固态磁盘存储数据。
若全部使用固态,成本过高,且存放冷数据较为浪费,
因此我们可以将实时数据(5天内)存储到热节点(固态磁盘)中,历史数据(5天前)的存储到冷节点(普通机械磁盘)中,并且可以利用ES自身的特性,比如日志类的,可以按天建立索引,这样能更方便的根据时间将热节点的数据迁移到冷节点中,达到冷热分离,提升热点数据的查询速度
硬件设施
选择合理的硬件:数据节点尽量使用SSD
- 搜索类等性能要求高的场景,建议SSD
- 按照1:10的比例配置内存和硬盘
- 日志类和查询并发低的场景,可以考虑使用机械硬盘存储
- 按照1:50的比例配置内存和硬盘
- 单节点数据建议控制在2TB以内,最大不建议超过5TB
- JVM配置机器内存的一般,JVM内存配置不建议超过32G
内存
最大内存设置,不要超过50%,单节点最大内存不要超过32G
内存大小要根据Node需要存储的数据进行估算
- 搜索类:1:16
- 日之类:1:48-1:96
举例:加入需要存储1T的数据,设置一个副本,需要2T的空间
如果是搜索类项目:最大32G内存那么,一个节点最多存储32*16=512G,所以至少需要4个节点存储数据。
主分片
7.0之前默认是5个主分片,7.0之后默认是1个主分片
【主分片少的问题】
单个主分片可以解决多个主分片算分不准等问题,但是单个主分片即使新增节点,也无法水平扩展
【主分片多的问题】
主分片过多,每个分片是一个lucene的索引,会占用更多的资源,并且每次搜索需要从每个分片上获取数据,会增加master节点管理负担,建议控制分片在10w以内。
数据量较少时,主分片过多也会导致相关性算分不准
【如何设计主分片】
根据数据量大小判定
- 日志类应用,单个分片不要大于50G
- 搜索类应用,单个分片不要大于20G
副本分片
【副本分片优点】
1、有效保证集群高可用
2、减缓主分片查询压力,可以提升整体查询的QPS
【缺点】
1、副本分片是主分片的一个拷贝,需要占与主分片一样的资源
2、创建索引及文档时,有几个副本就要消耗几倍的cpu
【如何设计副本分片】
考虑节点资源,如果资源充足、需要提升查询速度,可以设置多的副本分片
1、多线程
使用线程池,线程数设置为cpu核心数+1,避免过多的上下文切换,来不及处理的放入队列,队列不能太大,否则占用的内存会成为GC的负担
2、使用_bulk批量写入
单次建议在5-15M,使用负载均衡策略尽量将数据打到不同节点
3、分片设定:
(1)适当减少副本数
(2)合理设置主分片数,确保均匀分配到所有数据节点上(eg:5个数据节点的集群,5个主分片5个副本分片,需要限定每个节点上可分配分片数为(5+5)/5=2个,生产中还要适当调大,保证节点下线时,分片无法正常迁移)
4、关闭不需要的功能(mapping中设置)
(1)不需要聚合搜索的字段,index参数设置为fasle(不建立正排索引)
(2)不需要算分的字段,norms设置为fasle
(3)对于不需要分词的字符串使用keyword(不分词)
5、Index Buffer刷新到segment的时间默认是1s,可以设置为-1,即禁止自动refresh(Index Buffer存满再刷新),避免频繁refresh生成过多的segment文件;与此同时增大Index Buffer大小,可以进一步的减少生成segment文件(会导致搜索实时性降低)
6、每次写入数据,都会存入Transaction Log并落盘,把Transaction Log的落盘改为异步、把落盘时间改为60s(每分钟落盘一次)、增大Transaction Log大小(默认512M)(会降低数据的容灾性)
1、使用SSD盘和冷热分离架构,将旧的数据存到普通盘的节点上,把新的常查询的数据分布在SSD盘的节点上(日志类型的按时间序列建立索引)
2、尽量使用Filter Context,利用缓存机制,减少不必要的算分
3、禁止使用*开头通配符查询
4、避免查询时对数据做计算(字符串长度筛选等),可以在存储时计算存入新的字段
可以使用profile,explain api分析慢查询问题
5、【分片优化】
(1)适当减少主分片数,因为主分片数为5,一个查询需要访问5个分片
(2)控制单个分片尺寸,搜索类20G以内、日志类50G以内
6、使用基于时间序列的索引,将旧的只读的索引进行手动merge,减少segment的数量(日志类型)
分词器选用ik分词器,能够较好的对中文分词
创建索引时使用ik_max_word分词,搜索时使用ik_smart分词
即:索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。
https://www.modb.pro/db/390323
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。