ElasticSearch
、Lucene、solr、搜索、facet、高可用、可伸缩、mongodb、SearchHub、商品中心
本文档适用人员:研发和运维
- 曾经的基于MongoDB的筛选+排序解决方案
- MongoDB方案的缺陷
- 看中了搜索引擎的facet特性
- 看中了ES的简洁
- 看中了ES的天生分布式设计
- 窝窝的ES方案
- ES的几次事故和教训
- ES自身存在的问题
一,曾经的基于 MongoDB 的筛选+排序解决方案
持久化缓存模式,尽量减少接口调用
”:
|
二,MongoDB 方案的缺陷
由于频道页流量小于首页,尤其是用户很少点击到的深度筛选条件组合查询,所以下图中的所有枚举项商品数量都容易缓存失效或缓存挤出: 图2 筛选越来越复杂,标题数字却要保持准确性 一旦缓存失效后,但凡我从上图的“20元以下”点击切换到“51-80元”或做更深层次筛选,那么程序就要针对上面所有组合条件对 MongoDB 商品记录逐一做 count 计算。 虽然每一个 count 计算都很快不属于慢查询,但也架不住多啊,尤其是配上区县和商圈等动辄6、7层深的筛选组合,点击一次轻易就涉及成百次的 count 计算,代价还是很大的。 由于在商城模式下,不同频道很可能不断增加新筛选条件, 导致筛选组合越来越复杂,最终可能要求我们从基于 NoSQL 的排序和筛选方案,尽快转变为基于搜索引擎的排序和筛选方案 。 |
MongoDB 的索引命中率不高
,MongoDB一旦没有命中索引,
其查询效率会直线下降
,从而造成整个MongoDB的压力增大响应变慢(MongoDB 的索引策略基本和 MySQL 的差不多)。有段时间,我们不止一次遇到由于 MongoDB 的慢查,拖挂所有前台工程的情况,焦头烂额。
技术选型主要集中在 solr 和 ES 这两个均构建于 Lucene 之上的搜索引擎
。
http://solr-vs-elasticsearch.com/
三,看中了搜索引擎的 facet 特性
介绍分面 分面是指事物的多维度属性。例如一本书包含主题、作者、年代等分面。而分面搜索是指通过事物的这些属性不断筛选、过滤搜索结果的方法。可以将分面搜索看成搜索和浏览的结合。
|
facet 特性对我们最大优点是,查询结果里自带 count 信息,无需我们单独计算不同排列组合的 count 信息,一举扫清性能瓶颈。
- Field Facet:如果需要对多个字段进行Facet查询,那么将 facet.field 参数声明多次,Facet字段必须被索引;
- Date Facet:时间字段的取值有无限性,用户往往关心的不是某个时间点而是某个时间段内的查询统计结果,譬如按月份查;
- Facet Query:利用类似于filter query的语法提供了更为灵活的Facet,譬如根据价格字段查询时,可设定不同价格区间;
五,看中了 ES 的天生分布式
访谈提及 Github 是如何使用 ES
:1,用了 40~50 个索引,包括库、用户、问题、pull请求、代码、用户安全日志、系统异常日志等等;2,44台 EC2 主机处理 30T 的数据,每台机器配备 2T SSD 存储;3,8台机器仅仅用于搜索,不保存数据。当然,Github 也曾经在 ES 升级上栽过大跟头,那是2013年1月17日的事儿了,参考《2013,GitHub使用elasticsearch遇到的一些问题及解决方法,
中文译稿
,
英文原文
》。
六,窝窝的 ES 方案
6.1>架出一层 SearchHub
6.2>通过 NotifyServer 来异步更新各个系统
6.3>索引设计方案
6.3.1>商品索引设计
商品维度是我们主要的查询维度,其业务复杂度也比较高。针对网站查询特性,我们的商品主索引方案为:每个城市建立一个
index,所以一共有
400多个
index,每个城市仅有
1个主
shard(不分片)。这样做的好处是以后我们根据热点城市和非热点城市,可以将各个
index 手工分配到不同的
node 上,可以做很多优化。
其结构为:
图7 goodsinfo
为了减少索引量和功能拆分,减少商品索引的内存占用,所以我们把全文检索单独建为一个索引。
每个城市索引或者商品索引按频道分为几个
type,如下图所示。
图9 type
商品频道映射到
es的
type是很容易理解的,因为每个频道的模型不同:有的频道特有“用餐人数”属性,有的频道特有“出发城市”和“目的地城市”属性 所以每个频道对应一个
es的
type,每个
type绑定一种特定的
mapping(这个
mapping里面可以指定该频道各自的特殊属性如何储存到
ES)。
6.3.2>门店索引设计
6.4>集群节点设计方案
6.5>分词设计
6.6>高可用和可伸缩方案
看一下窝窝商品索引,窝窝采用的方案是一个城市一个索引,所有索引的“副本(replicas)”都设为
1,这样比如 shop_index,它有 5 个 shard,每个 shard (只)有一个副本。(注:1个副本一方面可以省空间,另一方面是为了效率,在 ES 0.90版本下,ES 的副本更新是全量备份的方案,多个副本就会有更新效率的问题。ES 1.0 后有改进,王超认为在增加服务器后,可以考虑多增加副本。)
ES 会保证所有
shard 的主副本不在同一个
node 上面,
但我们是
ES 服务器集群,每台服务器上有多个
node,一个
shard 的主副本不在同一个节点还是不够的
,我们还需要一个
shard 的主副本不在同一台服务器,甚至在多台物理机的情况下保证要保证不在同一个机架上,才可以保证系统的高可用性。
所以
ES提供了一个配置:
cluster.routing.allocation.awareness.attributes: rack_id
。
这个属性保证了主副
shard 会分配到名称不同的
rack_id 上面。
当我们停止一个节点时,如停止
174_node_2,则
ES 会自动重新平衡数据,如下图所示:
图13 重新分布
即使一台物理机完全
down 掉,我们可以看到其他物理机上的数据是完整的,
ES 依然可以保证服务正常。
七,ES 的几次事故和教训
7.1>误删数据
是在没有给定索引的情况下,误执行了DELETE 操作,结果删除了全部索引
。其实配一下 ES 是可以避免的,加入这个配置:
action.disable_delete_all_indices=true
7.2>mvel 脚本引发的ES事故
ES集群表象:
一天,ES 各个节点负载升高,
JVM Full GC 频繁。
查看其内存使用状况发现,
ES 各个节点的
JVM perm 区均处于满或者将要满的状态,如下图所示:
图15 当时perm的容量
注1:jstat -gc <pid>命令返回结果集中,上图红色方框中字段的含义为:
PC Current permanent space capacity (KB). 当前perm的容量 ;
PU Permanent space utilization (KB). perm的使用。
大家可以看到图1中的PU值基本等于PC值了。
问题原因:
头一天上线了商品搜索的一个动态排序功能,它采用 ES 的
mvel 脚本
来动态计算商品的排序分值。而 mvel 的原理是基于 JIT,动态字节码生成的,于是很有可能造成 perm 区持续升高,原因是它不断地加载和生成动态 class。
由于 ES 各个节点的
perm 区接近饱和状态,所以造成了服务器负载升高,
GC 频繁,并进一步造成
ES 集群出现了类似于“脑裂”的状态。
经验教训:
-
引入新技术,还是要谨慎,毕竟如果真是
mevl 脚本引起的问题,其实线下做压力测试就能提前发现。
-
加强
ES的监控。
- 虽然现在回过头来看,如果在第一时间重启所有 nodes,损失应该是最小的——但是王超认为当时采用的保守策略依然是有意义的,因为在弄清楚问题原因之前,直接重启 nodes 有可能反而造成更大的数据破坏。
7.3>mark shard as failed 的 ES事故
问题现象:
marked shard as started, but shard has not been created, mark shard as failed
]
临时解决办法:
经验教训:
可以确定此次问题并不是新配置导致的
。
ES 自身的集群状态管理(至少在 0.90.2 这个版本上)是有问题的
——先是从正常状态逐渐变为“越来越多的 shard 损坏”,重启之后数据又完全恢复,所有 shard 都没有损坏。
故障原因不明
,所以随后安排 ES 从 0.90 版本升级到 1.3 版本。
八,ES 存在的问题以及改进
Elastic Search 在窝窝运行几年来基本稳定,可靠性和可用性也比较高,但是也暴露了一些问题:
-
ES 的更新效率,作为基于
lucene 的分布式中间件,受限于底层数据结构,所以其更新索引的效率较低,
lucene 一直在优化;
-
ES 的可靠性的前提是保证其集群的整体稳定性,但我们遇到的情况,往往是当某个节点性能不佳的情况下,可能会拖累与其同服务器上的所有节点,从而造成整个集群的不稳定。
-
其实解决这个问题不难,两种方法:
-
-
增加服务器,让节点尽可能地散开;
-
当某个节点出现问题的时候,需要我们及早发现处理,不至于拖累整个集群。其实监控一个节点是否正常的方法不难,ES 是基于
JVM 的服务,它出现问题,往往和
GC、和内存有关,所以只要监控其内存达到某个上限就报警即可;
-
没有一个好的客户端可视化集群管理工具,官方或者主流的可视化管理工具,基本都是基于
ES 插件的,不符合我们的要求,所以需要一款可用的客户端可视化集群管理工具;
ES 的升级问题,由于
ES 是一个快速发展的中间件系统,每一次新版本的更新,更改较大,甚至导致我们无法兼容老版本,所以
ES 升级问题是个不小的问题,再加上我们数据量较大,迁移也比较困难。
——
END——
窝窝的解决方案介绍列表:
#研发解决方案#基于StatsD+Graphite的智能监控解决方案
es术语介绍:
cluster:
代表一个集群,集群中有多个节点,其中有一个为主节点。这个主节点是可以通过选举产生的。注意,主从节点是对于集群内部来说的。
es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于
集群外部来说的,因为从外部来看
es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个
es集群通信是等价的。
shards
代表索引分片。
es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
replicas
代表索引副本,
es可以设置多个索引的副本。副本的作用,一是提高系统的容错性,当某个节点的某个分片损坏或丢失时可以从副本中恢复,二是提高
es的查询效率,
es会自动对搜索请求进行负载均衡。
recovery
代表数据恢复或叫数据重新分布,
es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
river
代表
es的一个数据源,也是其他存储方式(如:数据库)同步数据到
es的一个方法。它是以插件方式存在的一个
es服务,通过读取
river中的数据并把它索引到
es中,官方的
river有
couchDB的,
RabbitMQ的,
Twitter的,
Wikipedia的。
gateway
代表
es索引快照的存储方式。
es默认是先把索引存放到内存中,当内存满了时再持久化到本地硬盘。
gateway对索引快照进行存储,当这个
es集群关闭再重新启动时,就会从
gateway中读取索引备份数据。
es支持多种类型的
gateway,有本地文件系统(默认),分布式文件系统,
Hadoop的
HDFS和
amazon的
s3云存储服务。
discovery.zen
代表
es的自动发现节点机制。
es是一个基于
p2p的系统,它先通过广播寻找存在的节点,再通过
多播
协议来进行节点之间的通信,同时也支持点对点的交互。
Transport
代表
es内部节点或集群与客户端的交互方式。默认内部是使用
tcp协议进行交互,同时它支持
http协议(
json格式)、
thrift、
servlet、
memcached、
zeroMQ等的传输协议(通过插件方式集成)。