全文检索与消息队列中间件
在前面的章节中 我们学习了构建一个分布式系统所必需的各种基本知识和技能 比如分布式系统的基础理论、网络编程技术、 RP 架构、内存计算 分布式文件系统、分布式计算框架等,但仅仅掌握这些内容还是远远不够的 我们还需要学习和掌握分布式系统中常用的一些中间件 这些中间件主要用于分布式系统中常见的一些业务场景 数据全文检索、日志和消息处理、数据库的分片、网站的负载均衡等。由于篇幅有限, 本章只对全文检索与消息队列这两个用途广泛又相对复杂的中间件进行全面介绍。
全文检索
我们已经习惯以网上搜索的方式来快速学习知识并解决技术问题了,这就需要互联网搜索引擎。如何在海量网页(文本)信息中准确且快速地找到包含我们所搜索的关键字的所有网页并合理排序展示,的确是一个很有挑战的难题。
除了我们日常工作使用的搜索引擎,大量互联网应用都需要具备关键字检索(即全文检索)功能。要理解关键字检索的价值,我们需要先了解关系型数据库索引的局限性。我们在SQL查询语句中使用like "%keyword%"这种查询条件时,数据库的索引是不起作用的。此时,搜索就变成类似于一页页翻书的遍历过程了,几乎全部都是IO操作,因此对性能的负面影响很大;如果需要对多个关键词进行模糊匹配,比如 like"%keyword1%" and like "%keyword2%",此时的查询效率也就可想而知了。
关键字检索在本质上是将一系列文本文件的内容以“词组(关键词)”为单位进行分析并生成对应的索引记录。索引存储了关键词到文章的映射关系,在映射关系中记录了关键词所在的文章编号、出现次数、出现频率等关键信息,甚至包括了关键词在文章中出现的起始位置于是我们有机会看到关键字“高亮展示”的查询结果页面。
关键字检索的第一步是对整个文档(Document)进行分词,得到文本中的每个单词,这对于英文来说毫无困难,因为一个英文语句中的单词乙间是通过空格子付大杰力开B,但十人口句中的字与词是两个概念,所以中文分词就成了一个很大的问题,比如利. 北尔人女目如何分词呢?是“北京、天安门”还是“北、京、天安、门”﹖解决这个问题的最好办法是将中文词库结合中文分词法,其中比较知名的中文分词法有IK(IKAnalyzer)或庖丁(PaodingAnalyzcr),配合开源Lucene使用起来非常方便。
Lucene
Java生态圈中有名的全文检索开源项目是 Apache Lucene(后简称Lucene),它在2001年成为Apache的开源项目。Lucene最初的贡献者Doug Cutting是全文检索领域的一位资深专家,曾经是V-Twin搜索引擎(苹果的Copland操作系统的成就之一)的主要开发者,他贡献Lucene的目的是为各种中小型应用程序加入全文检索功能。目前Apache官方维护的Lucene相关的开源项目如下。
- Lucene Core:用Java编写的核心类库,提供了全文检索功能的底层API 与SDK。
- Solr:基于Lucene Core开发的高性能搜索服务,提供了RESTAPI的高层封装接口,还提供了一个 Web管理界面。
- PyLucene:一个 Python版的Lucene Core的高仿实现。
为了对一个文档进行索引,Lucene提供了5个基础类,分别是Document、Field、Index Writer、Analyzer和 Directory。首先,Document用来描述任何待搜索的文档,例如HTML页面、电子邮件或文本文件。我们知道,一个文档可能有多个属性,比如一封电子邮件有接收日期、发件人、收件人、邮件主题、邮件内容等属性,每个属性都可以用一个Field对象来描述。此外,我们可以把一个Document对象想象成数据库中的一条记录,而每个Field对象就是这条记录的个字段。其次,在一个 Document 能被查询之前,我们需要对文档的内容进行分词以找出文档包含的关键字,这部分工作由 Analyzer对象实现。Analyzer把分词后的内容交给IndexWriter建立索引。IndexWriter是Lucene用来创建索引(Index)的核心类之一,用于把每个Document对象都加到索引中来,并且把索引对象持久化保存到Directory中。Directory代表了Lucene索引的存储位置,目前有两个实现:第1个是 FSDirectory,表示在文件系统中存储;第2个是RAMDirectory,表示在内存中存储。
在明白了建立 Lucene索引所需要的这些类后,我们就可以对任意文档创建索引了。下面给出了对指定文件目录下的所有文本文件建立索引的源码:
//索引文件目录
Directory indexDir = FSDirectory.open (Paths.get ("index-dir"));Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig config = new IndexWriterConfig(analyzer);IndexWriter indexWriter = new Indexwriter (indexDir, config);//需要被索引的文件目录
String dataDir=". ";
File[] dataFiles = new File(dataDir).listFiles();long startTime - new Date() .getTime();
for(int i= 0; i<dataFiles.length; i++){
if(dataFiles[i].isFile () && dataFiles[i].getName ().endsWith(".txt"))(
System .out.println ("Indexing file"+
dataFiles[i]-getCanonicalPath());
Reader txtReader = new FileReader (dataFiles[i]);Document doc - new Document();
//文档的文件名也被作为一个Field,从而定位到具体的文件
doc.add(new StringField("filename", dataFiles[i].getName (),
Field.store.YES));
doc.add(new TextField("body", txtReader));indexwriter.addDocument (doc);
}
}
indexWriter.close();
long endTime - new Date( .getTime();
System.out.println("It takes " +(endTime - startTime)
+ " milliseconds to create index for the files in directory "+dataDir);
你可以把包含英文句子的任意文本(比如英文歌词)都放到项目的根目录下,运行上面的程序完成索引的创建过程,如果一切正常,则会出现类似于如下所示的提示:
Indexing file D:\project\leader-study-search\lemon-tree.txt
It takes 337 milliseconds to create index for the files in directory .
接下来我们尝试查询关键字,查询内容(对应 body字段)包括“good”的所有文档并输出结果。为此,我们首先需要打开指定的索引文件,然后构造Query对象并执行查询逻辑,最后输出查询结果。下面是对应的源码:
//打开指定的索引文件
Directory indexDir = FSDirectory.open (Paths.get("index-dir"));IndexReader reader = DirectoryReader.open (indexDir);
IndexSearcher searcher = new IndexSearcher(reader);//查询
String querystr = "good";
Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser ( "body", analyzer);Query a- parser.parse(querystr);
int hitsPerPage = 10;
TopDocs docs=searcher.search(q, hitsPerPage);ScoreDoc[] hits = docs.scoreDocs;
//输出查询结果
System.out.println("Found " +hits.length + " hits.");for (int i =0;i< hits.length; ++i){
int docid = hits[i] .doc;
Document d = searcher.doc(docId);
System.out.println((i +1)+ "." +d.get ("filename "));
}
如果你搜索的关键字恰好在某个文本文件中,则运行这段代码,控制台会输出类似于下面的内容:
Found 1 hits.1. lemon-tree.txt
通过对上面例子的学习,我们已经初步掌握了Lucene 的基本用法,Lucene编程的整个流程如下图所示。
Lucene编程的整个流程可以总结为如下三个独立步骤。
- 建模:根据被索引文档(原始文档)的结构与信息,建模对应的 Document对象与相关的 Lucene 的索引字段(可以有多个索引),这一步类似于数据库建模。关键点之一是确定原始文档中有哪些信息需要作为Field存储到Document对象中。通常文档的ID或全路径文件名是要保留的(Field.Store.YES),以便检索出结果后让用户查看或下载原始文档。
- 收录:编写一段程序扫描每个待检索的目标文档,将其转换为对应的 Document对象并且创建相关索引,最后存储到Lucene的索引仓库(Directory)中。这一步可以被类比为初始化数据(批量导入数据)。
- 检索:使用类似于SQL查询的Lucene API来编写我们的全文检索条件,从Lucene的索引仓库中查询符合条件的 Document并且输出给用户,这一步完全类似于SQL查询.
Lucene还普遍与网络爬虫技术相结合,提供基于互联网资源的全文检索功能,比如有不少提供商品比价和最优购物的信息类网站,通过爬虫去抓取各个电商平台上的商品信息并将其录入Lucene索引库里,然后提供用户检索服务,如下所示为此类系统的一个典型架构图。
Solr
如果把Lucene与MySQL做对比,你会发现Lucene像MySQL的某个存储引擎,比如InnoDB或者MyISAM。Lucene只提供了基本的全文检索相关的API,还不是一个独立的中间件,功能不够丰富,API也比较复杂,不太方便使用。除此之外,Lucene还缺乏一个更为关键的特性—分布式,当我们要检索的文档数量特别庞大时,必然会遇到宕机的瓶颈,所以有了SolrElasticSearch,它们都是基于Lucene的功能丰富的分布式全文检索中间件。
如下所示是Solr的架构示意图。我们看到,Solr在 Lucene的基础上开发了很多企业级增强功能:提供了一套强大的Data Schema来方便用户定义document的结构;增加了高效灵活的缓存功能;增加了基于Web的管理界面以提供集中的配置管理功能;可以将Solr的索引数据分片存储到多个节点上,并且通过多副本复制的方式来提升系统的可靠性。
Solr的分布式集群模式也被称为SolrCloud,这是一种很灵活的分布式索引和检索系统。SolrCloud也是一种具有去中心化思想的分布式集群,在集群中并没有特殊的 Master 节点,而是依靠ZooKeeper来协调集群。SolrCloud中一个索引数据(Collection)可以被划分为多个分片(Shard)并存储在不同的节点上(Solr Core或者Core),在索引数据分片的同时,SolrCloud也可以实现分片的复制(Replication)功能以提升集群的可用性。SolrCloud集群的所有状态信息都被放在ZooKeeper中统一维护,客户端在访问SolrCloud集群时,首先要向ZooKeeper查询索引数据(Collection)所在的Core节点的地址列表,然后就可以连接到任意Core 节点上来完成索引的所有操作(CRUD)了。
如下图所示给出了一个 SolrCloud 参考部署方案,本方案中的索引数据(Collection)被分为两个分片,同时每个 Shard分片的数据都有3份,其中一份所在的Core节点被称为Leader,其他两个Core节点被称为Replica。所有索引数据都分布在8个Core 中,他们位于独立的3台服务器上,所以其中任何一台机器宕机,都不会影响到系统的可用性。如果某个服务器在运行中宕机,那么SolrCloud 会自动触发Leader的重新选举行为,这是通过ZooKeeper提供的分布式锁功能来实现的。
之前说到,SolrCloud中的每个Shard分片都是由一个Leader 与N个Replica组成的,而且客户端可以连接到任意一个Core节点上进行索引数据的操作,那么,此时索引数据是如何实现多副本同步的呢?下图给出了背后的答案。
如果客户端连接的Core不是Leader,则此节点会把请求转发给所在Shard分片的Leader节点。
Leader 会把数据(Document)路由到所在Shard分片的每个Replica节点。如果文档分片路由规则计算出目标Shard分片是另外一个分片,则 Leader会把数据转发给该分片对应的Leader节点去处理。
接下来谈谈另外一个重要问题,这个问题就是SolrCloud采用了什么算法进行索引数据的分片Shard?为了选择合适的分片算法,SolrCloud提出了以下两个关键要求。
(1)分片算法的计算速度必须快,因为在建立索引及访问索引的过程中都频繁用到分片算法。
(2)分片算法必须保证索引数据能均匀地分布到每一个分片上,SolrCloud 的查询是先分后总的过程,如果某个分片中的索引文档(Document)的数量远大于其他分片,那么在查询此分片时所花的时间就会明显多于其他分片,也就是说最慢分片的查询速度决定了整体的查询速度。
基于以上两点,SolrCloud选择了一致性哈希算法来实现索引分片。
本节最后说说SolrCloud 所支持的“近实时搜索”这个高级特性。近实时搜索就是在较短的时间内使得新添加的 Document可见可查,这主要基于Solr的 Soft Commit机制。在8.1.2节中讲到,Lucene在创建索引时数据是在提交时被写入磁盘的,这就是Hard Commit,它确保了即便停电也不会丢失数据,但会增加延时。同时,对于之前已经打开的Searcher 来说,新加入的Document也是不可见的。Solr为了提供更实时的检索能力,提供了Soft Commit的新模式,在这种模式下仅把数据提交到内存,此时并没有将其写入磁盘索引文件中,但索引Index可见,Solr 会打开新的Searcher 从而使新的 Document可见。同时,Solr会进行预热缓存及查询以使得缓存的数据也是可见的。为了保证数据最终会被持久化保存到磁盘上,可以每1~10分钟自动触发Hard Commit而每秒钟自动触发Soft Commit.Soft Commit也是一把双刃剑,一方面Commit越频繁,查询的实时性越高,但同时增加了Solr 的负荷,因为Commit越频繁,越会生成小且多的索引段(Segment),于是Solr Merge 的操作会更加频繁。在实际项目中建议根据业务的需求和忍受度来确定Soft Commit 的频率。
ElasticSearch
ElasticSearch(后简称ES)并不是 Apache出品的,它与Solr类似,也是基于Lucene的一个分布式索引服务中间件。ES 的出现晚于Solr,但从当前的发展状况来看,它的势头和流行度要超过前辈许多。值得一提的是在日志分析领域,以ES为核心的ELK三件套(ELK Stack)成为事实上的标准。ELK其实并不是一款软件,而是一整套解决方案,是三款软件即ES、Logstash和Kibana首字母的缩写。这三款软件都是开源软件,通常配合使用,又先后归于Elastic.co 公司名下,故被简称为ELK Stack。在 Google上有文章提到,ELK Stack每个月的下载量达到50万次,已经成为世界上最流行的日志管理平台,而在当前流行的基于Docker 与 Kubernetes 的PaaS平台上,ELK也是标配之一,非 Apache 出品的ES之所以能后来居上,与ELK的流行和影响力也有着千丝万缕的联系。
实际上,在所有分布式系统中最需要全文检索的就是日志模块了。如果尝试对节点数超过5个的分布式系统做Trouble Shooting,你就会明白日志集中收集并提供全文检索功能的重要性和紧迫性了。在没有类似于ELK Stack这样一套日志子系统的情况下,我们不得不登录每个主机来查询日志,并且“拼接”所有相关的查询结果,以定位和分析故障出现的环节及前因后果,这项工作看起来并不复杂,但实际上很耗费精力,因为在每个主机上都可能有多个日志文件需要分析,仅仅定位某个时间点的日志就让人很头疼了。
如下所示是ELK Stack 的一个架构组成图。Logstash是一个有实时管道能力的数据收集引擎,用来收集日志数据并且作为索引数据写入ES集群中,我们也可以开发自定义的日志采集探头并按照ELK的日志索引格式写入ES集群中;Kibana则为ES提供了数据分析及数据可视化的Web平台,它可以在ES的索引中查找数据并生成各种维度的表图。
ES通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单,它提供了近实时的索引、搜索、分析等功能。我们可以这样理解和描述ES。
- 分布式的实时文档存储,文档中的每个字段都可被索引并搜索。
- 分布式的实时分析搜索引擎。
- 可以扩展到上百台服务器,处理PB级的结构化或非结构化的数据。
在ES中增加了Type这个概念,如果我们把Index类比为Database,Type就相当于Table,但这个比喻不是很恰当,因为我们知道不同 Table 的结构完全不同,而一个Index中所有Document 的结构是高度一致的。ES 中的Type其实是Document 中的一个特殊字段,用来在查询时过滤不同的 Document,比如我们在做一个 B2C的电商平台时,需要对每个店铺的商品进行索引,则可以用Type区分不同的商铺。实际上,Type的使用场景非常少,这是我们需要注意的。
与SolrCloud一样,ES也是分布式系统,但ES并没有采用ZooKeeper作为集群的协调者,而是自己实现了一套被称为Zen Discovery 的模块,该模块主要负责集群中节点的自动发现和Master节点的选举。Master节点维护集群的全局状态,比如节点加入和离开时进行Shard的重新分配,集群的节点之间则使用P2P的方式进行直接通信,不存在单点故障的问题。ES不使用ZooKeeper 的一个好处是系统部署和运维更加简单了,坏处是可能出现所谓的脑裂问题。要预防脑裂问题,我们需要重视的一个参数是
discovery.zen.minimum_master_nodes,它决定了在选举 Master节点的过程中需要有多少个节点通信,一个基本原则是这里需要设置成N/2+1,N是集群中节点的数量。例如在一个三节点的集群中,minimum_master_nodes应该被设为3/2+1 =2(四舍五入),当两个节点的通信失败时,节点1会失去它的主状态,同时节点2不会被选举为Master节点,没有一个节点会接收索引或搜索的请求,没有一个分片会处于不一致状态。ES在Zen Discovery算法上做了不少改进以解决脑裂问题,GitHub上关于脑裂的Issue后来在2014年被关闭了,如下所示是相关说明:
2. Zen Discovery
Pinging after master loss (no local elects)Fixes the split brain issue:#2488
Batching join requests
More resilient joining process (wait on a publish from master)
ES 的集群与SolrCloud还有一个重大差别,即ES集群中的节点类型不止一种,有以下几种类型。
- Master节点:它有资格被选为主节点,控制整个集群。
- Data节点:该节点保存索引数据并执行相关操作,例如增删改查、搜索及聚合。
- Load balance节点:该节点只能处理路由请求、处理搜索及分发索引操作等,从本质上来说该节点的表现等同于智能负载平衡器。Load balance节点在一个较大的集群中是非常有用的,Load balance节点在加入集群后可以得到集群的状态,并可以根据集群的状态直接路由请求。
- Tribe节点:这是一个特殊的Load balance节点,可以连接多个集群,在所有连接的集群上都执行搜索和其他操作。
- Ingest节点:是 ES 5.0新增的节点类型,该节点大大简化了以往ES集群中添加数据的复杂度。
如下所示是Tribe节点连接多个ES集群展示日志的ELK部署方案,据说魅族就采用了这种方案来解决各个IDC机房日志的集中展示问题。
本节最后,我们一起安装ES并编写一些简单的例子来加深对ES 的理解并初步掌握其API的用法。我们可以去ES官网下载 ES 的二进制版本(在Windows上运行时可以下载ZIP包),解压后在其 bin目录下有可执行的脚本,比如 elasticsearch.bat。在执行启动脚本后,在浏览器里访问http://localhost:9200,会显示如下信息,表明ES启动正常:
{
"name" : "Y8 klCx",
"cluster name" :"elasticsearch",
"cluster uuid" :"X3tmO4iXSKa8l_ ADWNh_g","version" :{
"number" :"5.3.0",
"build hash" :"3adb13b",
"build date" : "2017-03-23T03:31:50.652Z","build snapshot" :false,
"lucene version" :"6.4.1",
},
"tagline" :"You Know, for Search"
}
ES提供了REST接口以让我们很方便地将一个Document加入索引中,为了学会这个API首先,我们需要知道在ES中一个Document由以下3个字段唯一确定。
- _index:文档所在的索引。
- _type:文档的类型。
- _id:文档的字符串ID,可以在插入文档时自己指定,也可以让ES自己随机生成。
现在我们就容易理解Document CRUD的REST接口的URL的写法了: http:/localhost:9200/<index>/<type>/[<id>]。
此外,在ES中一个Document是用JSON结构体来表示的,由于JSON的结构本身就有字段类型的暗示,比如字符串与数字的属性是用不同方式表示的,因此 ES可以实现JSO到Document Schema t日以刻凤ES被称为Schema-less 系统的原因。但Schema-less i进个的方你想要的字段类型映别,心T配的Schema不满意,则也可以使用自定义映射(MappTgO nNo Schema,如果对ES自动匹配的Schema不满意,则也可以使用自定义映射(Mapping)的方式来设计更为合理的Schema。
从5.0版本开始,ES开发了一个全新的Java客户端API,这个API的最大目标是移除对ES 及 Lucene类库的依赖,变得更加轻量级,同时采用了分层设计的思路,底层仅仅包括一个HTTP通信层及一个Sniffer用于发现集群中的其他节点,其他层则包括Query DSL等功能,我们在本节中就使用这个新的Java API来完成Document的操作。
我们需要在Maven中引用这个API:
<dependency>
<groupId>org.elasticsearch.client</groupId><artifactId>sniffer</artifactId>
<version>5.3.0</version>
</dependency>
下面这段代码的作用是获取ES的健康信息,类似于我们在浏览器中访问地址
http://localhost:9200/_cluster/health:
RestClient client = RestClient
.builder(new HttpHost("localhost",9200)).build();
Response response = client.performRequest(
"GET", "/ cluster/health", Collections.singletonMap ("pretty"
"true"));
HttpEntity entity =response.getEntity();
System. out.println(EntityUtils.tostring(entity));
在运行后会输出下面这样一段内容,其中 status属性比较重要,green表示集群很健康:
"cluster name" :"elasticsearch",
"status" :"green",
"timed out" : false,"number of nodes" :1,
"number of_data_nodes" :1,
"active_primary_shards" :0,"active shards" :0,
"relocating_shards" :0,"initializing_shards" :0,"unassigned shards" :0,
"delayed unassigned_shards" :0,"number of pending tasks" :0,"number of in_flight_fetch" :0,
"task max _waiting_in_queue_millis" :0,"active_shards percent as_number" :100.0
}
接下来,我们在名为 blogs 的 Index里插入一个 Document,Document 的 type为 blogId为1。下面是Document的JSON内容:
{
"user" : "Leader us",
"post date" :"2017-12-12",
"message" : "Mycat 2.0 is coming ! "
}
对应的代码如下:
//index a document
String docJson= "{An"+
" "user\ " :\"Leader us\ ",n"+
" "post datel ": "2017-12-12\",n" +
" \"messaael" :\ "Mycat 2.0 is coming!\"\n"+"}";
System. out.println(docJson);
entity = new NStringEntity(docJson,ContentType.APPLICATION_JSON);Response indexResponse = client.performRequest("PUT","/blogs/blog/1",
Collections.singletonMap ( "pretty", "true"), entity);
System.out.println (Entityotils.tostring (indexResponse.getEntity()));
运行上述代码后,在ES中成功插入一个索引文档,控制台会输出如下信息:
{
" index" :"blogs",
"_type" : "blog","_id" :"1",
"_version" :1,"result" : "created"," _shards" :{
"total" :2,
"successful" :1,
"failed" :0
},
"created" : true
}
如果第2次运行上面的代码,则输出信息中的result值会从created变为updated,表明是更新Document 的操作,同时_version会累加。我们用下面的代码继续增加100个用于测试的Document:
String[] products= {"Mycat ", "Mydog", "Mybear","MyAllice"};
for (int i=0;i<100;i++)
{
//index a document
String docJson= "{\n"+
"\ "user\ ":\ "Leader us ",\n" +
\"post_date\" :\""+(2017+i)+"-12-12\",\n"+
"\ "messagel" :\""+products[i%products.length] +i+" is
coming! "\n" +
"]";
HttpEntity entity = new NStringEntity(docJson, ContentType.APPLICATIONM_ JSOtResponse indexResponse = client.performRequest ("PUT","/blos/blog/"+i,
Collections.singletonMap ("pretty", "true"),
entity);
System.out.println(EntityUtils.toString(indexResponse.getEntity()));
}
此时打开浏览器,输入查询指令:
http://localhost:9200/blogs/blog/_search?pretty=true
则会出现下面的查询信息:
{
"took" :14,
"timed_ out" :false," shards" :{
"total":5,
"successful" :5,"failed" :0
),
"hits" :{
"total" :100,
"max_score" : 1.0,"hits" :[
{
" index" :"blogs"," type" :"blog",
"id" : "19"," score" :1.0," source":{
"user" : "Leader us",
"postdate" :"2036-12-12",
"message" :"MyAllice19 is coming ! "
},
{
" index" : "blogs","type" : "blog" ," id" :"22"," score" :1.0," source" :{
"user":"Leader us",
"post date" :"2039-12-12",
"message" :"Mybear22 is coming!"
}
},
根据上述信息,我们得知 blogs这个Index的分片数量为5个,hits部分为匹配查询条件的Document列表,总共有100个符合条件的文档,_source部分为我们录入的原始Document的信息。如果查询某个特定Document的内容,则只要在URL中指定文档的ID即可,比如
http://localhost:9200/blogs/blog/1。
如果我们要查询包含某个关键字的文档,则该怎么办?ES提供了一个Query DSL 的语法,采用JSON格式描述,用起来也比较方便,比如下面这段DSL 语句表明查询任意字段值中包含mycat这个关键字的Document:
{
"query":{
"query string": {
" query" :"mycat"
}
}
}
我们只要将上述DSL作为JSON内容Post到某个索引的URL地址即可,下面是具体的代码:
//search document
String dsl- "{"query":{"+
"\ " query string \": {"+
" "auery ": \ "mycat\""+
"] ]}";
System.out.println (dsl);
HttpEntity entity = new NStringEntity(dsl,
ContentType.APPLICATION_JSON);
Response response =
client.performRequest("POST", "/blogs/blog/search",
Collections.singletonMap ("pretty", "true"),entity);
System.out.println (EntityUtils.toString (response.getEntity()));
在这里就不再深入讨论ES的其他编程内容了,我们主要围绕Query DSL的语法细节进行讲解,比如高亮显示匹配结果、过滤查询结果、控制结果集缓存及联合查询等内容。