如何用 Node.js 和 Elasticsearch 构建nodejs 搜索引擎擎

7842人阅读
Search(43)
Elasticsearch(43)
JavaScript(67)
AngularJS(26)
使用AngularJS为基于Elasticsearch的应用创建前端
如果使用Elasticsearch使用应用的数据源,我们可以很方便的使用AngularJS结合Elasticsearch提供的相关模块为它创建一个前端。
以创建一个简单的员工信息花名册为例。
准备工作分为以下两个方面:
添加前端依赖
安装Bower在bower.json中添加对于AngularJS和Elasticsearch的依赖:
&dependencies&: {
&angular&: &~1.2.15&,
&elasticsearch&: &~2.4.0&
准备运行时环境
对于简单的应用和Demo,可以直接使用Node环境中提供的http-server,非常简单快捷。
安装Node安装http-server,通过命令:npm install -g http-server
配置AngularJS以及ES
配置AngularJS
var rosterApp = angular.module('rosterApp', ['elasticsearch']);
配置Elasticsearch
对于一个只读的应用,我们只需要将搜索的功能暴露出来。可以通过自定义一个factory来实现:
rosterApp.factory('rosterService',
['$q', 'esFactory', '$location', function($q, elasticsearch, $location){
var client = elasticsearch({
host: $location.host() + &:9200&
var search = function(term, offset){
var deferred = $q.defer();
var query = {
&match&: {
&_all&: term
client.search({
&index&: 'roster',
&type&: 'employee',
&size&: 10,
&from&: (offset || 0) * 10,
&query&: query
}).then(function(result) {
var ii = 0, hits_in, hits_out = [];
hits_in = (result.hits || {}).hits || [];
for(;ii & hits_in.length; ii++){
hits_out.push(hits_in[ii]._source);
deferred.resolve(hits_out);
}, deferred.reject);
return deferred.
&search&: search
在以上定义的factory中,我们只暴露了search方法。该方法调用了client的search方法完成基于关键字的搜索。
控制器(Controller)
下面需要做的就是实现一个控制器来完成页面与上述search方法的交互:
rosterApp.controller('rosterCtrl',
['rosterService', '$scope', '$location', function(rosterService, $scope, $location){
$scope.employees = [];
$scope.page = 0;
$scope.allResults = false;
$scope.searchTerm = '';
$scope.search = function(){
$scope.page = 0;
$scope.employees = [];
$scope.allResults = false;
$location.search({'q': $scope.searchTerm});
$scope.loadMore();
$scope.loadMore = function(){
rosterService.search($scope.searchTerm, $scope.page++).then(function(results){
if(results.length !== 10){
$scope.allResults = true;
var ii = 0;
for(;ii & results.length; ii++){
$scope.employees.push(results[ii]);
$scope.loadMore();
search方法用来执行搜索,loadMore方法用于加载更多记录。
页面主要用于展示Employee对象的具体属性。
&div ng-app='rosterApp' ng-controller='rosterCtrl'&
&h1&Employee Search&/h1&
&section class='searchField'&
&form ng-submit='search()'&
&input ng-model='searchTerm' type='text'&
&input type='submit' value='Search for employees'&
&/section&
&section class='results'&
&div class='no-employees' ng-hide='employees.length'&No results&/div&
&article class='employee' ng-cloak ng-repeat='employee in employees'&
&a ng-href='{{employee.link}}'&{{employee.name}}&/a&
&li ng-repeat='historyItem in employee.historyItems'&{{ historyItem }}&/li&
{{employee.introduction}}
&a ng-href='{{employee.link}}'&More information...&/a&
&/article&
&div class='load-more' ng-cloak ng-hide='allResults'&
&a ng-click='loadMore()'&More...&/a&
&/section&
进入工程目录,运行http-server。访问http://localhost:8080。
当需要使用不同的查询类型时,通过修改search方法中query的创建部分即可。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1881981次
积分:13900
积分:13900
排名:第892名
原创:142篇
译文:44篇
评论:300条
(2)(1)(1)(1)(2)(2)(1)(2)(2)(5)(10)(8)(4)(3)(1)(2)(1)(3)(3)(1)(3)(1)(7)(25)(29)(22)(25)(4)(1)(7)(1)(2)(5)时下最火搜索引擎:ElasticSearch详解与优化设计
我的图书馆
时下最火搜索引擎:ElasticSearch详解与优化设计
ElasticSearch(简称ES)是一个分布式、Restful的搜索及分析服务器,设计用于分布式计算;能够达到实时搜索,稳定,可靠,快速。和Apache Solr一样,它也是基于Lucence的索引服务器,而ElasticSearch对比Solr的优点在于:
轻量级:安装启动方便,下载文件之后一条命令就可以启动。
Schema free:可以向服务器提交任意结构的JSON对象,Solr中使用schema.xml指定了索引结构。
多索引文件支持:使用不同的index参数就能创建另一个索引文件,Solr中需要另行配置。
分布式:Solr Cloud的配置比较复杂。
2013年初,GitHub抛弃了Solr,采取ElasticSearch 来做PB级的搜索。
近年ElasticSearch发展迅猛,已经超越了其最初的纯搜索引擎的角色,现在已经增加了数据聚合分析(aggregation)和可视化的特性,如果你有数百万的文档需要通过关键词进行定位时,ElasticSearch肯定是最佳选择。当然,如果你的文档是JSON的,你也可以把ElasticSearch当作一种“NoSQL数据库”, 应用ElasticSearch数据聚合分析(aggregation)的特性,针对数据进行多维度的分析。
ElasticSearch一些国内外的优秀案例:
Github:“GitHub使用ElasticSearch搜索20TB的数据,包括13亿文件和1300亿行代码”。
SoundCloud:“SoundCloud使用ElasticSearch为1.8亿用户提供即时而精准的音乐搜索服务”。
百度:百度目前广泛使用ElasticSearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据。
Cluster和Node
ES可以以单点或者集群方式运行,以一个整体对外提供search服务的所有节点组成cluster,组成这个cluster的各个节点叫做node。
这是ES存储数据的地方,类似于关系数据库的database。
索引分片,这是ES提供分布式搜索的基础,其含义为将一个完整的index分成若干部分存储在相同或不同的节点上,这些组成index的部分就叫做shard。
索引副本,ES可以设置多个索引的副本,副本的作用一是提高系统的容错性,当个某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高ES的查询效率,ES会自动对搜索请求进行负载均衡。
代表数据恢复或叫数据重新分布,ES在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
ES索引快照的存储方式,ES默认是先把索引存放到内存中,当内存满了时再持久化到本地硬盘。gateway对索引快照进行存储,当这个ES集群关闭再重新启动时就会从gateway中读取索引备份数据。
Discovery.zen
代表ES的自动发现节点机制,ES是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。
代表ES内部节点或集群与客户端的交互方式,默认内部是使用tcp协议进行交互,同时它支持http协议(json格式)。
& 安装部署
ES由java语言实现,运行环境依赖java。ES 1.x版本,官方推荐使用jdk1.7+的环境,建议使用oracle jdk1.8;ES可以去官网下载,本文使用elasticsearch-1.6.0.tar.gz。
(1) 解压elasticsearch-1.6.0.tar.gz,sudo tar -zvxf elasticsearch-1.6.0.tar.gz,在当前路径生成目录:elasticsearch-1.6.0;
(2) 配置ES。这里只做最简单的配置,修改ES_HOME/config/elasticsearch.yml文件, 相关配置参数
cluster.name: elasticsearch
node.name: "node1"
#节点是否存储数据
node.data: true
#索引分片数
index.number_of_shards: 5
#索引副本数
index.number_of_replicas: 1
#数据目录存放位置
path.data: /data/elasticsearch/data
#日志数据存放位置
path.logs: /data/elasticsearch/log
index.cache.field.max_size: 500000
#索引缓存过期时间
index.cache.field.expire: 5m
(3) 启动ES。进入ES安装目录,执行命令:bin/elasticsearch -d -Xms512m -Xmx512m,然后在浏览器输入
& 数据索引
ES索引我们可以理解为数据入库的一个过程。我们知道ES是基于Lucene框架的一个分布式检索平台。索引的同样也是基于Lucene创建的,只不过在其上层做了一些封装。ElasticSearch客户端支持多种语言如PHP、Java、Python、Perl等,下面以Python为例:
一,安装官方提供的Python API
&& pip install elasticsearch
二,创建索引
调用create或index方法,创建索引。
三,批量录入索引数据
ElasticSearch批量索引的命令是bulk,利用Python API提交
四,数据检索查询
五,数据更新、删除
对于索引的批量删除和更新操作,对应的文档格式如下,更新文档中的doc节点是必须的。
六、常见错误
SerializationError:JSON数据序列化出错,通常是因为不支持某个节点值的数据类型
RequestError:提交数据格式不正确
ConflictError:索引ID冲突
TransportError:连接无法建立
ES索引优化主要从两个方面解决问题:
一、索引数据过程
大家可能会遇到索引数据比较慢的过程。其实明白索引的原理就可以有针对性的进行优化。ES索引的过程到相对Lucene的索引过程多了分布式数据的扩展,而这ES主要是用tranlog进行各节点之间的数据平衡。所以从上我可以通过索引的settings进行第一优化:
这两个参数第一是到tranlog数据达到多少条进行平衡,默认为5000,而这个过程相对而言是比较浪费时间和资源的。所以我们可以将这个值调大一些还是设为-1关闭,进而手动进行tranlog平衡。第二参数是刷新频率,默认为120s是指索引在生命周期内定时刷新,一但有数据进来能refresh像lucene里面commit,我们知道当数据addDoucment后,还不能检索到要commit之后才能行数据的检索,所以可以将其关闭,在最初索引完后手动refresh一之,然后将索引setting里面的index.refresh_interval参数按需求进行修改,从而可以提高索引过程效率。
另外的知道ES索引过程中如果有副本存在,数据也会马上同步到副本中去。我个人建议在索引过程中将副本数设为0,待索引完成后将副本数按需量改回来,这样也可以提高索引效率。
"number_of_replicas": 0
二、检索过程
其实检索速度快度与索引质量有很大的关系。而索引质量的好坏主要与以下几方面有关:
分片数,与检索速度非常相关的的指标,如果分片数过少或过多都会导致检索比较慢。分片数过多会导致检索时打开比较多的文件别外也会导致多台服务器之间通讯。而分片数过少会导致单个分片索引过大,所以检索速度慢。基于索引分片数=数据总量/单分片数的计算公式,在确定分片数之前需要进行单服务单索引单分片的测试,目前我们测试的结果单个分片的内容为10G。
副本数与索引的稳定性有比较大的关系,如果Node在非正常挂了,经常会导致分片丢失,为了保证这些数据的完整性,可以通过副本来解决这个问题。建议在建完索引后在执行Optimize后,马上将副本数调整过来。
分词对于索引的影响可大可小,看自己把握。大家或许认为词库越多,分词效果越好,索引质量越好,其实不然。分词有很多算法,大部分基于词表进行分词。也就是说词表的大小决定索引大小。所以分词与索引膨涨率有直接关系。词表不应很多,而对文档相关特征性较强的即可。比如论文的数据进行建索引,分词的词表与论文的特征越相似,词表数量越小,在保证查全查准的情况下,索引的大小可以减少很多。索引大小减少了,那么检索速度也就提高了。
索引段即lucene中的segments概念,我们知道ES索引过程中会refresh和tranlog也就是说我们在索引过程中segments number不只一个。而segments number与检索是有直接联系的,segments number越多检索越慢,而将segments numbers 有可能的情况下保证为1,这将可以提高将近一半的检索速度。
ES对于内存的消耗,和很多因素相关,诸如数据总量、mapping设置、查询方式、查询频度等等。默认的设置虽开箱即用,但不能适用每一种使用场景。作为ES的开发、运维人员,如果不了解ES对内存使用的一些基本原理,就很难针对特有的应用场景,有效的测试、规划和管理集群,从而踩到各种坑,被各种问题挫败。
1要理解ES如何使用内存,先要尊重下面两个基本事实:
ES是JAVA应用
首先,作为一个JAVA应用,就脱离不开JVM和GC。很多人上手ES的时候,对GC一点概念都没有就去网上抄各种JVM“优化”参数,却仍然被heap不够用,内存溢出这样的问题搞得焦头烂额。即使对于JVM GC机制不够熟悉,头脑里还是需要有这么一个基本概念: 应用层面生成大量长生命周期的对象,是给heap造成压力的主要原因,例如读取一大片数据在内存中进行排序,或者在heap内部建cache缓存大量数据。如果GC释放的空间有限,而应用层面持续大量申请新对象,GC频度就开始上升,同时会消耗掉很多CPU时间。严重时可能恶性循环,导致整个集群停工。因此在使用ES的过程中,要知道哪些设置和操作容易造成以上问题,有针对性的予以规避。
底层存储引擎是基于Lucene的
Lucene的倒排索引(Inverted Index)是先在内存里生成,然后定期以段文件(segment file)的形式刷到磁盘的。每个段实际就是一个完整的倒排索引,并且一旦写到磁盘上就不会做修改。 API层面的文档更新和删除实际上是增量写入的一种特殊文档,会保存在新的段里。不变的段文件易于被操作系统cache,热数据几乎等效于内存访问。
基于以上2个基本事实,我们不难理解,为何官方建议的heap size不要超过系统可用内存的一半。heap以外的内存并不会被浪费,操作系统会很开心的利用他们来cache被用读取过的段文件。
Heap分配多少合适?遵从官方建议就没错。 不要超过系统可用内存的一半,并且不要超过32GB。JVM参数呢?对于初级用户来说,并不需要做特别调整,仍然遵从官方的建议,将xms和xmx设置成和heap一样大小,避免动态分配heap size就好了。虽然有针对性的调整JVM参数可以带来些许GC效率的提升,当有一些“坏”用例的时候,这些调整并不会有什么魔法效果帮你减轻heap压力,甚至可能让问题更糟糕。
2ES的heap是如何被瓜分掉的?
以下分别做解读几个我知道的内存消耗大户:
Segment Memory
Segment不是file吗?segment memory又是什么?前面提到过,一个segment是一个完备的lucene倒排索引,而倒排索引是通过词典 (Term Dictionary)到文档列表(Postings List)的映射关系,快速做查询的。 由于词典的size会很大,全部装载到heap里不现实,因此Lucene为词典做了一层前缀索引(Term Index),这个索引在Lucene4.0以后采用的数据结构是FST (Finite State Transducer)。 这种数据结构占用空间很小,Lucene打开索引的时候将其全量装载到内存中,加快磁盘上词典查询速度的同时减少随机磁盘访问次数。
下面是词典索引和词典主存储之间的一个对应关系图:
说了这么多,要传达的一个意思就是,ES的data node存储数据并非只是耗费磁盘空间的,为了加速数据的访问,每个segment都有会一些索引数据驻留在heap里。因此segment越多,瓜分掉的heap也越多,并且这部分heap是无法被GC掉的! 理解这点对于监控和管理集群容量很重要,当一个node的segment memory占用过多的时候,就需要考虑删除、归档数据,或者扩容了。
怎么知道segment memory占用情况呢? &CAT API可以给出答案。
1. &查看一个索引所有segment的memory占用情况:
2. &查看一个node上所有segment占用的memory总和:
那么有哪些途径减少data node上的segment memory占用呢? 总结起来有三种方法:
删除不用的索引。
关闭索引 (文件仍然存在于磁盘,只是释放掉内存)。需要的时候可以重新打开。
定期对不再更新的索引做optimize (ES2.0以后更改为force merge api)。这Optimze的实质是对segment file强制做合并,可以节省大量的segment memory。
Filter Cache
Filter cache是用来缓存使用过的filter的结果集的,需要注意的是这个缓存也是常驻heap,无法GC的。默认的10% heap size设置工作得够好了,如果实际使用中heap没什么压力的情况下,才考虑加大这个设置。
Field Data cache
对搜索结果做排序或者聚合操作,需要将倒排索引里的数据进行解析,然后进行一次倒排。在有大量排序、数据聚合的应用场景,可以说field data cache是性能和稳定性的杀手。这个过程非常耗费时间,因此ES 2.0以前的版本主要依赖这个cache缓存已经计算过的数据,提升性能。但是由于heap空间有限,当遇到用户对海量数据做计算的时候,就很容易导致heap吃紧,集群频繁GC,根本无法完成计算过程。 ES2.0以后,正式默认启用Doc Values特性(1.x需要手动更改mapping开启),将field data在indexing time构建在磁盘上,经过一系列优化,可以达到比之前采用field data cache机制更好的性能。因此需要限制对field data cache的使用,最好是完全不用,可以极大释放heap压力。这里需要注意的是,排序、聚合字段必须为not analyzed。 设想如果有一个字段是analyzed过的,排序的实际对象其实是词典,在数据量很大情况下这种情况非常致命。
Bulk Queue
Bulk Queue是做什么用的?当所有的bulk thread都在忙,无法响应新的bulk request的时候,将request在内存里排列起来,然后慢慢清掉。一般来说,Bulk queue不会消耗很多的heap,但是见过一些用户为了提高bulk的速度,客户端设置了很大的并发量,并且将bulk Queue设置到不可思议的大,比如好几千。这在应对短暂的请求爆发的时候有用,但是如果集群本身索引速度一直跟不上,设置的好几千的queue都满了会是什么状况呢? 取决于一个bulk的数据量大小,乘上queue的大小,heap很有可能就不够用,内存溢出了。一般来说官方默认的thread pool设置已经能很好的工作了,建议不要随意去“调优”相关的设置,很多时候都是适得其反的效果。
Indexing Buffer
Indexing Buffer是用来缓存新数据,当其满了或者refresh/flush interval到了,就会以segment file的形式写入到磁盘。 这个参数的默认值是10% heap size。根据经验,这个默认值也能够很好的工作,应对很大的索引吞吐量。 但有些用户认为这个buffer越大吞吐量越高,因此见过有用户将其设置为40%的。到了极端的情况,写入速度很高的时候,40%都被占用,导致OOM。
Cluster State Buffer
ES被设计成每个Node都可以响应用户的api请求,因此每个Node的内存里都包含有一份集群状态的拷贝。这个Cluster state包含诸如集群有多少个Node,多少个index,每个index的mapping是什么?有少shard,每个shard的分配情况等等 (ES有各类stats api获取这类数据)。 在一个规模很大的集群,这个状态信息可能会非常大的,耗用的内存空间就不可忽视了。并且在ES2.0之前的版本,state的更新是由Master Node做完以后全量散播到其他结点的。 频繁的状态更新都有可能给heap带来压力。 在超大规模集群的情况下,可以考虑分集群并通过tribe node连接做到对用户api的透明,这样可以保证每个集群里的state信息不会膨胀得过大。
超大搜索聚合结果集的fetch
ES是分布式搜索引擎,搜索和聚合计算除了在各个data node并行计算以外,还需要将结果返回给汇总节点进行汇总和排序后再返回。无论是搜索,还是聚合,如果返回结果的size设置过大,都会给heap造成很大的压力,特别是数据汇聚节点。超大的size多数情况下都是用户用例不对,比如本来是想计算cardinality,却用了terms aggregation + size:0这样的方式; 对大结果集做深度分页;一次性拉取全量数据等等。
在开发与维护过程中我们总结出以下优化建议:
尽量运行在Sun/Oracle JDK1.7以上环境中,低版本的jdk容易出现莫名的bug,ES性能体现在在分布式计算中,一个节点是不足以测试出其性能,一个生产系统至少在三个节点以上。
倒排词典的索引需要常驻内存,无法GC,需要监控data node上segment memory增长趋势。
根据机器数,磁盘数,索引大小等硬件环境,根据测试结果,设置最优的分片数和备份数,单个分片最好不超过10GB,定期删除不用的索引,做好冷数据的迁移。
保守配置内存限制参数,尽量使用doc value存储以减少内存消耗,查询时限制size、from参数。
如果不使用_all字段最好关闭这个属性,否则在创建索引和增大索引大小的时候会使用额外更多的CPU,如果你不受限CPU计算能力可以选择压缩文档的_source。这实际上就是整行日志,所以开启压缩可以减小索引大小。
避免返回大量结果集的搜索与聚合。缺失需要大量拉取数据可以采用scan & scroll api来实现。
熟悉各类缓存作用,如field cache, filter cache, indexing cache, bulk queue等等,要设置合理的大小,并且要应该根据最坏的情况来看heap是否够用。
必须结合实际应用场景,并对集群使用情况做持续的监控。
本文转载自开源技术社区
TA的最新馆藏
喜欢该文的人也喜欢如何用Node.js 和Elasticsearch构建搜索引擎 - 蓝蓝设计_UI设计公司
如何用Node.js 和Elasticsearch构建搜索引擎
Elasticsearch&是一款开源的搜索引擎,由于其高性能和分布式系统架构而备受关注。本文将讨论其关键特性,并手把手教你如何用它创建 Node.js 搜索引擎。
Elasticsearch 概述
Elasticsearch 底层使用&Apache Lucene&库,Apache&Lucene&自身是一款高性能、基于文本的搜索引擎库。 Elasticsearch 并不以提供数据存储和检索等类数据库功能为核心目标,相反,它以搜索引擎(服务器端)为目标,意在提供数据索引、数据检索、和数据实时分析功能
Elasticsearch 采用分布式架构,因而通过新增节点、或者部署到系统已有节点上即可实现水平扩展。Elasticsearch 可以在数以百计的服务器上处理 PB级别的数据。水平扩展同时也意味着高可用性,如果有节点异常,数据可重新被调度执行。
一旦数据导入完成,即可被检索。Elasticsearch 提供无模式、JSON 格式文件存储、数据结构和类型自动检检测等功能。
Elasticsearch 同时采用完全 API 驱动,这意味着:几乎所有的操作都可在&HTTP 上通过使用符合 JSON 数据格式的Restful API 完成。Elasticsearch 提供多种程序语言的客户端 lib,包括 Node.js。本文档将使用&the official client library。
Elasticsearch 对软硬件要求比较灵活。虽然官方建议线上环境采用 64GB 内存,和尽可能多 CPU 系统配置,但其在一个资源受限的系统中依然可以很好地运行(前提是你的数据集不大)。如本文中的示例,2GB 内存,单核 cpu 系统即可。
Elasticsearch 支持主流操作系统,如 Linux、Mac os 和 Windows,只需安装最新版的 Java 运行时环境(参考 Elasticsearch 安装章节)。对于本文中的示例,还需要安装&Node.js&(v0.11.0 之后的版本都可) 和&npm。
Elasticsearch术语
Elasticsearch使用自己的术语,在某些情况下和典型的数据库系统中使用的术语不同。下面列出了Elasticsearch中常用的一些术语及其含义。
索引: 在Elasticsearch环境中,该术语有两个含义。第一个含义是添加数据的操作。当添加数据时,文本会被拆分成分词(token)(例如:单词),每个分词都会被索引。然而,一个索引也指的是所有索引数据的存储位置。通常,当我们导入数据时,数据会被索引成一个index。每次需要对数据执行任何操作时,都必须指定它的索引名。
类型:Elasticsearch在一个索引中对文档提供了更详细的分类,称为类型。一个索引中的每个文档还必须有一个类型。例如,我们可以定义一个图书馆(library)索引,然后再将数据索引成多种类型,比如,文章(article)、书(book)、报告(report)和演示(presentation)。由于索引几乎有固定的开销,所以建议使用较少的索引和较多的类型,而不是较多的索引和较少的类型。
检索: 如字面意思,你可以检索不同的索引和类型数据。Elasticsearch 提供了多种类型的检索关键字,如term、phrase、range、fuzzy,甚至还提供了地理数据的查询词。
过滤: Elasticsearch 支持过滤功能。根据不同的过滤规则过滤检索结果,以便进一步缩小检索结果集。Elasticsearch 依据相关性对文档进行排序。如果你为旧文档新增查询词,可能会触发文档的相关性排序,使得旧文档顺序发生变化。但如果只是新增过滤词,旧文档的顺序保持不变。
聚合: 可在不同类型的聚合数据上展开统计分析,比如minimum, maximum, average, summation, histograms, 等等.
建议: 针对文本输入,Elasticsearch&提供不同的建议类型,这些建议类型可以是一个单词、短语,甚至是完整的语句。
安装Elasticsearch
Elasticsearch&受Apache&2许可证保护,可以被下载,使用,免费修改。安装Elasticsearch&之前你需要先确保在你的电脑上安装了Java Runtime Environment (JRE)&,Elasticsearch&是使用java实现的并且依赖java库运行。你可以使用下面的命令行来检测你是否安装了java
java -version
推荐使用java最新的稳定版本(写这篇文章的时候是1.8)。你可以在这里找到在你系统上安装java的指导手册。
接下来是下载最新版本的Elasticsearch&(写这篇文章的时候是2.3.5),去下载页下载ZIP&文件。Elasticsearch&不需要安装,一个zip文件就包含了可在所有支持的系统上运行的文件。解压下载的文件,就完成了。有几种其他的方式运行Elasticsearch&,比如:获得TAR&文件或者为不同Linux发行版本的包(看这里)。
如果你使用的是Mac操作系统并且安装了&Homebrew&,你就可以使用这行命令安装Elasticsearch&brew install elasticsearch.Homebrew&会自动添加executables&到你的系统并且安装所需的服务。它也可以使用一行命令帮你更新应用:brew upgrade elasticsearch.
想在Windows上运行Elasticsearch&,可以在解压的文件夹里,通过命令行运行bin\elasticsearch.bat&。对于其他系统,可以从终端运行&./bin/elasticsearch.这时候,Elasticsearch&就应该可以在你的系统上运行了。
就像我之前提到的,你可以使用Elasticsearch的几乎所有的操作,都可以通过RESTful APIs完成。Elasticsearch&默认使用9200&端口。为了确保你正确的运行了Elasticsearch。在你的浏览器中打开http://localhost:9200/&,将会显示一些关于你运行的实例的基本信息。
如果想深入阅读关于安装和故障排除,可以访问文档.
图形用户界面
Elasticsearch不须图形用户界面,只通过REST APIs就提供了几乎所有的功能。然而如果我不介绍怎么通过APIs和&Node.js执行所有所需的操作,你可以通过几个提供了索引和数据的可视化信息GUI工具来完成,这些工具甚至含有一些高水平的分析。
Kibana, 是同一家公司开发的工具,&它提供了数据的实时概要,并提供了一些可视化定制和分析选项。Kibana&是免费的,这是详细文档
还有一些是社区开发的工具,如&elasticsearch-head,&Elasticsearch GUI, 甚至谷歌浏览器的扩展组件ElasticSearch Toolbox.这些工具可以帮你在浏览器中查看你的索引和数据,甚至可以试运行不同的搜索和汇总查询。所有这些工具提供了安装和使用的攻略。
创建一个Node.js环境
弹性搜索为Node.js提供一个官方模块,称为elasticsearch。首先,你需要添加模块到你的工程目录下,并且保存依赖以备以后使用。
npm install elasticsearch
然后,你可以在脚本里导入模块,如下所示:
const elasticsearch = require('elasticsearch');
最终,你需要创建客户端来处理与弹性搜索的通讯。在这种情况下,我假设你正在运行弹性搜索的本地机器IP地址是127.0.0.1,端口是9200(默认设置)。
const esClient = new elasticsearch.Client({
host: '127.0.0.1:9200',
log: 'error' });
日志选项确保所有错误被打印出来。在本篇文章末处,我将使用相同的esClient对象与Elasticsearch进行通讯。这里提供Node模块的复杂文档说明。
注意:这篇导读的所有源代码都可以在GitHub下载查看。最简单的查看方式是在你的PC机上克隆仓库,并且从那里运行示例代码:
git clone https: cd node-elasticsearch-tutorial
npm install
在本教程中,我将使用 1000 篇学术论文里的内容,这些内容是根据随机算法逐一生成的,并以 JSON 格式提供,其中的数据格式如下所示:
{ "_id": "c3a68c0a8ab3", "title": "Nostrud anim proident cillum non.", "journal": "qui ea", "volume": 54, "number": 11, "pages": "109-117", "year": 2014, "authors": [
{ "firstname": "Allyson", "lastname": "Ellison", "institution": "Ronbert", "email": "" },
], "abstract": "Do occaecat reprehenderit dolore ...", "link": "http://mollit.us/c3a68c0a8ab3.pdf", "keywords": [ "sunt", "fugiat",
], "body": "removed to save space" }
JSON 格式中的每个字段如字面意思,无需多余解释,但值得注意的是:由于&body&包含随机生成的文章的全部的内容(大概有100~200个段落),所以并未展示,若要获取完整数据,请访问这里.
虽然 Elasticsearch 提供了索引(indexing),更新(updating)、删除(deleting)单个数据的方法,但我们采用批量(bulk)接口导入数据,因为批量接口在大型数据集上执行操作的效率更高。
let bulkBody = [];
data.forEach(item =& {
bulkBody.push({
_index: index,
_type: type,
_id: item.id
bulkBody.push(item);
esClient.bulk({body: bulkBody})
.then(response =& { console.log('here'); let errorCount = 0;
response.items.forEach(item =& { if (item.index && item.index.error) { console.log(++errorCount, item.index.error);
}); console.log( `Successfully indexed ${data.length - errorCount} out of ${data.length} items` );
.catch(console.err);};const test = function test() { const articlesRaw = fs.readFileSync('data.json');
bulkIndex('library', 'article', articles);};
这里,我们调用函数bulkIndex建立索引,并传入 3 个参数,分别是:索引名 library,类型名library,JSON 数据格式变量 articles。bulkIndex函数自身则通过调用esClient对象的bulk接口实现,bulk 方法包含一个body属性的对象参数,并且每个body属性值是一个包含 2 种操作实体的数组对象。第一个实体是 JSON 格式的操作类型对象,该对象中的index属性决定了操作的类型(本例子是文件索引)、索引名、文件ID。第二个实体则是文件对象本身。
注意,后续可采用同样的方式,为其他类型文件(如书籍或者报告)添加索引。我们还可以有选择的每个文件分配一个唯一的ID,如果不体统唯一的ID,Elasticsearch 将主动为每个文件分配一个随机的唯一ID。
假设你已经从代码库中下载了 Elasticsearch 项目代码,在项目根目录下执行如下命令,即可将数据导入至Elasticsearch中:
$ node index.js 1000 items parsed from data file
Successfully indexed 1000 out of 1000 items
检查数据的索引是否准确
Elasticsearch 最大的特性是接近实时检索,这意味着,一旦文档索引建立完成,1 秒内就可被检索(见这里)。索引一旦建立完成,则可通过运行 indice.js 检查索引信息的准确性(源码链接):
const indices = function indices() { return esClient.cat.indices({v: true})
.then(console.log)
.catch(err =& console.error(`Error connecting to the es client: ${err}`));
client 中的cat 对象方法提供当前运行实例的各种信息。其中的 indices 方法列出所有的索引信息,包括每个索引的健康状态、以及占用的磁盘大小。 而其中的 v 选项为 cat方法新增头部响应。
当运行上面代码段,您会发现,集群的健康状态被不同的颜色标示。其中,红色表示为正常运行的有问题集群;黄色表示集群可运行,但存在告警;绿色表示集群正常运行。在本地运行上面的代码段,您极有可能(取决于您的配置)看到集群的健康状态颜色是黄色,这是因为默认的集群设置包含 5 个节点,但本地运行只有 1 个实例正常运行。鉴于本教程的目的仅局限于 Elasticsearch 指导学习,黄色即可。但在线上环境中,你必须确保集群的健康状态颜色是绿色的。
$ node indices.js
elasticsearch indices information:
health status index
pri rep docs.count docs.deleted store.size pri.store.size
yellow open
library 5 1 1000 0 41.2mb 41.2mb
动态和自定义映射
如前所述, Elasticsearch 无模式(schema-free),这意味着,在数据导入之前,您无需定义数据的结构(类似于SQL数据库需要预先定义表结构),Elasticsearch 会主动检测。尽管 Elasticsearch 被定义为无模式,但数据结构上仍有些限制。
Elasticsearch 以映射的方式引用数据结构。当数据索引建立完成后,如果映射不存在,Elasticsearch 会依次检索 JSON 数据的每个字段,然后基于被字段的类型(type)自动生成映射(mapping)。如果存在该字段的映射,则会确保按照同样的映射规则新增数据。否则直接报错。
比如:如果{“key1″: 12}&已经存在,Elasticsearch 自动将字段 key1 映射为长整型。现在如果你尝试通过{“key1″: “value1″, “key2″: “value2″} 检索, 则会直接报错,因为系统预期字段 key1 为长整型。同时,如果通过&{“key1″: 13, “key2″: “value2″} 检索则不会报错,并为字段 key2 新增 string 类型。
映射不能超出文本的范围,大都数情况下,系统自动生成的映射都可正常运行。如果想深入了解映射,建议查阅Elasticsearch 文档。
构建搜索引擎
一旦完成数据索引,我们就可以开始实现搜索引擎。Elasticsearch提供了一个直观的基于JSON的全搜索查询的结构-Query DSL,定义查询。有许多有用的搜索查询类型,但是在这篇文章中,我们将只看到几个通用的类型。关于Query DSL的完整文章可以在这里看到。
&请记住,我提供了每个展示例子的源码的连接。设置完你的环境和索引测试数据后,你可以下载源码,然后运行在你的机器上运行任何例子。可以通过命令行运行节点filename.js。
返回一个或多个索引的所有记录
为了执行我们的搜索,我们将使用客户端提供的多种搜索方法。最简单的查询是match_all,它可以返回一个或多个索引的所有的记录。下面的例子显示了我们怎么样获取在一个索引中获取所有存储的记录(源码).
const search = function search(index, body) { return esClient.search({index: index, body: body});
}; const test = function test() { let body = {
size: 20, from: 0,
match_all: {}
search('library', body)
.then(results =& { console.log(`found ${results.hits.total} items in ${results.took}ms`); console.log(`returned article titles:`);
results.hits.hits.forEach(
(hit, index) =& console.log( `\t${body.from + ++index} - ${hit._source.title}` )
.catch(console.error);
主要的搜索查询包含在Query对象中。就像我们接下来看到的那样,我们可以添加不同的搜索查询类型到这个对象。我们可以为每一个Query添加一个查询类型的关键字(如match_all),让这个Query成为一个包含搜索选项的对象。由于我们想返回索引的所有记录,所以在这个例子中没有查询选项。
除了Query对象,搜索体中可以包含其他选项的属性,如&size&和from。size属性决定了返回记录的数量。如果这个值不存在,默认返回10个记录。from属性决定了返回记录的起始索引,这对分页有用。
理解查询API的返回结果
如果你打印搜索API返回结果(上面例子的结果)日志。由于它包含了很多信息,刚开始看起来无所适从。
{ took: 6,
timed_out: false,
_shards: { total: 5, successful: 5, failed: 0 }, :
{ total: 1000,
max_score: 1,
[ [Object],
[Object] ] } }
在最高级别日志输出里,返回结果中含有took&属性,该属性值表示查找结果所用的毫秒数,timed_out只有在最大允许时间内没有找到结果时为true,_shards&是不同节点的状态的信息(如果部署的是节点集群),hits是查询结果。
hits的属性值是一个含有下列属性的对象:
total&—表示匹配的条目的总数量
max_score&—&找到的条目的最大分数
hits&—&找到的条目的数组,在hits数组里的每一天记录,都有索引,类型,文档,ID,分数,和记录本身(在_source元素内)。
这十分复杂,但是好消息是一旦你实现了一个提取结果的方法,不管你的搜索查询结果时什么,你都可以使用相同的格式获取结果。
还需要注意的是Elasticsearch&有一个好处是它自动地给每一个匹配记录分配分数,这个分数用来量化文件的关联性,返回结果的顺序默认的按钮分数倒排。在例子中我们使用match_all取回了所有的记录,分数是没有意义的,所有的分数都被计算为1.0。
匹配含指定字段值的文档
现在我们看几个更加有趣的例子. 我们可以通过使用 match 关键字查询文档是否与指定的字段值匹配。一个最简单的包含 match 关键字的检索主体代码如下所示:(源码链接).
query: 'search terms go here' }
如上文所述, 首先通过为查询对象新增一个条目,并指定检索类型,上面示例给的是 match 。然后再检索类型对象里面,申明待检索的文档对象,本例是 title 文档对象。然后再文档对象里面,提供相关检索数据,和 query 属性。我希望你测试过上述示例之后,惊讶于 Elasticsearch 的检索效率。
上述示例执行成功后,将返回title(标题)字段与任一 query 属性词匹配的所有文档信息。同时还可以参考如下示例,为查询对象附加最小匹配数量条件:
query: 'search terms go here',
minimum_should_match: 3 }
与该查询匹配的文档 title(标题)字段至少包含上诉指定的 3 个关键词。如果查询关键词少于 3个,那么匹配文档的 title(标题)字段必须包含所有的查询词。Elasticsearch 的另一个有用的功能是&fuzziness(模糊匹配).这对于用户输入错误的查询词将非常有用,因为fuzzy(模糊匹配)将发现拼写错误并给出最接近词供选择。对于字符串类型,每个关键字的模糊匹配值是根据算法&Levenshtein distance&算出的最大允许值。fuzziness(模糊匹配)示例如下所示:
: { title: {
query: 'search tems go here',
minimum_should_match: 3,
fuzziness: 2 }
多个字段搜索
如果你想在多个字段中搜索,可以使用multi_match搜索类型。除了Query对象中的fields属性外,它同match有点类似。fields属性是需要搜索的字段的集合。这里我们将在title,authors.firstname, 和authors.lastname&字段中搜索(源码)。
multi_match: {
query: 'search terms go here',
fields: ['title', 'authors.firstname', 'authors.lastname']
multi_match查询支持其他搜索属性,如minimum_should_match&和fuzziness。Elasticsearch支持使用通配符(如*)匹配字段,那么我们可以使用['title', 'authors.*name']把上面的例子变得更短些。
匹配一个完整的句子
Elasticsearch也支持精确的匹配一个输入的句子,而不是在单词级别。这个查询是在普通的match&查询上扩展而来,叫做&match_phrase。下面是一个match_phrase的例子(源码)
query: 'search phrase goes here',
type: 'phrase' }}
联合多个查询
到目前为止,在例子中我们每次请求只使用了单个查询。然而Elasticsearch允许你联合多个查询。最常用的复合查询是bool,bool查询接受4种关键类型must,&should,&must_not, 和filter.&像它们的名字表示的那样,在查询结果的数据里必须匹配must里的查询,必须不匹配must_not里的查询,如果哪个数据匹配should里的查询,它就会获得高分。每一个提到的元素可以使用查询数组格式接受多个搜索查询。
下面,我们使用bool查询及一个新的叫做query_string的查询类型。它允许你使用&AND&或&OR写一些比较高级的查询。在这里可以看到&query_string语法的所有文档。另外,我们使用了&range查询(文档在这里),它可以让我们通过给定的范围的方式去限制一个字段。(源码)
query_string: {
query: '(authors.firstname:term1 OR authors.lastname:term2) AND (title:term3)' }
query: 'search phrase goes here',
type: 'phrase' }
must_not: [
gte: 2011,
lte: 2013 }
在上面的例子中,查询返回的数据,作者的名包含term1&或它们的姓包含term2,并且它们的title含有term3,而且它们不在或2013年出版的,还有在body字段里含有给定句子数据将获得高分,并被排列到结果的前面(由于在should从句中的match&查询)。
过滤,聚合,和建议
除了它先进的搜索功能外,Elasticsearch&还提供了其他的功能。接下来,我们再看看其他三个比较常用的功能。
也许,你经常想使用特定的条件凝缩查询结果。Elasticsearch通过filters&提供了这样的功能。在我们的文章数据里,假设你的查询返回了几个文章,这些文章是你选择的在5个具体年份发布的文章。你可以简单的从搜索结果中过滤出那些不匹配条件的数据,而不改变查询结果的顺序。
在bool&查询的must&从句中,过滤和相同查询之间的不同之处在于,过滤不会影响搜索分数,而must&查询会。当查询结果返回并且用户使用给定的条件过滤时,他们不想改变结果的顺序,相反地,他们只想从结果中移除不相关的数据。过滤与搜索的格式一样,但在通常情况下,他们在有明确值的字段上定义,而不是文本字符串上。Elasticsearch&推荐通过bool复合查询的filter从句添加过滤。
继续看上面的例子,假设我们想把搜索结果限制在在年之间发布的文章里。这样做,我们只需要在一般搜索查询的filter&部分添加range&查询。这将会从结果中移除那些不匹配的数据。下面是一个过滤查询的例子(源码)。
title: 'search terms go here' }
gte: 2011,
lte: 2015 }
聚合框架会基于一次搜索查询,提供各种聚合数据和统计信息。两个主要的聚合类型是度量和分块, 度量聚合会对一个文档的集合进行持续的跟踪并计算度量,而分块聚合则会进行块的构建,每个块都会跟一个键和一个文档查询条件关联起来。度量聚合的示例有平均值,最小值,最大值,加总值还有计数值。分块聚合的示例有范围、日期范围、直方图以及主题项。对聚合器更加深入的描述可以在&这里&找到。
聚合可以放置在一个 aggregations 对象里面,而对象自己则是被直接放到 search 对象体中。在 aggregations 对象里面,每一个键都是由用户赋予一个聚合器的名称。聚合器的类型和其它选项都应该是作为这个键的值而放置的。接下来我们要来看看两个不同类型的聚合器,一个是度量的,一个块的。我们会用度量聚合器来尝试找出数据集合中最小的年份值(也就是最久远的文章),而使用块集合器我要做的就是尝试找出每一个关键词各自出现了多少次。(源代码链接)
aggregations: {
min_year: {
min: {field: 'year'}
keywords: {
terms: {field: 'keywords'}
在上述示例中,我们将度量聚合器命名为 min_year&(也可以是其它名称), 也就是 year 这个域上的 min 类型。块聚合器责备命名为&keywords, 就是 keywords 这个域上的 terms 类型。聚合操作的结果被装在了响应消息里的 aggregations 元素里面,更深入一点会发现里面包含了每一个聚合器(这里是 min_year 和 keywords)以及它们的聚合操作结果。 如下是来自这个示例响应消息中的部分内容。
... "aggregations": { "keywords": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 2452, "buckets": [
{ "key": "pariatur", "doc_count": 88 },
{ "key": "adipisicing", "doc_count": 75 },
}, "min_year": { "value": 1970 }
响应消息中默认最多会有10个块返回。你可以在请求中 filed 的边上加入一个size键来规定返回的块的最大数量。如果你想要接收到所有的块,可以将这个值设置为 0。
Elasticsearch 提供了多种可以对输入内容提供替换和补全的关联项推荐器(见文档)。下面将介绍术语和短语推荐器。术语推荐器为每个输入文本中的术语提供关联推荐(如果有的话),而短语推荐器将整个输入文本看做一个短语(与将其拆分成术语对比),然后提供其他短语的推荐(如果有的话)。使用推荐API时,需要调用Node.js client的suggest方法。如下为术语推荐器的示例。(见源码)
esClient.suggest({
index: 'articles',
text: 'text goes here',
titleSuggester: {
field: 'title',
}).then(...)
与其他client的方法相同,在请求体中包含一个index字段指明采用的索引。在body字段中添加查询推荐的文本,然后给每个推荐器一个(包含了聚合对象的)名称(本例中的titleSuggester)。其值指明了推荐器的类型和配置。这里,为title字段使用了术语推荐器,限制最大建议的数量是每个token最多5个(size: 5)。
建议API返回的数据中包含了对应请求中每一个建议器的key,其值是一个与你输入文本中术语数量相同的一个数组。对于数组中的每一个元素,包含一个options数组,其每个对象的text字段中包含了推荐的文本。如下是上面例子中返回数据的一部分。
... "titleSuggester": [
{ "text": "term", "offset": 0, "length": 4, "options": [
{ "text": "terms", "score": 0.75, "freq": 120 },
{ "text": "team", "score": 0.5, "freq": 151 }
获取短语推荐的时候,采用与上文相同的格式并替换推荐器的类型字段即可。如下的例子中,返回数据将与上例格式相同。(见源码)
esClient.suggest({
index: 'articles',
text: 'phrase goes here',
bodySuggester: {
field: 'body' }
}).then(...).catch(...)
进一步阅读
Elasticsearch 提供了许多特性,这些特性远远超出了这一篇文章所能讨论的范围。在这篇文章中,我试图站在一个很高的层次上来解释它的特性,并为你提供可用来进一步学习的合适资源。Elasticsearch是非常可靠的,并且有着出色的表现(我希望你在运行范例时已经注意到了这一点)。再加之不断增长的社区支持,使得Elasticsearch在工业中的应用也在不断增加,尤其是对于需要处理实时数据或大数据的公司。
(&)是一家专注而深入的,为期望卓越的国内外企业提供卓越的UI界面设计、、&、&、&、&、&、&
富兰克林曾经说过:“如果你对自己没有一个正确的定位,即使是宝贝,放错了地方也是一堆废物。”的确,找准自己的位置对于每一个成功的人来说都非常重要。 16:57
上周五参加了ixdc国际用户体验大会,有3000名从业者,几十个工作坊。我参加的工作坊是“设计管理驱动创新力“,会后自发组织了微信群,大家在这之后的一周里,保持着每天几千条微信的热情问问题,看完要很久好累。但可感受到这个行业工作者的上进努力。有问题挺好,问题本身有时比答案更重要。 08:40
深泽直人的设计中,一切都回归本原。靠事物本身所散发的魅力,设计所给人的感觉,第一并不是好用,而是深入人心,倍感亲切。第二才是好用。 21:19
设计认识有三个层次,即认知,模仿,本原。一般来说,大多数的设计师只到了模仿+创新的层次,因为,他们的想法是基于固有案例+本品需求基础上的创新,虽然设计的很棒,但内涵通常都不深厚,反映设计思想浮夸的一面。 21:19
何为强? 战胜不安和恐惧,把自己锻炼的锐利光滑,百尘不侵,这样的力量就是强了吧!
所谓世界第一行业第一名应该由谁来决定?我们追求的不是这种东西,心里面那个不变的理想和目标才是支持我们继续下去的动力。 11:03

我要回帖

更多关于 btsearchs搜索引擎 的文章

 

随机推荐