面试官: 项目中有用到Elasticsearch?
了不起: 有的
面试官: 知道如何提升查询效率吗?
数据写入过程
- 数据写入到内存buffer
- 同时写入到数据到translog buffer,这是为了防止数据不会丢失
- 每隔1s数据从buffer中refresh到FileSystemCache中,生成segment文件,这是因为写入磁盘的过程相对耗时,借助FileSystemCache,一旦生成segment文件,就能通过索引查询到了
- refresh完,memory buffer就清空了, 但是translog并不会被清空。
- 每隔5s中,translog 从buffer flush到磁盘中(6.0开始每次请求translog都会落盘)
- 定期/定量从FileSystemCache中,结合translog内容flush index到磁盘中。做增量flush的。
- 当translog达到一定程度的时候会出发一次commit操作, 也叫flush操作(默认情况下30分钟或者512M执行一次flush)。可以用index.translog.flush_threshold_period和index.translog.flush_threshold_size修改默认配置。
commit(flush)程分为以下几步:
7.1 Memory buffer清空并且Refresh
7.2 调用fsync, 将FileSystemCache中的Segments写入磁盘
7.3 清空删除translog重启一个新的translog
ES利用这个commit point来决定哪些Segments属于当前shard。Commit point和Segments的关系如下
这里有两个知识点
- ES之所以被称为准实时是因为数据refresh到了FileSystemCache之后就能被搜索到
- [数据可能会丢失每隔5秒translog才会落盘,如果这个时候机器宕机,内存数据就没有了,所以有5秒的数据丢失]
数据查找过程
1、Query阶段 ⽤用户发出搜索请求到 ES 节点。节点收到请求 后, 会以 Coordinating 节点的身份,在 6 个 主副分⽚片中随机选择 3 个分片,发送查询请求。被选中的分⽚执⾏行查询,进行排序。然后,每 个分片都会返回 From Size 个排序后的⽂文 档 Id 和排序值 给 Coordinating 节点。2、Fetch阶段 Coordinating Node 会将 Query 阶段,从 从每个分⽚片获取的排序后的文档 Id 列表, 重新进行排序。选取 From 到 From Size 个⽂文档的 Id。最后以 multi get 请求的⽅方式,到相应的分⽚片获 取详细的⽂文档数据
提高读取效率
杀手锏FileSystemCache
根据之前数据写入过程分析我们可以看到,如果数据已经写入了Filesystem cache, 那么数据就能被搜索到了,所以如果给Filesystem cache更多的内存, 让内存容纳所有的Segment file索引文件, 性能会很高。有人做过测试,走磁盘的搜索性能是秒级的,纯走内存基本是毫秒级别,从几毫秒到几百毫秒不等。怎样才能尽量把数据都存在FileSystem cache里?可以采用ES Mysql/Hbase的架构。举个例子, 假如你有一行数据差不多有30个字段,如:id, name, age...。ES中仅仅存储用来检索的少数几个字段。其他字段都存在HBase中。从ES中根据name和age进行检索,拿到doc id, 然后根据doc id去Hbase中查询doc id对应的完整数据,返回给前端。写入ES的数据量最好小于等于Filesystem cache的内容容量。采用这种方式,从ES检索花费20ms, 去查询HBase花费30m,总共也就50ms, 相比于把1T数据都放在ES中检索花费5~10s, 性能提升很大
数据预热
虽然FileSystem cache是杀手锏,但是难免会有机器配置不高,ES的数据量还是远远大于Filesystem cache的情况, 这种情况下可以做数据预热。举个例子,对于电商平台来说,iphone是比较热门的商铺,可以自己搞一个后端程序,每隔一段时间访问一下iphone, 就能把数据刷到filesystem cache中。所以最好做一个专门的缓存预热子系统, 让数据进入到FileSystem cache中。
冷热分离
类似mysql的水平拆分,对于大量的访问频率比较少的数据,单独建一个索引存储,和热数据区分开。假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据。热数据可能就占总数据量的 10%,此时数据量很少,几乎全都保留在 filesystem cache 里面了,就可以确保热数据的访问性能是很高的。但是对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,大家互相之间都没什么联系了。
分页性能优化
如果每页有10条数据,你现在要查询第100页,实际会把每个shard上存储的前1000条数据查到协调节点,有5个shard就有5000条数据,然后协调节点再进行合并处理,最终获得第100页的10条数据。可能翻到第10页就要5到10秒的时间了。
- 和产品经理协商不允许深度翻页
- 采用类似微博下拉加载新数据的交互,参考Scroll API
数据建模
- ES里面复杂的关联模型尽量不要使用。尽可能Denormalize数据
- 使用Nested类型数据,查询会慢几倍
- 使用Parent/Child关系,查询会慢几百倍
- 尽量使用Filter Context, 利用缓存机制减少不必要的算法如下例子
- 应该避免在查询时使用脚本, 可以使用Index pipeline替代
- 优化分片
- 避免Over Sharding
- 控制单个分片的尺寸
- For-merge read-only 索引
提高写入效率
客户端
- 每个bulk请求体数据量不要太大,官方建议5-15MB
- bulk请求超时时长可以大一点,建议60s
- 写入端尽量将数据打到不同的节点
服务端
- 降低IO操作
- 使用ES自动生成的文档ID,修改ES相关配置,比如提高Refresh inveral时间
- 降低CPU和存储开销
- 减少没有必要的分词,避免不必要的doc_values(可以节省磁盘空间), 文档字段每次写入的时候保证相同顺序,提高文档压缩率
- 尽可能做到写入和分片的负载均衡
- Shard Filtering/Write Load Balancer
- 调整Bulk线程池和队列
关闭没必要的功能
下面是设置mapping的例子
代码语言:javascript复制{
"english":{
"_source": {
"enabled": false
},
"properties": {
"content":{
"type":"text",
"store":true,
"index":false
},
"title":{
"type":"text",
"store":true,
"index":false
}
}
}
}
1、不需要聚合和搜索时, index设置成false,比如上面的title
和content
2、不要对字符串使用默认的dynamic mapping, 比如text类型会默认额外生成keywords, 字符串数量过多时,会对性能产生影响
3、index_options可以控制在创建倒排索引的时候,哪些内容可以被加入到倒排索引中,可以节省CPU
4、关闭_source, 比如上面的english。这样可以减少IO操作,适合指标型数据
针对性能的取舍
如果追求极致的写入速度,可以牺牲数据可靠性和搜索的实时性换取性能。
- 牺牲可靠性:将副本分片设置为0,写入完毕再调整回去
- 牺牲搜索实时性:增加Refresh interval的时间
- 牺牲可靠性:修改Translog的配置, 如下请求,可以增大间隔时间,同时改成了异步写的方式
PUT /my_index/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "60s"
}
下图是一个索引优化的例子