分布式全文搜索引擎ElasticSearch入门详解

1 ElasticSearch

1.1 为什么要使用ElasticSearch

虽然全文搜索领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。
但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene的配置及使用非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
实际项目中,我们建立一个网站或应用程序,并要添加搜索功能,令我们受打击的是:搜索工作是很难的。我们希望我们的搜索解决方案要快,我们希望有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON/XML通过HTTP的索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并在需要扩容时方便地扩展到数百,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。

1.2 ElasticSearch的特点

ES即为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案,其第一个版本于2010年2月出现在GitHub上并迅速成为最受欢迎的项目之一。
首先,ES的索引库管理支持依然是基于Apache Lucene™的开源搜索引擎。
ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
不过,ES的核心不在于Lucene,其特点更多的体现为:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可搜索PB量级的数据
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据
  • 高度集成化的服务,你的应用可以通过简单的 RESTful API、各种语言的客户端甚至命令行与之交互。
  • 上手Elasticsearch非常容易。它提供了许多合理的缺省值,并对初学者隐藏了复杂的搜索引擎理论。它拥有开瓶即饮的效果(安装即可使用),只需很少的学习既可在生产环境中使用。

2 ElasticSearch安装及使用说明

2.1安装ES

ES服务只依赖于JDK,推荐使用JDK1.7+。
(1)下载ES安装包:ES官方下载地址
(2)运行ES (双击bin目录下的elasticsearch.bat)
(3)验证是否运行成功,访问:http://localhost:9200/
如果看到如下信息,则说明ES集群已经启动并且正常运行。
分布式全文搜索引擎ElasticSearch入门详解_第1张图片

2.2 ES交互方式客户端

(1)基于RESTful API
ES和所有客户端的交互都是使用JSON格式的数据.
其他所有程序语言都可以使用RESTful API,通过9200端口的与ES进行通信,在开发测试阶段,你可以使用你喜欢的WEB客户端, curl命令以及火狐的POSTER插件方式和ES通信。
Curl命令方式:
默认windows下不支持curl命令,在资料中有curl的工具及简单使用说明。
分布式全文搜索引擎ElasticSearch入门详解_第2张图片
火狐的POSTER插件界面:
类似于Firebug,在火狐的“扩展”中搜索“POSTER”,并安装改扩展工具。
在这里插入图片描述
使用POSTER模拟请求的效果
分布式全文搜索引擎ElasticSearch入门详解_第3张图片
(2)Java API
ES为Java用户提供了两种内置客户端:
节点客户端(node client):
节点客户端以无数据节点(none data node)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。
传输客户端(Transport client):
这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。
两个Java客户端都通过9300端口与集群交互,使用ES传输协议(ES Transport Protocol)。集群中的节点
之间也通过9300端口进行通信。如果此端口未开放,你的节点将不能组成集群。
注意
Java客户端所在的ES版本必须与集群中其他节点一致,否则,它们可能互相无法识别。

2.3 辅助管理工具Kibana5

(1)Kibana5.2.2下载地址:Kibana官方下载地址
(2)解压并编辑config/kibana.yml,设置elasticsearch.url的值为已启动的ES
(3)启动Kibana5 (在bin目录下双击kibana.bat)
(4)验证是否成功,默认访问地址:http://localhost:5601

2.4 head工具入门+postman

(1)进入head文件中,输入cmd,打开控制台,输入npm install进行安装。
(2)安装完成,输入命令npm run start启动服务
(3)配置允许跨域访问,在elasticsearch/config/elasticsearch.yml文件末尾加上
http.cors.enabled: true
http.cors.allow-origin: “*”
(4)重启elasticsearch服务,访问http://localhost:9100

3 ES的基本操作

3.1 ES的CRUD

#新增
PUT crm/department/1
{
  "name":"财务部",
  "sn":"cwb"
}
#查询
GET crm/department/1

#修改
POST crm/department/1
{
  "name":"人事部",
  "sn":"rsb"
}

#删除
DELETE crm/department/1

#如果没有指定id,则自动生成id
POST crm/department
{
  "name":"开发部",
  "sn":"kfb"
}

# AW8jt-oSqTn8hjKcCo2i
GET crm/department/AW8jt-oSqTn8hjKcCo2i

# 查询全部
GET _search

3.2 ES的特殊写法

# 查询所有
GET _search

#打印出漂亮格式
GET crm/department/AW8jt-oSqTn8hjKcCo2i?pretty

#指定返回的列
GET crm/department/AW8jt-oSqTn8hjKcCo2i?_source=name,sn

#不要元数据 只返回具体数据
GET crm/department/AW8jt-oSqTn8hjKcCo2i/_source

3.3 局部修改

#修改 --覆盖以前json
POST crm/department/AW8jt-oSqTn8hjKcCo2i
{
  "name":"市场部"
}

#局部更新
POST crm/department/AW8jt-oSqTn8hjKcCo2i/_update
{
  "doc":{
    "name":"管理部"
    }
}

3.4 批量操作

POST _bulk
{ "delete": { "_index": "xlj", "_type": "department", "_id": "123" }}
{ "create": { "_index": "xlj", "_type": "book", "_id": "123" }}
{ "title": "我发行的第一本书" }
{ "index": { "_index": "itsource", "_type": "book" }}
{ "title": "我发行的第二本书" }

# 普通查询:
GET  crm/department/id
# 批量查询:
GET xlj/book/_mget
{
  "ids" : [ "123", "AH8ht-oSqTn8hjKcHo2i" ]
}

3.5 查询条件

# 从第0条开始查询3条student信息
GET crm/student/_search?size=3

# 从第2条开始查询2条student信息
GET crm/student/_search?from=2&size=2

#  表示查询age=15的人
GET crm/student/_search?q=age:15

# 查询3条student的信息,他们的age范围到1020
GET crm/student/_search?size=3&q=age[10 TO 20] 

4 DSL查询与过滤

4.1 什么是DSL

由ES提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。
DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。
DSL分成两部分:DSL查询 、DSL过滤。

4.2 DSL过滤和DSL查询的区别

(1)过滤结果可以缓存并应用到后续请求。
​(2)查询语句同时匹配文档,计算相关性,所以更耗时,且不缓存。
(3)过滤语句可有效地配合查询语句完成文档过滤。

总之在原则上,使用DSL查询做全文本搜索或其他需要进行相关性评分的场景,其它全用DSL过滤。

4.3 使用DSL查询与过滤

4.3.1 全匹配(match_all)普通搜索,匹配所有文档

{
	"query" : {
		"match_all" : {}
	}
}

如果需要使用过滤条件:

{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": {
        "term": {
          "name": "zs1"
        }
      }
    }
  }
}

4.3.2 标准查询(match和multi_match)

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。
如果你使用match查询一个全文本字段,它会在真正查询之前用分析器先分析查询字符:

{
	"query": {
		"match": {
			"fullName": "Steven King"
		}
	}
}

上面的搜索会对Steven King分词,并找到包含Steven或King的文档,然后给出排序分值。如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed的字符串时,它将会为你搜索你给定的值,如:

{ "match": { "age": 26 }}
{ "match": { "date": "2014-09-01" }}
{ "match": { "public": true }}
{ "match": { "tag": "full_text" }}

multi_match查询允许你做match查询的基础上同时搜索多个字段:

{
	"query":{
		"multi_match": {
			"query": "Steven King",
			"fields": [ "fullName", "title" ]
		}
	}
}

fullName = ‘steven King’ or tile = ‘steven King’
上面的搜索同时在fullName和title字段中匹配。
提示:match一般只用于全文字段的匹配与查询,一般不用于过滤。

4.3.3 单词搜索与过滤(Term和Terms)

{
	"query": {
		"bool": {
			"must": { 
				"match_all": {} 
			}, 
			"filter": { 
				"term": { 
					"tags": "elasticsearch" 
				} 
			} 
		} 
	}
}

Terms搜索与过滤:

{
	"query": {
		"terms": {
			"tags": ["jvm", "hadoop", "lucene"],
			"minimum_match": 2
		}
	}
}

minimum_match:至少匹配个数,默认为1

4.3.4 组合条件搜索与过滤(Bool)

组合搜索bool可以组合多个查询条件为一个查询对象,查询条件包括must、should和must_not。
例如:查询爱好有篮球,同时也有喜欢游戏或者运动的,且出生于1996-09-02及之后的人。

{
	"query": {
		"bool": {
			"must": [{"term": {"hobby": "篮球"}}],
			"should": [{"term": {"hobby": "游戏"}}, 
							 {"term": {"hobby": "运动"}} 
							],
			"must_not": [
				{"range" :{"birth_date":{"lt": "1996-09-02"}}} 
			],
        	"filter": [...],
			"minimum_should_match": 1
		}
	}
}

注意:如果bool查询下没有must子句,那至少应该有一个should自居。但是如果有must子句,那么没有should子句也可以进行查询。

4.3.5 范围查询与过滤(range)

range过滤允许我们按照指定范围查找一批数据:

{
	"query":{
		"range": {
			"age": {
				"gte": 20,
				"lt": 30
			}
		}
	}
}

上例中查询年龄大于等于20并且小于30的人
gt:> gte:>= lt:< lte:<=

4.3.6 存在和缺失过滤器(exists和missing)

{
	"query": {
		"bool": {
			"must": [{
				"match_all": {}
			}],
			"filter": {
				"exists": { "field": "gps" }
			}
		}
	}
}

提示:exists和missing只能用于过滤结果。

4.3.7 前匹配搜索与过滤(prefix)

和term查询相似,前匹配搜索不是精确匹配,而是类似于SQL中的like ‘key%’

{
	"query": {
		"prefix": {
			"fullName": "王"
		}
	}
}

上例即查询所有姓王的人。

4.3.8 通配符搜索(wildcard)

使用*代表0~N个,使用?代表一个。

{
	"query": {
		"wildcard": {
			"fullName": "王*华"
		}
	}
}

表示查询所有姓名以王开头华结尾的人。

5 分词与映射

5.1 为什么要使用分词与映射

在全文检索理论中,文档的查询是通过关键字查询文档索引来进行匹配,因此将文本拆分为有意义的单词,对于搜索结果的准确性至关重要,因此,在建立索引的过程中和分析搜索语句的过程中都需要对文本串分词。
ES中分词需要对具体字段指定分词器等细节,因此需要在文档的映射中明确指出。

5.2 IK分词器

ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成IK分词器。

  • ES的IK分词器插件源码地址:ES的IK分词器插件源码地址
  • 下载之后解压文件,并将其内容放置于ES根目录/plugins/ik中
  • 分词器的词典配置在config/IKanalyzer.cfg.xml中配置
  • 重启ES
  • 测试分词器
POST _analyze
{
  "analyzer":"ik_smart",
  "text":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
}

注意:IK分词器有两种类型,分别是ik_smart分词器和ik_max_word分词器。

  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
  • ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合。

5.3 文档映射Mapper

ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型。

5.3.1 ES字段类型

(1)基本字段类型

  • 字符串:text(分词),keyword(不分词) StringField(不分词文本),TextFiled(要分词文本)
    text默认为全文文本,keyword默认为非全文文本
  • 数字:long,integer,short,double,float
  • 日期:date
  • 逻辑:boolean
  {user:{“key”:value}{hobbys:[xxx,xx]}

(2)复杂数据类型

  • 对象类型:object
  • 数组类型:array
  • 地理位置:geo_point,geo_shape

5.3.2 默认映射

查看索引类型的映射配置:GET {indexName}/_mapping/{typeName}
ES在没有配置Mapping的情况下新增文档,ES会尝试对字段类型进行猜测,并动态生成字段和类型的映射关系。

JSON type Field type
Boolean: true or false “boolean”
Whole number: 123 “long”
Floating point: 123.45 “double”
String, valid date:“2014-09-15” “date”
String: “foo bar” “string”

5.3.3 简单类型映射

字段映射的常用属性配置列表:

type 类型:基本数据类型,integer,long,date,boolean,keyword,text…
enable 是否启用:默认为true。 false:不能索引、不能搜索过滤,仅在_source中存储
boost 权重提升倍数:用于查询时加权计算最终的得分
format 格式:一般用于指定日期格式,如 yyyy-MM-dd HH:mm:ss.SSS
ignore_above 长度限制:长度大于该值的字符串将不会被索引和存储
ignore_malformed 转换错误忽略:true代表当格式转换错误时,忽略该值,被忽略后不会被存储和索引
include_in_all 转换错误忽略:true代表当格式转换错误时,忽略该值,被忽略后不会被存储和索引
null_value 默认控制替换值。如空字符串替换为”NULL”,空数字替换为-1
store 是否存储:默认为false。true意义不大,因为_source中已有数据
index 索引模式:analyzed (索引并分词,text默认模式), not_analyzed (索引不分词,keyword默认模式),no(不索引)
analyzer 索引分词器:索引创建时使用的分词器,如ik_smart,ik_max_word,standard
search_analyzer 搜索分词器:搜索该字段的值时,传入的查询内容的分词器
fields 多字段索引:当对该字段需要使用多种索引模式时使用。有些类型 有时候需要分词 有时候不需要分词

如:城市搜索New York 下面字段city既可以分词有可以不分词

"city": {
     "type": "text",
     "analyzer": "ik_smart",
     "fields": {
            "raw": { 
                "type":  "keyword"
             }
     }
}

city分词
city.raw 不分词
那么以后搜索过滤和排序就可以使用city.raw字段名
(1)针对单个类型的映射配置方式

  • 查询映射类型:
    GET shop/goods/_mapping
  • 修改映射类型
    a.Delete shop;
    b.PUT shop;
    c.POST shop/goods/_mapping
{
	"goods": {
		"properties": {
			"price": {
			"type": "integer"
          },
		"name": {
			"type": "text",
			"analyzer": "ik_smart",
			"search_analyzer": "ik_smart"
           }
        }
    }
}

d.加入数据

put shop/goods/1
{
  "price":6888,
  "name": "iphone8"
}

注意:你可以在第一次创建索引的时候指定映射的类型。此外,你也可以晚些时候为新类型添加映射(或者为已有的类型更新映射)。
你可以向已有映射中增加字段,但你不能修改它。如果一个字段在映射中已经存在,这可能意味着那个字段的数据已经被索引。如果你改变了字段映射,那已经被索引的数据将错误并且不能被正确的搜索到。
(2)同时对多个类型的映射配置方式

PUT {indexName}
{
  "mappings": {
    "user": {
      "properties": {
        "id": {
          "type": "integer"
        },
        "info": {
          "type": "text",
          "analyzer": "ik_smart",
          "search_analyzer"
        }
      }
    },
    "dept": {
      "properties": {
        "id": {
          "type": "integer"
        },
        ....更多字段映射配置
      }
    }
  }
}

5.3.4 对象及数组类型的映射

(1)对象的映射与索引

{
	“id” : 1,
	“star” : {
   	 	“name” : “刘德华”,
    	“age”  : 45
	}
}

对应的mapping配置:

{ 
	"properties": {
    	"id": {"type": "long"},
    	"girl": {
			"properties":{
				"name": {"type": "keyword"},
				"age": {"type": "integer"}
			}
		}
     }
}

(2)数组与对象数组
a.数组的映射
注意:数组中元素的类型必须一致

{
	“id” : 1,
	“hobby” : [“吃东西”,“听音乐”]
}

对应的mapping配置是:

{ 
	"properties": {
            "id": {"type": "long"},
            "hobby": {"type": "keyword"}
     }
}

b.对象数组的映射

{
	"id" : 1,
	"star":[{"name":"刘德华","age":50},{"name":"黎明","age":51}]
}

对应的映射配置为:

"properties": {
"id": {
            "type": "long"
        },
        "star": {
            "properties": {
              "age": { "type": "long" },
              "name": { "type": "text" }
            }
        }
}
-----------put---------
[{
Age:50
Name:刘德华
},{
Age:51
Name:黎明
}]

注意:同内联对象一样,对象数组也会被扁平化索引

{
    "user1.star.age":    [50, 51],
    "user2.star.name":   ["刘德华", "黎明"]
}

5.3.5 全局映射

全局映射可以通过动态模板和默认设置两种方式实现。

  • 默认方式:default
    索引下所有的类型映射配置会继承_default_的配置,如:
PUT {indexName}
{
  "mappings": {
    "_default_": { 
      "_all": {
        "enabled": false 关闭默认映射配置
      }
    },
"user": {
	//指定自己的自定义配置
}, 
    "dept": { 
      "_all": {
        "enabled": true //启动默认配置
      }
},
....
  }
}

上例中:默认的enabled=false 表示关闭模式的配置,如果你想用,在自己的配置里面开启配置.

  • 动态模板:dynamic_templates
    注意:ES会默认把string类型的字段映射为text类型(默认使用标准分词器)和对应的keyword类型,如:
"name": {
     "type": "text",
     "fields": {
         "keyword": {
             "type": "keyword",
             "ignore_above": 256
          }
      }
}

在实际应用场景中,一个对象的属性中,需要全文检索的字段较少,大部分字符串不需要分词,因此,需要利用全局模板覆盖自带的默认模板:

PUT _template/global_template  //创建名为global_template的模板
{
  "template":   "*",  //匹配所有索引库
  "settings": { "number_of_shards": 1 }, //匹配到的索引库只创建1个主分片
  "mappings": {
    "_default_": {
      "_all": { 
        "enabled": false //关闭所有类型的_all字段
      },
      "dynamic_templates": [
        {
          "string_as_text": { 
            "match_mapping_type": "string",//匹配类型string
            "match":   "*_text", //匹配字段名字以_text结尾 a_text
            "mapping": {
              "type": "text",//将类型为string的字段映射为text类型
              "analyzer": "ik_max_word",
              "search_analyzer": "ik_max_word",
              "fields": {
                "raw": {
                  "type":  "keyword",
                  "ignore_above": 256
                }
              }
            }
          }
        }
      ]
    }
  }}

上面的意思:如果索引库里面字段是以_text结尾就需要进行分词,如果不是,就不分词。

6 ES集群

6.1 为什么需要集群

  • 解决单点故障问题
  • 解决高并发问题
  • 解决海量数据问题

6.2 ES集群相关概念

  • ES的1个集群中放多个节点,放多个shard(分片,shard又分成主shard和从shard)。
  • 具体解释请看文章:ES集群解析
  • ES节点类型Node有三种节点:
    master Node:主节点,维护集群信息 索引库操作
    data node:数据节点, 文档crud
    client node:只负责处理用户请求
  • 可以通过两个属性进行设置:node.master 和 node.data
  • 默认情况下,每个节点都有成为主节点的资格,也会存储数据,还会处理客户端的请求。- 在生产环境下,如果不修改ElasticSearch节点的角色信息,在高数据量,高并发的场景下集群容易出现脑裂等问题
  • 在一个生产集群中我们可以对这些节点的职责进行划分。建议集群中设置3台以上的节点作为master节点【node.master: true node.data: false】,这些节点只负责成为主节点,维护整个集群的状态。
  • 再根据数据量设置一批data节点【node.master: false node.data: true】,这些节点只负责存储数据,后期提供建立索引和查询索引的服务,这样的话如果用户请求比较频繁,这些节点的压力也会比较大。
  • 在集群中建议再设置一批client节点【node.master: false node.data: false】,这些节点只负责处理用户请求,实现请求转发,负载均衡等功能。

6.3 ES集群理解

6.3.1 单node环境

  • 单node环境下,创建一个index,有3个primary shard,3个replica shard
  • 集群status是yellow
  • 这个时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的
  • 集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法承接任何请求

6.3.2 2个node环境

  • replica shard分配:3个primary shard,3个replica shard,2 node
  • primary —> replica同步
  • 读请求:primary/replica

6.4 搭建ES集群

6.4.1 环境准备

真实环境:

NodeName Web端口,客户端端口
node-1 172.168.1.1:9200 172.168.1.1:9300
node-2 172.168.1.2:9200 172.168.1.2:9300
node-3 172.168.1.3:9200 172.168.1.3:9300

模拟环境:

NodeName Web端口,客户端端口
node-1 127.0.0.1:9201 127.0.0.1:9301
node-2 127.0.0.1:9202 127.0.0.1:9302
node-3 127.0.0.1:9203 127.0.0.1:9303

步骤:
(1)拷贝三份ES服务
(2)修改每个内存配置lg
(3)修改三个节点配置

6.4.2 修改三个节点配置

配置参数详解:

名称 解释
cluster.name 集群名,自定义集群名,默认为elasticsearch,建议修改,因为低版本多播模式下同一网段下相同集群名会自动加入同一集群,如生产环境这样易造成数据运维紊乱。
node.name 节点名,同一集群下要求每个节点的节点名不一致,起到区分节点和辨认节点作用
node.master 是否为主节点,选项为true或false,当为true时在集群启动时该节点为主节点,在宕机或任务挂掉之后会选举新的主节点,恢复后该节点依然为主节点
node.data 是否处理数据,选项为true或false。负责数据的相关操作
path.logs 默认日志路径
bootstrap.mlockall 内存锁,选项为true或false,用来确保用户在es-jvm中设置的ES_HEAP_SIZE参数内存可以使用一半以上而又不溢出
network.host 对外暴露的host,0.0.0.0时暴露给外网
http.port 对外访问的端口号,默认为9200,所以外界访问该节点一般为http://ip:9200/
transport.tcp.port 集群间通信的端口号,默认为9300
discovery.zen.ping.unicast.hosts 集群的ip集合,可指定端口,默认为9300,如 [“192.168.1.101”,“192.168.1.102”]
discovery.zen.minimum_master_nodes 最少的主节点个数,为了防止脑裂,最好设置为(总结点数/2 + 1)个
discovery.zen.ping_timeout 主节点选举超时时间设置
gateway.recover_after_nodes 值为n,网关控制在n个节点启动之后才恢复整个集群
node.max_local_storage_nodes 值为n,一个系统中最多启用节点个数为n
action.destructive_requires_name 选项为true或false,删除indices是否需要现实名字

配置信息:

  • node-1
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-1
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9201
#集群间通讯端口号
transport.tcp.port: 9301
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: [“127.0.0.1:9301”,”127.0.0.1:9302”,”127.0.0.1:9303”]
  • node-2:
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-2
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9202
#集群间通讯端口号
transport.tcp.port: 9302
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: [“127.0.0.1:9301”,”127.0.0.1:9302”,”127.0.0.1:9303”]
  • node-3
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-3
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9203
#集群间通讯端口号
transport.tcp.port: 9303
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: [“127.0.0.1:9301”,”127.0.0.1:9302”,”127.0.0.1:9303”]

6.4.3 启动并测试

分别启动创建索引,创建类型,插入文档。
分布式全文搜索引擎ElasticSearch入门详解_第4张图片
新增数据:
分布式全文搜索引擎ElasticSearch入门详解_第5张图片
查询数据:
分布式全文搜索引擎ElasticSearch入门详解_第6张图片

7 Java API

7.1 什么是JavaAPI

ES对Java提供一套操作索引库的工具包,即Java API。所有的ES操作都使用Client对象执行。
ES的Maven引入:

<dependency>
    <groupId>org.elasticsearch.clientgroupId>
    <artifactId>transportartifactId>
    <version>5.2.2version>
dependency>
<dependency>
    <groupId>org.apache.logging.log4jgroupId>
    <artifactId>log4j-apiartifactId>
    <version>2.7version>
dependency>
<dependency>
    <groupId>org.apache.logging.log4jgroupId>
    <artifactId>log4j-coreartifactId>
    <version>2.7version>
dependency>

7.2 链接ES获取Client对象

  • 方式一:把每台服务器的ip端口配上
    TransportClient 利用transport模块远程连接一个ES集群。它并不加入到集群中,只是简单的获得一个或者多个初始化的transport地址,并以轮询的方式与这些地址进行通信。
// on startup
TransportClient client = new PreBuiltTransportClient(Settings.EMPTY)
        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host1"), 9300))
        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host2"), 9300));
// on shutdown
client.close();
  • 方式二:通过集群名称来查找
    注意:如果你有一个与 ES集群不同的集群,你可以设置机器的名字。
Settings settings = Settings.builder()
        .put("cluster.name", "myClusterName").build();
TransportClient client = new PreBuiltTransportClient(settings);
//添加地址到client中
  • 方式三:嗅探方式(一般使用这种方式)
    你可以设置client.transport.sniff为true来使客户端去嗅探整个集群的状态,把集群中其它机器的ip地址加到客户端中,这样做的好处是一般你不用手动设置集群里所有集群的ip到连接客户端,它会自动帮你添加,并且自动发现新加入集群的机器。代码实例如下:
/**
     * 连接服务方法(嗅探方式)
     * @return
     * @throws Exception
     */
    public TransportClient getClient() throws Exception{
        Settings settings = Settings.builder()
                .put("client.transport.sniff", true).build();
        TransportClient client = new PreBuiltTransportClient(settings)
                .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
        return  client;
    }

7.3 具体操作

  • 新增数据
	/**
     * 新增数据
     * @throws Exception
     */
    @Test
    public void test() throws Exception{
        //得到client
        TransportClient client = getClient();
        IndexRequestBuilder builder = client.prepareIndex("xihua", "students", "1");
        Map mp = new HashMap();
        //存值
        mp.put("name","xiaoming");
        mp.put("age",18);
        builder.setSource(mp);
        IndexResponse indexResponse = builder.get();
        System.out.println(indexResponse);
        //关闭服务
        client.close();
    }
  • 查询数据
	/**
     * 查询数据
     * @throws Exception
     */
    @Test
    public void testSearch() throws Exception{
        //得到client
        TransportClient client = getClient();
        System.out.println(client.prepareGet("xihua", "students", "1").get().getSource());
        client.close();
    }
  • 修改数据
	/**
     * 修改数据
     * @throws Exception
     */
    @Test
    public void testUpdate() throws Exception{
        TransportClient client = getClient();
        IndexRequest indexRequest = new IndexRequest("xihua", "students", "1");
        Map mp = new HashMap();
        mp.put("name","xiaohong");
        mp.put("age",28);
        //id不存在就新增,如果存在就更新
        UpdateRequest updateRequest = new UpdateRequest("xihua", "students", "1").doc(mp).upsert(indexRequest);
        //执行
        client.update(updateRequest).get();
        client.close();
    }
  • 删除数据:
	/**
     * 删除数据
     * @throws Exception
     */
    @Test
    public void testDel() throws Exception{
        TransportClient client = getClient();
        client.prepareDelete("xihua","students","1").get();
        client.close();
    }
  • 批量操作
	/**
     * 批量操作
     * @throws Exception
     */
    @Test
    public void testBulk() throws Exception{
        TransportClient client = getClient();
        BulkRequestBuilder req = client.prepareBulk();
        for(int i=0;i<50;i++){
            Map mp = new HashMap();
            mp.put("name","xm"+i);
            mp.put("age",18+i);
            req.add(client.prepareIndex("shoppings","goods",""+i).setSource(mp));
        }
        BulkResponse responses = req.get();
        if(responses.hasFailures()){
            System.out.println("出错了!");
        }
    }
  • DSL查询(分页、排序、过滤)
	/**
     * DSL查询  分页、排序、过滤
     * @throws Exception
     */
    @Test
    public void testDSL() throws Exception{
        //得到client对象
        TransportClient client = getClient();
        //得到builder
        SearchRequestBuilder builder = client.prepareSearch("shoppings").setTypes("goods");
        //得到bool对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //得到must
        List<QueryBuilder> must = boolQuery.must();
        must.add(QueryBuilders.matchAllQuery());
        //添加filter过滤器
        boolQuery.filter(QueryBuilders.rangeQuery("age").gte("18").lte("58"));
        builder.setQuery(boolQuery);
        //添加分页
        builder.setFrom(0);
        builder.setSize(10);
        //设置排序
        builder.addSort("age", SortOrder.DESC);
        //设置查询字段
        builder.setFetchSource(new String[]{"name","age"},null);
        //取值
        SearchResponse response = builder.get();
        //得到查询内容
        SearchHits hits =  response.getHits();
        //得到命中数据 返回数组
        SearchHit[] hitsHits = hits.getHits();
        //循环数组 打印获取值
        for (SearchHit hitsHit : hitsHits) {
            System.out.println("得到结果"+hitsHit.getSource());
        }
    }

你可能感兴趣的:(ElasticSearch,分布式)