ElasticSearch
什么是 elasticsearch
Elasticsearch 是一个基于 Lucene 的实时的分布式搜索和分析 引擎。实时搜索,稳定,可靠, 快速,安装使用方便。
Elasticsearch 与 solr 对比
- 性能对比:大型互联网公司,实际生产环境测试,将搜索引擎从 Solr 转到Elasticsearch 以后的平均查询速度有了 50 倍的提升。
- 优势
a)Elasticsearch 是分布式的。不需要其他组件,分发是实时的,被叫做”Push replication”。
b)Elasticsearch 完全支持 Apache Lucene 的接近实时的搜索。处理多租户(multitenancy)不需要特殊配置,而 Solr 则需要更多的高级设置。
c)Elasticsearch 采用 Gateway 的概念,使得备份更加简单。
d)各节点组成对等的网络结构,某些节点出现故障时会自动分配其他节点代替其进行工作。
Elasticsearch 与关系型数据库对比
ElasticSearch 与关系型数据库的相似:
1.一个 ES 集群可以包含多个索引(数据库),每个索引又包含了很多类型(表),类型中包含了很多文档(行),每个文档又包含了很多字段(列)。
2.传统数据库为特定列增加一个索引,例如 B-Tree 索引来加速检索。Elasticsearch 和 Lucene 使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。
3.倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。
部署(注:java 版本要求:最低 1.7)
- 下载
https://www.elastic.co/downloads/ - 配置
修改 config/elasticsearch.yml (注意要顶格写,冒号后面要加一个空格)
a) Cluster.name: shsxt (同一集群要一样)
b) Node.name:node-1 (同一集群要不一样)
c) Network.Host: 192.168.1.194 (这里不能写 127.0.0.1)
d) 防止脑裂的配置
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping_timeout: 120s
client.transport.ping_timeout: 60s
discovery.zen.ping.unicast.hosts: [“192.168.1.191”,“192.168.1.192”, “192.168.1.193”] - 启动
ES_HOME/bin/elasticsearch
ES_HOME/bin/elasticsearch -d(后台运行) - 访问 http://localhost:9200
rest
REST 全称是 Resource Representational State Transfer,通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:
- Resource:资源,即数据。比如 news,friends 等;
- Representational:某种表现形式,比如用 JSON,XML,JPEG 等;
- State Transfer:状态变化。通过 HTTP 动词实现。
-
Rest 操作
– GET:获取对象的当前状态;
– PUT:改变对象的状态;
– POST:创建对象;
– DELETE:删除对象;
– HEAD:获取头信息。 -
ES 内置的 REST 接口
CURL命令
-X 指定 http 请求的方法
-HEAD GET POST PUT DELETE
-d 指定要传输的数据
-
创建和删除索引
创建索引库:
curl -XPUT http://192.168.78.101:9200/shsxt/
PUT/POST 都可以
删除索引库:
curl -XDELETE http://192.168.78.101:9200/shsxt/ -
创建document
curl -XPOST http://192.168.78.101:9200/shsxt/employee -d '{ "first_name" : "john", "last_name" : "smith", "age" : 25, "about" : "I love to go rock climbing" }'
- 创建索引注意事项
• 索引库名称必须要全部小写,不能以下划线开头,也不能包含逗号
• 如果没有明确指定索引数据的 ID,那么 es 会自动生成一个随机的 ID,这时需要使用 POST 方式,PUT 方式会出错。
命令:curl -XPOST http://localhost:9200/shsxt/employee -d ‘{“first_name” : “John”}’
命令:curl -XPUT http://localhost:9200/shsxt/employee -d '{“first_name” : “John”}'会报错
- 更新document
curl -XPUT http://192.168.78.101:9200/shsxt/employee/1 -d ' { "first_name" : "god bin", "last_name" : "pang", "age" : 38, "about" : "I love to go rock climbing", "address": "shanghai" }'
- PUT 和 POST 都是新增,修改。PUT 必须指定 ID,所以 PUT 一般数据更新 。POST 可以指定 ID,也可以不指定 做新增比较好。
- 局部更新:可以添加新字段或者更新已有字段(必须使用 POST)
curl -XPOST http://localhost:9200/shsxt/employee/1/_update -d ' { "doc":{ "city":"beijing", “sex”:”male” } }'
- 全局更新:curl -XPUT http://localhost:9200/shsxt/employee/1 -d ‘{“city”:“beijing”,"car
":“BMW”}’
- 普通查询索引
- 根据员工 id 查询
curl -XGET http://localhost:9200/shsxt/employee/1?pretty - 在任意的查询字符串中添加 pretty 参数,es 可以得到易于识别的 json 结果。
- curl 后添加-i 参数,这样你就能得到反馈头文件
curl -i XGET http://localhost:9200/shsxt/employee/1?pretty - 检索文档中的一部分,如果只需要显示指定字段
curl -XGET http://localhost:9200/shsxt/employee/1?_source=name,age
如果只需要 source 的数据
curl -XGET http://localhost:9200/shsxt/employee/1/_source?pretty - 查询所有
curl -XGET http://localhost:9200/shsxt/employee/_search?pretty - 根据条件进行查询
curl -XGET http://localhost:9200/shsxt/employee/_search?q=last_name:smith
- 查询
DSL 查询 •Domain Specific Language
- 领域特定语言
curl -XGET http://localhost:9200/shsxt/employee/_search?pretty -d '{ "query":{"match": {"last_name":"smith"} } }'
#对多个 field 发起查询:multi_match
curl -XGET http://localhost:9200/shsxt/employee/_search?pretty -d ' { "query": {"multi_match": { "query":"bin", "fields":["last_name","first_name"], "operator":"and" } } }'
- 复合查询,must,must_not, should
must: AND
must_not:NOT
should:OR
curl -XGET http://192.168.78.101:9200/shsxt/employee/_search?pretty -d ' { "query": {"bool" : { "must" : {"match": {"first_name":"bin"} }, "must" : {"match": {"age":37} } } } }'
- 查询 first_name=bin 的,并且年龄不在 20 岁到 30 岁之间的
curl -XGET http://192.168.78.101:9200/shsxt/employee/_search -d ' { "query": {"bool" : { "must" : {"term" : { "first_name" : "bin" } } , "must" : {"range": {"age" : { "from" : 30, "to" : 40 } } } } } }'
- 删除索引
curl -XDELETE http://localhost:9200/shsxt/employee/1?pretty
- 如果文档存在,es 会返回 200 ok 的状态码,found 属性值为 true,_version 属性的值+1
- found 属性值为 false,但是_version 属性的值依然会+1,这个就是内部管理的一部分,它保证了我们在多个节点间的不同操作的顺序都被正确标记了
- 注意:删除一个文档也不会立即生效,它只是被标记成已删除。 Elasticsearch 将会在你之后添加更多索引的时候才会在后台进行删除内容的清理
Elasticsearch 插件安装
head
- 在es的bin目录下执行(只要一台机器)
./plugin install mobz/elasticsearch-head
如果不行可以上传安装包离线安装:./plugin install file:绝对路径 - 启动es就可以了
- 访问地址是:ip:9200/_plugin/head
kibana
为es提供日志分析的web接口
解压安装,然后修改配置文件 config/kibana.yml 的 elasticsearch.url 属性为当前机器的ID地址端口号9200
marvel
Marvel 插件可以帮助使用者监控 elasticsearch 的运行状态,不过这个插件需要 license。安装完 license 后可以安装 marvel 的 agent,agent 会收集 elasticsearch 的运行状态。
- Install Marvel into Elasticsearch:(3 台 es 都装)
Es_home/bin/plugin install license
Es_home/bin/plugin install marvel-agent - Install Marvel into Kibana(在 kibana 机器上安装)
Kibana_home/bin/kibana plugin --install elasticsearch/marvel/latest - Start Elasticsearch and Kibana
bin/elasticsearch
bin/kibana - Navigate to http://localhost:5601/app/marvel
分词器集成
- 下载https://github.com/medcl/elasticsearch-analysis-ik
- 放在 es 的plugins的ik下,解压缩unzip elasticsearch-analysis-ik-1.8.0.zip
- 每台机器都这样操作,重新启动 elasticsearch 集群
- 测试例子:
a. 创建索引库
curl -XPUT http://localhost:9200/ik
b. 设置 mapping
curl -XPOST http://localhost:9200/ik/ikType/_mapping -d' { "properties": { "content": { "type": "string", "index":"analyzed", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" } } }'
c. 插入数据
curl -XPOST http://localhost:9200/ik/ikType/1 -d' {"content":"美国留给伊拉克的是个烂摊子吗"}' curl -XPOST http://localhost:9200/ik/ikType/2 -d' {"content":"公安部:各地校车将享最高路权"}' curl -XPOST http://localhost:9200/ik/ikType/3 -d' {"content":"中韩渔警冲突调查:韩警平均每天扣 1 艘中国渔船"}' curl -XPOST http://localhost:9200/ik/ikType/4 -d' {"content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"}'
d. 查询
curl -XGET http://localhost:9200/ik/ikType/_search?pretty -d'{ "query" : { "term" : { "content" : "中国" }} }'
Elasticsearch 核心概念
cluster
代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es 的一个重要概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看 es 集群,在逻辑上是个整体,你与任何一 个节点的通信和与整个 es 集群通信是等价的。主节点的职责是负责管理集群状态,包括管理分片的状态和副本的状态,以及节点的发现和删除。
- 只需要在同一个网段之内启动多个 es 节点,就可以自动组成一个集群。
- 默认情况下 es 会自动发现同一网段内的节点,自动组成集群。
- 集群状态查看
shards
代表索引分片,es 可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
- 可以在创建索引库的时候指定
curl -XPUT ‘localhost:9200/test1/’ -d’{“settings”:{“number_of_shards”:3}}’ - 默认是一个索引库有 5 个分片
number_of_shards: 5
replicas
代表索引副本,es 可以给索引设置副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高 es 的查询效率,es 会自动对搜索请求进行负载均衡。
- 可以在创建索引库的时候指定
curl -XPUT ‘localhost:9200/test2/’ -d’{“settings”:{“number_of_replicas”:2}}’ - 默认是一个分片有 1 个副本 (总共有两片)
number_of_replicas: 1
recovery
代表数据恢复或叫数据重新分布,es 在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
gateway
- 代表 es 索引的持久化存储方式,es 默认是先把索引存放到内存中,当内存满了时再持久化到硬盘。当这个 es 集群关闭再重新启动时就会从 gateway 中读取索引数据。es 支持多种类型的 gateway,有本地文件系统(默认),分布式文件系统,Hadoop 的 HDFS 和 amazon 的
s3 云存储服务。 - 如果需要将数据落地到 hadoop 的 hdfs 需要先安装插件 elasticsearch/elasticsearch-hadoop
discovery.zen
代表 es 的自动发 现节点机制,es 是一个基于 p2p 的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。
如果是不同网段的节点如何组成 es 集群
- 禁用自动发现机制
- discovery.zen.ping.multicast.enabled: false
- 设置新节点被启动时能够发现的主节点列表
- discovery.zen.ping.unicast.hosts: [“192.168.1.191", " 192.168.1.192"]
7.Transport
代表 es 内部节点或集群与客户端的交互方式,默认内部是使用 tcp 协议进行交互,同时它支持 http 协议(json 格式)、thrift、servlet、 memcached、zeroMQ 等的传输协议(通过插件方式集成)。
Elasticsearch 中的 settings 和 mappings
- settings 修改索引库默认配置
- 例如:分片数量,副本数量
curl -XGET http://localhost:9200/shsxt/_settings?pretty curl -XPUT http://localhost:9200/helloword/ -d '{ "settings": { "number_of_shards":3, "number_of_replicas":2 } }'
- Mapping,就是对索引库中索引的字段名称及其数据类型进行定义,类似于关系数据库中表建立时要定义字段名及其数据类型那样,(和solr 中的 schme 类似)不过 es 的 mapping 比数据库灵活很多,它可以动态添加字段。一般不需要要指定 mapping 都可以,因为 es 会自动根据数据格式定义它的类型,如果你需要对某些字段添加特殊属性(如:定义使用其它分词器、是否分词、是否存储等),就必须手动添加 mapping
- 查询索引库的 mapping 信息
curl -XGET http://localhost:9200/shsxt/employee/_mapping?pretty - mappings 修改字段相关属性
- 例如:字段类型,使用哪种分词工具
Elasticsearch 的 java API
- 通过 TransportClient 这个接口,我们可以不启动节点就可以和es 集群进行通信, 它需要指定 es 集群中其中一台或多台机的 ip 地址和端口
TransportClient client = new TransportClient() .addTransportAddress( new InetSocketTransportAddress("host1", 9300)) .addTransportAddress(new InetSocketTransportAddress("host2", 9300));
如果需要使用其他名称的集群(默认是 elasticsearch),需要如下设置
Settings settings = ImmutableSettings.settingsBuilder() .put("cluster.name", "myClusterName").build(); TransportClientclient = new TransportClient(settings) .addTransportAddress(new InetSocketTransportAddress("host1", 9300));
- 通过 TransportClient 这个接口,自动嗅探整个集群的状态,es 会自动把集群中其它机器的 ip 地址 加到客户端中
Settings settings = ImmutableSettings.settingsBuilder().p ut("client.transport.sniff", true).build(); TransportClient client = new TransportClient(settings) .addTransportAddress(new InetSocketTransportAddress("host1", 9300));
- 索引 index(四种 json,map,bean,es helpers)
IndexResponse response = client.prepareIndex(“shsxt", "emp", "1").setSource().execute().actionGet();
- 查询 get
GetResponse response = client.prepareGet(“shsxt", "emp","1").execute().actionGet();
- 更新 update
- 更新或者插入 upsert
- 删除 delete
DeleteResponse response = client.prepareDelete(“shsxt", "employee","1").execute().actionGet();
- 总数 count
long count = client.prepareCount(“shsxt").execute().get().getCount();
- 批量操作 bulk
- 查询 search
- SearchType
Elasticsearch 的查询
es 的搜索类型有 4 种
- query and fetch(速度最快)(返回 N 倍数据量)
向索引的所有分片(shard)都发出查询请求,各分片返回的时候把元素文档(document)和计算后的排名信息一起返回。这种搜索方式是最快的。因为相比下面的几种搜索方式,这种查询方法只需要去shard 查询一次。但是各个 shard 返回的结果的数量之和可能是用户要求的 size 的 n 倍。 - query then fetch(默认的搜索方式)
如果你搜索时,没有指定搜索方式,就是使用的这种搜索方式。这种搜索方式,大概分两个步骤,第一步,先向所有的 shard 发出请求,各分片只返回排序和排名相关的信息(注意,不包括文档 document),然后按照各分片返回的分数进行重新排序和排名,取前 size个文档。然后进行第二步,去相关的 shard 取 document。这种方式返回的 document 与用户要求的 size 是相等的。 - DFS query and fetch(可以更精确控制搜索打分和排名。)
这种方式比第一种方式多了一个初始化散发(initial scatter)步骤,有这一步可以更精确控制搜索打分和排名 - DFS query then fetch (最慢)
初始化散发其实就是在进行真正的查询之前,先把各个分片的词频率和文档频率收集一下,然后进行词搜索的时候,各分片依据全局的词频率和文档频率进行搜索和排名。显然如果使用 DFS_QUERY_THEN_FETCH 这种查询方式,效率是最低的,因为一个搜索,可能要请求3 次分片。但使用 DFS 方法,搜索精度应该是最高的。
- 总结一下,从性能考虑 QUERY_AND_FETCH 是最快的,DFS_QUERY_THEN_FETCH 是最慢的。从搜索的准确度来说,DFS 要比非 DFS的准确度更高。
- 查询:query – builder.setQuery(QueryBuilders.matchQuery(“name”, “test”))
- 分页:from/size – builder.setFrom(0).setSize(1)
- 排序:sort – builder.addSort(“age”, SortOrder.DESC)
- 过滤:filter – builder.setPostFilter(QueryBuilders.rangeQuery(“age”).from(1).to(19))
- 高亮:highlight
- 统计:facet(已废弃)使用 aggregations 替代
- 根据字段进行分组统计
- 根据字段分组,统计其他字段的值
- size 设置为 0,会获取所有数据,否则,只会返回 10 条
Elasticsearch 的分页
- 与 SQL 使用 LIMIT 来控制单“页”数量类似,Elasticsearch 使用的是 from 以及 size 两个参数:
- size:每次返回多少个结果,默认值为 10
- from:从哪条结果开始,默认值为 0
- 假设每页显示 5 条结果,那么 1 至 3 页的请求就是:
- GET /_search?size=5
- GET /_search?size=5&from=5
- GET /_search?size=5&from=10
- 注意:不要一次请求过多或者页码过大的结果,这么会对服务器造成很大的压力。因为它们会在返回前排序。一个请求会经过多个分片。每个分片都会生成自己的排序结果。然后再进行集中整理,以确保最终结果的正确性。
- timed_out 告诉了我们查询是否超时
curl -XGET http://localhost:9200/_search?timeout=10ms
es 会在 10ms 之内返回查询内容 - 注意:timeout 并不会终止查询,它只是会在你指定的时间内返回当时已经查询到的数据,然后关闭连接。在后台,其他的查询可能会依旧继续, 尽管查询结果已经被返回了。
Elasticsearch 分片查询
- 默认是 randomize across shards
- 随机选取,表示随机的从分片中取数据
- _local:指查询操作会优先在本地节点有的分片中查询,没有的话再在其它节点查询。
- _primary:指查询只在主分片中查询
- _primary_first:指查询会先在主分片中查询,如果主分片找不到
(挂了), 就会在副本中查询。 - _only_node:指在指定 id 的节点里面进行查询,如果该节点只有查询索引的部分分片,就只在这部分分片中查找,所以查询结果可能不完整。如 _only_node:123 在节点 id 为 123 的节点中查询。
- _prefer_node:nodeid 优先在指定的节点上执行查询
- _shards:0 ,1,2,3,4:查询指定分片的数据
Elasticsearch 中脑裂问题
所谓脑裂问题(类似于精神分裂),就是同一个集群中的不同节点,对于集群的状态有了不一样的理解。discovery.zen.minimum_master_nodes 用于控制选举行为发生的最小集群节点数量。推荐设为大于1 的数值, 因为只有在 2 个以上节点的集群中,主节点才是有意义的。
- 正常情况下,集群中的所有的节点,应该对集群中 master 的选择是一致的,这样获得的状态信息也应该是一致的,不一致的状态信息,说明不同的节点对 maste 节点的选择出现了异常——也就是所谓的脑裂问题。这样的脑裂状态直接让节点失去了集群的正确状态,导致集群不能正常工作
Elasticsearch 中脑裂产生的原因:
- 网络:由于是内网通信,网络通信问题造成某些节点认为 mas
ter 死掉,而另选 master 的可能性较小 - 节点负载:由于 master 节点与 data 节点都是混合在一起的,所以当工作节点的负载较大时,导致对应的 ES 实例停止响应, 而这台服务器如果正充当着 master 节点的身份,那么一部分 节点就会认为这个 master 节点失效了,故重新选举新的节点, 这时就出现了脑裂;同时由于 data 节点上 ES 进程占用的内存较大,较大规模的内存回收操作也能造成 ES 进程失去响应。
- Elasticsearch 中脑裂解决
- 主节点
node.master: true
node.data: false - 从节点
node.master: false
node.data: true - 所有节点
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: [“slave1”, “master” ,“slave2"]
Elasticsearch 的优化
- 调大系统的"最大打开文件数",建议 32K 甚至是 64K
- ulimit -a (查看)
- ulimit -n 32000(设置)
- 修改配置文件调整 ES 的 JVM 内存大小
修改 bin/elasticsearch.in.sh 中 ES_MIN_MEM 和 ES_MAX_MEM 的大小,建议设置一样大,避免频繁的分配内存,根据服务器内存大小,一般分配 60%左右(默认 256M) - 设置 mlockall 来锁定进程的物理内存地址
- 避免交换(swapped)来提高性能
- 修改文件 conf/elasticsearch.yml
bootstrap.mlockall: true
- 分片多的话,可以提升建立索引的能力,5-20 个比较合适。
- 如果分片数过少或过多,都会导致检索比较慢。分片数过多会导致
检索时打开比较多的文件,另外也会导致多台服务器之间通讯。而分
片数过少会导至单个分片索引过大,所以检索速度慢。建议单个分片
最多存储 20G 左右的索引数据,所以,分片数量=数据总量/20G - 副本多的话,可以提升搜索的能力,但是如果设置很多副本的话也
会对服务器造成额外的压力,因为需要同步数据。所以建议设置 2-3
个即可。 - 要定时对索引进行优化,不然 segment 越多,查询的性能就越差
- 索引量不是很大的话情况下可以将 segment 设置为 1
curl -XPOST ‘http://localhost:9200/shsxt/_optimize?max_num_segments=1’ - java 码:client.admin().indices().prepareOptimize(“shsxt")
.setMaxNumSegments(1).get();
- 删除文档:在 Lucene 中删除文档,数据不会马上在硬盘上除去,而是在 lucene 索引中产生一个.del 的文件,而在检索过程中这部分数据也会参与检索,lucene 在检索过程会判断是否删除了,如果删除了在过滤掉。这样也会降低检索效率。所以可以执行清除删除文档
-
curl -XPOST ‘http://localhost:9200/elasticsearch/_optimize?only_expunge_deletes=true’
-
client.admin().indices().prepareOptimize(“elasticsearch”)
.setOnlyExpungeDeletes(true).get();
- 如果在项目开始的时候需要批量入库大量数据的话,建议将副本数设置为 0 – 因为 es 在索引数据的时候,如果有副本存在,数据也会马上同步到副本中,这样会对 es 增加压力。待索引完成后将副本按需要改回来。这样可以提高索引效率
- 去掉 mapping 中_all 域
ElasticSearch 默认为每个被索引的文档都定义了一个特殊的域- ‘_all’,它自动包含被索引文档中一个或者多个域中的内容, 在进行搜索时,如果不指明要搜索的文档的域,ElasticSearch 则会去搜索_all 域。_all 带来搜索方便,其代价是增加了系统在索引阶段对 CPU 和存储空间资源的开销.
可以使用"_all" : {“enabled”:false} 开关禁用它
Elasticsearch 使用经验谈
- 在使用 java 代码操作 es 集群的时候要保证本地使用的 es 的版本
和集群上 es 的版本保持一致。 - 保证集群中每个节点的 JDK 版本和 es 配置一致
Elasticsearch 的分片规则 - elasticsearch 在建立索引时,根据 id 或 id,类型进行 hash, 得
到 hash 值与该索引库的分片数量取余,取余的值即为存入的分片 I
D。 - 具体源码为:根据 OperationRouting 类 generateShardId 方法进
行分片