一、技术简介
(简述技术的内容、应用场景、与同类技术优缺点比较)
Elasticsearch 是一个使用JAVA开发,基于Apache Lucene类库的开源搜索引擎。
Elasticsearch 是一个分布式、可扩展、实时的数据存储、搜索与分析的企业级搜索引擎,可以扩展到上百台服务器,处理PB级结构化或非结构化数据。
Elasticsearch 提供RESTful API接口(不管程序用什么语言开发,任何程序都可以访问),并且提供了JAVA API接口。
与Elasticsearch 配合使用的插件:中文分词器(elasticsearch-analysis-ik)、kibana(一个功能强大的elasticsearch数据显示客户端)
Elasticsearch的几个核心概念:
1.接近实时(NRT)
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒)。
2.集群(cluster)
一个集群就是由一个或多个节点组织在一起,它们共同持有你整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。在产品环境中显式地设定这个名字是一个好习惯,但是使用默认值来进行测试/开发也是不错的。
3.节点(node)
一个节点是你集群中的一个服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
4.索引(index)
索引(名词) 它是相关文档存储的地方,一个索引(index)就像是传统关系数据库中的数据库。
索引(动词)「索引一个文档」表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像SQL中的INSERT关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。
倒排索引 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。默认情况下,文档中的所有字段都会被索引(拥有一个倒排索引),只有这样他们才是可被搜索的(我们可以通过设置让某些字段不被索引)。
5.类型(type)
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。
6.文档(document)
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。
在一个index/type里面,只要你想,你可以存储任意多的文档。
7.分片和复制(shards & replicas)
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。
为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
分片之所以重要,主要有两方面的原因:
- 允许你水平分割/扩展你的内容容量
- 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制之所以重要,有两个主要原因:
- 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
- 扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行
总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
应用场景:
一般的数据库查询技术无法实现全文搜索的时间要求,例如:用MySql或者oracle数据库,进行like语句查询时,由于不能用索引,会直接拉低数据库的性能,导致查询速度非常慢;ES则可以完美实现快速搜索功能,并且也有自己的查询语言,进行各种丰富的条件查询。
维基百科、github等大型的站点采用 ES作为其搜索服务。
同类技术:
Sphinx(斯芬克司)是一个基于SQL的全文检索引擎,但是在能处理的数据量级(ES能处理PB级的数据量)和可扩展性上都不如ES。
二、相关文件
(支撑技术的相关软件或jar包,列出获取方式或下载地址,将其下载并放入《相关文件》文件夹)
① 参考文档
Elasticsearch: 权威指南(https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html)
Elasticsearch基础教程(http://blog.csdn.net/cnweike/article/details/33736429)
Elasticsearch教程(http://www.sojson.com/blog/87.html)
三、验证过程
1:安装篇
I安装、配置java虚拟机
官方建议始终用最新的java虚拟机,本文中采用的是java 8,如果客户机上的jdk版本上是1.7,可以通过配置解决jdk版本不兼容的问题,具体操作看教程 https://www.cnblogs.com/everydaygift/p/7589273.html
II 下载、安装ES
在Elasticsearch官网下载相应版本的ES安装包,window需要下载zip格式的包,本文采用的版本是elasticsearch-5.5.1.zip
通过/bin/elasticsearch.in.bat脚本启动
III下载安装IK中文分词器(版本要和ES版本相匹配)
下载源码地址https://github.com/medcl/elasticsearch-analysis-ik
编译源码:mvn clean package,在ES_HOME/plugins路径下创建analysis-ik文件夹,将编译得到的elasticsearch-analysis-ik-5.5.1.zip解压到analysis-ik
安装插件后,重启ES
2:编程篇
I 常用的REST的url
1、检查集群健康:
curl ‘localhost:9200/_cat/health?v’
相应的响应是:
epoch timestamp cluster status node.total node.data shards pri relo init unassign
1394735289 14:28:09 elasticsearch green 1 1 0 0 0 0 0
2、获得节集群中的节点列表:
curl ‘localhost:9200/_cat/nodes?v’
对应的响应是:
host ip heap.percentram.percent load node.role master name
mwubuntu1 127.0.1.1 8 4 0.00 d * New Goblin
3、 列出所有的索引:
curl ‘localhost:9200/_cat/indices?v’
对应的响应是:
health index pri rep docs.count docs.deleted store.size pri.store.size
yellow customer 5 1 0 0 495b 495b
4、索引一个文档:
curl -XPUT ‘localhost:9200/customer/external/1?pretty’ -d ’
{
“name”: “John Doe”
}’
响应如下:
{
“_index” : “customer”,
“_type” : “external”,
“_id” : “1”,
“_version” : 1,
“created” : true
}
5、把索引的文档取出来:
curl -XGET ‘localhost:9200/customer/external/1?pretty’
响应如下:
{
“_index” : “customer”,
“_type” : “external”,
“_id” : “1”,
“_version” : 1,
“found” : true,
“_source” : { “name”: “John Doe” }
}
6、删除刚创建的索引:
curl -XDELETE ‘localhost:9200/customer?pretty’
响应如下:
{
“acknowledged” : true
}
仔细研究以上的命令,可以发现访问Elasticsearch中数据的一个模式。
这个模式可以被总结为:
curl - :///
7 索引/替换文档
先索引
curl -XPUT ‘localhost:9200/customer/external/1?pretty’ -d ’
{
“name”: “John Doe”
}’
再次,以上的命令将会把这个文档索引到customer索引、external类型中,其ID是1。如果我们对一个不同(或相同)的文档应用以上的命令,Elasticsearch将会用一个新的文档来替换(重新索引)当前ID为1的那个文档。
curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
{
"name": "Jane Doe"
}'
以上的命令将ID为1的文档的name字段的值从“John Doe”改成了“Jane Doe”。如果我们使用一个不同的ID,一个新的文档将会被索引,当前已经在索引中的文档不会受到影响。
在索引的时候,ID部分是可选的。如果不指定,Elasticsearch将产生一个随机的ID来索引这个文档。Elasticsearch生成的ID会作为索引API调用的一部分被返回。
以下的例子展示了怎样在没有指定ID的情况下来索引一个文档:
curl -XPOST 'localhost:9200/customer/external?pretty' -d '
{
"name": "Jane Doe"
}'
注意,在上面的情形中,由于我们没有指定一个ID,我们使用的是POST而不是PUT。
8、更新文档
Elasticsearch底层并不支持原地更新。在我们想要做一次更新的时候,Elasticsearch先删除旧文档,然后在索引一个更新过的新文档
curl -XPOST ‘localhost:9200/customer/external/1/_update?pretty’ -d ’
{
“doc”: { “name”: “Jane Doe” }
}’
下面的例子展示了怎样将我们ID为1的文档的name字段改成“Jane Doe”的同时,给它加上age字段:
curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '
{
"doc": { "name": "Jane Doe", "age": 20 }
}'
更新也可以通过使用简单的脚本来进行。这个例子使用一个脚本将age加5:
curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '
{
"script" : "ctx._source.age += 5"
}'
在上面的例子中,ctx._source指向当前要被更新的文档。
9、删除文档
curl -XDELETE ‘localhost:9200/customer/external/2?pretty’
我们也能够一次删除符合某个查询条件的多个文档。以下的例子展示了如何删除名字中包含“John”的所有的客户:
curl -XDELETE 'localhost:9200/customer/external/_query?pretty' -d '
{
"query": { "match": { "name": "John" } }
}'
注意,以上的URI变成了/_query,以此来表明这是一个“查询删除”API,其中删除查询标准放在请求体中,但是我们仍然使用DELETE。现在先不要担心查询语法,我们将会在本教程后面的部分中涉及。
10、批处理:
除了能够对单个的文档进行索引、更新和删除之外,Elasticsearch也提供了以上操作的批量处理功能,这是通过使用_bulk API实现的。这个功能之所以重要,在于它提供了非常高效的机制来尽可能快的完成多个操作,与此同时使用尽可能少的网络往返。
在一次bulk操作中索引了两个文档(ID 1 - John Doe and ID 2 - Jane Doe):
curl -XPOST ‘localhost:9200/customer/external/_bulk?pretty’ -d ’
{“index”:{“_id”:”1”}}
{“name”: “John Doe” }
{“index”:{“_id”:”2”}}
{“name”: “Jane Doe” }
’
在一个bulk操作中,首先更新第一个文档(ID为1),然后删除第二个文档(ID为2):
curl -XPOST ‘localhost:9200/customer/external/_bulk?pretty’ -d ’
{“update”:{“_id”:”1”}}
{“doc”: { “name”: “John Doe becomes Jane Doe” } }
{“delete”:{“_id”:”2”}}
’
注意上面的delete动作,由于删除动作只需要被删除文档的ID,所以并没有对应的源文档。
bulk API按顺序执行这些动作。如果其中一个动作因为某些原因失败了,将会继续处理它后面的动作。当bulk API返回时,它将提供每个动作的状态(按照同样的顺序),所以你能够看到某个动作成功与否。
11、 载入样本数据
curl -XPOST ‘localhost:9200/bank/account/_bulk?pretty’ –data-binary @accounts.json
响应是:
health index pri rep docs.count docs.deleted store.size pri.store.size
yellow bank 5 1 1000 0 424.4kb 424.4kb 这意味着我们成功批量索引了1000个文档到银行索引中(account类型)。
II ES的java API的demo
1、创建连接ES的transportClient
private static TransportClient getTransportClient(String clusterName, String hostName) {
/**
* 创建client
*/
Settings settings = Settings.builder().put(“cluster.name”, clusterName).build();
try {
@SuppressWarnings("resource")
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(hostName), 9300));
return client;
} catch (UnknownHostException e1) {
e1.printStackTrace();
}
return null;
}
2、创建一个索引
private void createIndex() {
TransportClient client = getTransportClient(“elasticsearch_dev_xu”, “localhost”);
try {
client.admin().indices().prepareCreate(“worddocinfo”).execute().actionGet();
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(“wordDocInfo”)
.startObject(“properties”).startObject(“docId”).field(“type”, “string”).endObject()
.startObject(“docName”).field(“type”, “string”).field(“store”, “yes”).field(“index”, “ik”)
.field(“ik”, “ik_max_word”).endObject().startObject(“docPath”).field(“type”, “string”).endObject()
.startObject(“pageCount”).field(“type”, “integer”).endObject().startObject(“wordCount”)
.field(“type”, “integer”).endObject().startObject(“docSize”).field(“type”, “long”).endObject()
.startObject(“docContent”).field(“type”, “string”).field(“store”, “yes”).field(“index”, “ik”)
.field(“ik”, “ik_smart”).endObject().endObject().endObject().endObject();
PutMappingRequest mappingRequest = Requests.putMappingRequest("worddocinfo").type("wordDocType")
.source(mapping);
client.admin().indices().putMapping(mappingRequest).actionGet();
/**
* 关闭node
*/
client.close();
} catch (ElasticsearchException el) {
el.printStackTrace();
System.err.println("该索引已经存在!");
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
3、索引一条数据
public static void createData(String index, String type, WordDoc wordDoc) {
TransportClient client = getTransportClient(“elasticsearch_dev_xu”, “localhost”);
IndexResponse indexResponse = client.prepareIndex(“worddocinfo”, “wordDocInfoType”, wordDoc.getDocId())
.setSource(JSON.toJSONString(wordDoc), XContentType.JSON).get();
}
4、搜索数据
public static Map