[关闭]
@xtccc 2016-06-05T09:18:15.000000Z 字数 15166 阅读 4733

概念、部署、运行

给我写信
GitHub

此处输入图片的描述


ElasticSearch


ElasticSearch是一个分布式的全文检索分析引擎(full-text search and analytics engine),它可以帮助你存储海量的数据,并以近乎实时的方式来实现检索和分析这些海量数据。

注意这里,不仅仅是检索数据,还可以分析数据。

本文将描述怎样来部署ElasticSearch,并简单地用其来对文本进行检索。




1. 环境及软件


Elastic Search 2.3.3
Java (至少也要Java 7,推荐JDK - 1.8.0_73)




2. 基本概念


参考 : Basic Concepts



存储数据的过程,称为indexing。To index a document, we must tell ElasticSearch which type in the index it should go to.

在ES中,一个field属于一个document,一个document属于一个type,一个type属于一个index。换言之,一个index中包含了若干个type,一个type中包含了若干个document,一个document中包含了多个field。

下面是ES与Relational DB的概念类比:

ElasticSearch 关系型数据库
index database
type table
document row
field column




3. 快速安装(单节点集群)


  1. 下载 ElasticSearch 2.3.3

  2. 进入bin目录,启动一个single node cluster

  1. cd bin
  2. ./elasticsearch

控制台输出如下
image_1akfmrcpr5o31j4u9ho1t2q1qssn.png-268.9kB

可以看到,此时,本节点的名称为 Adam II(这只是一个random Marvel character),并且已经将自己设为所在集群(默认名称为“elasticsearch”)的master。

也可以在启动时指定集群和节点的名字:

  1. ./elasticsearch --cluster.name xt_es_cluster --node.name xt_es_ecs1

image_1akfn3tmp1sa11q521ij81rotakj14.png-283.6kB



如果想在后台启动ES,使用

  1. bin/elasticsearch -d

在后台启动ES后,如果想关闭ES,则可以使用如下的API:

  1. [root@ecs1 elasticsearch-1.7.2]# curl -XPOST localhost:9200/_shutdown?pretty
  2. {
  3. "cluster_name" : "xt_es_cluster",
  4. "nodes" : {
  5. "sIEyLCUgRnenH2BBMcXO4Q" : {
  6. "name" : "ecs1"
  7. }
  8. }
  9. }



从标有 http 的那一行可以看出,我们的节点可以通过IP 127.0.0.1与端口9200来访问。默认情况下,ES通过端口9200来提供对REST API的访问。



4. 查看cluster相关数据


我们使用 _cat API来监控集群的状态数据。

  1. curl 'ecs1:9200/_cat/health?v'
  1. curl 'ecs1:9200/_cat/nodes?v'




5. 配置


ES的配置文件位于三处:


5.1 配置Cluster Name

在文件 config/elasticsearch.yml 中配置 cluster.name
一个刚启动的ES节点,会通过组播(multicast)的方式来寻找当前已有cluster。如果该节点的cluster.name与某一个cluster的名字相同,则会加入该cluster。

5.2 配置JVM Settings

在文件 bin/elasticsearch.in.sh 中可以在文件的开头加上ES_HEAP_SIZE=500m

5.3 组建集群




6. 基本的REST API



API的形式

QQ20151207-3@2x.png-148.7kB

Request body是一个JSON结构,用单引号包围起来。如果request body中包含单引号,可以如下解决: 先结束单引号,然后在需要引入的单引号两边加上双引号,如下:

  1. [root@ecs2 ~]# curl -XPUT 'ecs1:9200/get-together/group/1?pretty' -d '{
  2. "name" : "Elasticsearch '"'"S"'"'Denver",
  3. "organizer" : "Lee"
  4. }'
  5. {
  6. "_index" : "get-together",
  7. "_type" : "group",
  8. "_id" : "1",
  9. "_version" : 4,
  10. "created" : false
  11. }
  12. [root@ecs2 ~]# curl 'ecs1:9200/get-together/group/1?pretty'
  13. {
  14. "_index" : "get-together",
  15. "_type" : "group",
  16. "_id" : "1",
  17. "_version" : 4,
  18. "found" : true,
  19. "_source":{
  20. "name" : "Elasticsearch 'S'Denver",
  21. "organizer" : "Lee"
  22. }
  23. }


集群状态

通过 _cat API 来获取集群的健康状态

  1. [root@ecs1 ~]# curl 'localhost:9200/_cat/health?v'
  2. epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks
  3. 1444357721 10:28:41 xt_cluster_name yellow 1 1 5 5 0 0 5 0

其中,集群有greenyellowred三种健康状态,它们的意义为:

green : everything is good;
yellow : all data is available but some replicas are not yet allocated;
red : some data is not available.

或者:

  1. [root@ecs1 elasticsearch-1.7.2]# curl localhost:9200/?pretty
  2. {
  3. "status" : 200,
  4. "name" : "ecs1",
  5. "cluster_name" : "xt_es_cluster",
  6. "version" : {
  7. "number" : "1.7.2",
  8. "build_hash" : "e43676b1385b8125d647f593f7202acbd816e8ec",
  9. "build_timestamp" : "2015-09-14T09:49:53Z",
  10. "build_snapshot" : false,
  11. "lucene_version" : "4.10.4"
  12. },
  13. "tagline" : "You Know, for Search"
  14. }

还可以通过 _cat API 来获取集群的节点的数量

  1. [root@ecs1 ~]# curl 'localhost:9200/_cat/nodes?v'
  2. host ip heap.percent ram.percent load node.role master name
  3. ecs1.njzd.com 10.163.104.81 7 41 0.00 d * xt_node_name


打印Index列表

  1. [root@ecs1 elasticsearch-1.7.2]# curl localhost:9200/_cat/indices?v
  2. health status index pri rep docs.count docs.deleted store.size pri.store.size
  3. green open tweet 5 1 0 0 1.3kb 720b
  4. green open kimchy 5 1 0 0 1.1kb 648b
  5. green open twitter 5 1 0 0 1.2kb 720b


创建Index

下面创建一个名为customer的Index,然后列出所有的Index

  1. [root@ecs1 ~]# curl -XPUT 'localhost:9200/customer?pretty'
  2. {
  3. "acknowledged" : true
  4. }
  5. [root@ecs1 ~]# curl 'localhost:9200/_cat/indices?v'
  6. health status index pri rep docs.count docs.deleted store.size pri.store.size
  7. yellow open customer 5 1 0 0 575b 575b

这里的pretty表示要求pretty-print the JSON response (if any).
注意这个index的状态为yellow(因为存在replica没有分配)。

当我们插入一个文档时,如果对应的Index还不存在,那么该index会自动地被创建。既然这样,为什么要主动地事先创建Index呢?

Creating the index itself takes more time than creating a document, so you might want to have the index ready beforehand. Another reason to create indices in advance is if you want to specify different settings than the ones Elasticsearch defaults to, for example, you may want a specific number of shards.


Delete an Index

  1. [root@ecs1 ~]# curl -XDELETE 'localhost:9200/customer?pretty'
  2. {
  3. "acknowledged" : true
  4. }


访问数据的普遍形式

  1. curl -X<REST Verb> <Node>:<Port>/<Index>/<Type>/<ID>


Modify Data

Indexing/Replacing Documents

curl -XPUT <Node>:<Port>/<Index>/<Type>/<ID>?pretty' -d <new-doc>
就可以:如果该文档已存在(由Index、Type和ID三者共同决定是否已存在),则会被新文档覆盖掉;否则,会创建新的文档。


Updating Documents

Elasticsearch does not actually do in-place updates under the hood. Whenever we do an update, Elasticsearch deletes the old document and then indexes a new document with the update applied to it in one shot.

  1. [root@ecs1 ~]# curl -XGET 'localhost:9200/customer/external/1?pretty'
  2. {
  3. "_index" : "customer",
  4. "_type" : "external",
  5. "_id" : "1",
  6. "_version" : 1,
  7. "found" : true,
  8. "_source":{"name":"xt"}
  9. }
  10. [root@ecs1 ~]# curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '{ "doc" : {"name":"xiao tao"}}'
  11. {
  12. "_index" : "customer",
  13. "_type" : "external",
  14. "_id" : "1",
  15. "_version" : 2
  16. }
  1. [root@ecs1 ~]# curl -XGET 'localhost:9200/customer/external/1?pretty'
  2. {
  3. "_index" : "customer",
  4. "_type" : "external",
  5. "_id" : "1",
  6. "_version" : 2,
  7. "found" : true,
  8. "_source":{"name":"xiao tao"}
  9. }
  10. [root@ecs1 ~]# curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '{ "doc" : {"name":"xiao tao", "age":30}}'
  11. {
  12. "_index" : "customer",
  13. "_type" : "external",
  14. "_id" : "1",
  15. "_version" : 3
  16. }

这里,age可以是整型。

还可以通过scripts来更新文档(但是这个特性默认为disabled),如下:

  1. [root@ecs1 ~]# curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '{"script" : "ctx._source.age += 5" }'

ctx._source refers to the current source document that is about to be updated.


Deleting Documents

删除一个文档:

  1. [root@ecs1 ~]# curl -XDELETE 'localhost:9200/customer/external/1?pretty'

也可以用QUERY一次性删除多个文档。下面删除『customer, external』中name域包含“xiaotao”的全部文档:

  1. [root@ecs1 ~]# curl -XDELETE 'localhost:9200/customer/external/_query?pretty' -d '{"query": {"match":{"name":"xiaotao"}}}'


Batch Processing

通过 _bulk API,可以实现批量操作。

Index multiple documents

  1. [root@ecs1 ~]# curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
  2. {"index":{"_id":"1"}}
  3. {"name":"XT"}
  4. {"index":{"_id":"2"}}
  5. {"name":"CCC"}
  6. '
  7. [root@ecs1 ~]# curl 'localhost:9200/customer/external/1?pretty'
  8. {
  9. "_index" : "customer",
  10. "_type" : "external",
  11. "_id" : "1",
  12. "_version" : 5,
  13. "found" : true,
  14. "_source":{"name":"XT"}
  15. }
  16. [root@ecs1 ~]# curl 'localhost:9200/customer/external/2?pretty'
  17. {
  18. "_index" : "customer",
  19. "_type" : "external",
  20. "_id" : "2",
  21. "_version" : 3,
  22. "found" : true,
  23. "_source":{"name":"CCC"}
  24. }


Multiple Types of Operations

  1. [root@ecs1 ~]# curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
  2. {"update": {"_id":"1"}}
  3. {"doc":{"name":"Lalala"}}
  4. {"delete":{"_id":"2"}}
  5. '
  6. [root@ecs1 ~]# curl 'localhost:9200/customer/external/1?pretty'
  7. {
  8. "_index" : "customer",
  9. "_type" : "external",
  10. "_id" : "1",
  11. "_version" : 6,
  12. "found" : true,
  13. "_source":{"name":"Lalala"}
  14. }
  15. [root@ecs1 ~]# curl 'localhost:9200/customer/external/2?pretty'
  16. {
  17. "_index" : "customer",
  18. "_type" : "external",
  19. "_id" : "2",
  20. "found" : false
  21. }



当批量操作Bulk API返回时,将会为每个action都返回状态码(且是按照其执行的顺序返回的)。


Exploring Data

Loading Data From JSON File

首先从这里下载一个JSON格式的样本数据文件(里面含有1000个documents),并从中解压出一个名为accounts.json的文件。

然后,将其加载到ES中:

  1. [root@ecs1 data]# curl -XPOST 'localhost:9200/bank/account/_bulk?pretty' --data-binary "@accounts.json"
  2. [root@ecs1 data]# curl 'localhost:9200/_cat/indices?v'
  3. health status index pri rep docs.count docs.deleted store.size pri.store.size
  4. yellow open bank 5 1 1000 0 417.1kb 417.1kb
  5. yellow open customer 5 1 3 0 7.8kb 7.8kb


The Search API

进行search有两种方式:
1. sending search parameters through the REST request URI
2. sending search parameters through the REST request body

第二种方法显然更好。

可以通过_search来访问关于搜索的REST API。


The Query Language

下面举几个Query Parameter的例子

  1. 返回全部的文档

    1. {
    2. "query" : { "match_all": {} }
    3. }
  2. 指定返回文档的数量(默认值为10)

    1. {
    2. "query" : { "match_all": {} },
    3. "size" : 5
    4. }
  3. 分页查询 (from是zero-based,其默认值为0)

    1. {
    2. "query" : { "match_all": {} },
    3. "from" : 3,
    4. "size" : 5
    5. }
  4. 结果排序(不指定size则默认返回10条结果)

    1. {
    2. "query" : { "match_all" : {} },
    3. "sort" : { "balance" : {"order":"desc"} }
    4. }


Executing Searches

  1. 查询结果只返回指定fields

    当搜索返回结果时,默认情况是返回full JSON document(即返回结果中的_source域)。但是我们也可以要求只返回指定的一些fields。

    1. [root@ecs1 data]# curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    2. {
    3. "query" : { "match_all" : {} },
    4. "sort" : { "balance" : {"order":"desc"} },
    5. "_source":["balance", "account_number"]
    6. }'


  2. 条件查询
    通过match实现。例如下面我们的Query要求account_number为20

    1. [root@ecs1 data]# curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    2. {
    3. "query" : { "match" : {"account_number": 20} }
    4. }'



    下面的Query要求address域包含mill

    1. [root@ecs1 data]# curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    2. {
    3. "query" : { "match" : {"address": "milL"} },
    4. "_source":["address"]
    5. }'

    注意:这里的mill不区分大小写,且满足要求的address域的值必须包含完整的mill,如果改为mil则不会返回任何结果。



    下面的Query要求address域包含mill或者lane

    1. [root@ecs1 data]# curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    2. {
    3. "query" : { "match" : {"address": "mill lane"} },
    4. "_source":["address"]
    5. }'



    下面的Query要求address域包含mill lane这个词组:

    1. [root@ecs1 data]# curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    2. {
    3. "query" : { "match_phrase" : {"address": "mill lane"} },
    4. "_source":["address"]
    5. }'


  3. 布尔条件查询

    • AND

      1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
      2. {
      3. "query" : {
      4. "bool" : {
      5. "must" : [
      6. { "match" : { "address" : "mill" } },
      7. { "match" : { "address" : "lane" } }
      8. ]
      9. }
      10. }
      11. }'

      bool must子句要求所有的query条件都必须满足。


    • OR

      1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
      2. {
      3. "query" : {
      4. "bool" : {
      5. "should" : [
      6. { "match" : { "address" : "mill" } },
      7. { "match" : { "address" : "lane" } }
      8. ]
      9. }
      10. }
      11. }'

      bool should要求至少有一个query条件满足。


    • ALL NOT

      1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
      2. {
      3. "query" : {
      4. "bool" : {
      5. "must_not" : [
      6. { "match" : { "address" : "mill" } },
      7. { "match" : { "address" : "lane" } }
      8. ]
      9. }
      10. }
      11. }'

      bool must_not要求所有的query条件都不能满足,即所有的子句都返回false。

    • 条件组合

      1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
      2. {
      3. "query" : {
      4. "bool" : {
      5. "should" : [
      6. { "match" : { "address" : "mill" } },
      7. { "match" : { "address" : "lane" } }
      8. ],
      9. "must" : [
      10. { "match" : { "balance" : 45975} }
      11. ]
      12. }
      13. }
      14. }'


Executing Filters

在查询的结果中,会有一个名为_score的域,它衡量了document与query在多大程度上匹配。 ES中的任何Query都会触发对相关度的计算,如果不需要相关度的话,则可以通过filter来进行查询。

Filter与Query很相似,但是速度更快:

  • Filters do not score so they are faster to execute than queries
  • Filters can be cached in memory allowing repeated search executions to be significantly faster than queries

下面来看一个filtered query例子

  1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
  2. {
  3. "query": {
  4. "filtered": {
  5. "query": { "match_all": {} },
  6. "filter": {
  7. "range":{
  8. "balance": {
  9. "gte": 20000,
  10. "lte": 30000
  11. }
  12. }
  13. }
  14. }
  15. }
  16. }'


Executing Aggregations

参考 aggregations reference guide

例1

下面我们根据state进行group,然后返回按照降序(默认降序)排名前10(也是默认的)的结果:

  1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "group_by_state": {
  6. "terms": {
  7. "field": "state"
  8. }
  9. }
  10. }
  11. }'

size设置为0的原因是为了不显示search hits,只显示aggregation results。

以上的查询语句类似于

  1. SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC

输出结果为

  1. {
  2. "took" : 72,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 5,
  6. "successful" : 5,
  7. "failed" : 0
  8. },
  9. "hits" : {
  10. "total" : 1000,
  11. "max_score" : 0.0,
  12. "hits" : [ ]
  13. },
  14. "aggregations" : {
  15. "group_by_state" : {
  16. "doc_count_error_upper_bound" : 5,
  17. "sum_other_doc_count" : 744,
  18. "buckets" : [ {
  19. "key" : "tx",
  20. "doc_count" : 30
  21. }, {
  22. "key" : "md",
  23. "doc_count" : 28
  24. }, {
  25. "key" : "id",
  26. "doc_count" : 27
  27. }, {
  28. "key" : "al",
  29. "doc_count" : 25
  30. }, {
  31. "key" : "me",
  32. "doc_count" : 25
  33. }, {
  34. "key" : "wy",
  35. "doc_count" : 25
  36. }, {
  37. "key" : "dc",
  38. "doc_count" : 24
  39. }, {
  40. "key" : "ma",
  41. "doc_count" : 24
  42. }, {
  43. "key" : "nd",
  44. "doc_count" : 24
  45. }, {
  46. "key" : "tn",
  47. "doc_count" : 24
  48. } ]
  49. }
  50. }
  51. }



例2

在例1的基础上,我们按照州来计算balance的平均值(依然是按照降序排名前10):

  1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "group_by_state": {
  6. "terms": {
  7. "field": "state"
  8. },
  9. "aggs": {
  10. "average_balance": {
  11. "avg": {
  12. "field": "balance"
  13. }
  14. }
  15. }
  16. }
  17. }
  18. }'



例3
例2中是按照返回结果中的doc_count的降序排列的,我们现在希望按照balance的平均值以降序排列:

  1. curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "group_by_state": {
  6. "terms": {
  7. "field": "state",
  8. "order": {
  9. "average_balance": "desc"
  10. }
  11. },
  12. "aggs": {
  13. "average_balance": {
  14. "avg": {
  15. "field": "balance"
  16. }
  17. }
  18. }
  19. }
  20. }
  21. }'



例4
按照多个field来进行aggregate。


ES的安装、部署、配置

Environment Variables

在启动ES时,会将一个内置的JAVA_OPTS参数传递至JVM。

System Configuration

File Desciptor

Open file descriptor的数量推荐设置到32K或者64K。
可以通过 Nodes Info API 来查询当前max_file_descriptors的值。

  1. [root@ecs1 ~]# curl localhost:9200/_nodes/process?pretty


Virtual Memory

ES默认使用 hybrid mmapfs / niofs directory 来存储indices。而OS对mmap count的默认limit很低,易造成 out of memory 异常,因此需要如下操作:

  1. [root@ecs1 ~]# sysctl -w vm.max_map_count=262144

如果想让该设置永久生效,在文件/etc/sysctl.conf中修改vm.max_map_count的值。

Memory Settings

Swapping对性能的影响非常大(可能会把ES的进程交换出去),因此应该尽量避免Swapping的发生。

ElasticSearch Settings

在config/elasticsearch.yml 中可以配置以下值:

cluster.name: xt_es_cluster
node.name: ${HOSTNAME}
path.data: /path/to/data1,/path/to/data2,/path/to/data3


Index Setting

在创建Index时,ES在默认情况下是将Index存储在文件系统中,我们可以要求它将Index存储在内存中。

  1. [root@ecs1 elasticsearch-1.7.2]# curl -XPUT localhost:9200/kimchy/ -d \
  2. '
  3. index :
  4. store :
  5. type : memory
  6. '

或者在启动ES时指定这个配置:

  1. elasticsearch -Des.index.store.type=memory
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注