我当过几次晋升答辩的评委,ES应该算是答辩过程中比较热点出现的词。
场景一:
问:“当你们的业务主表数据量大了,会不会有性能瓶颈?你准备如何处理?分库、分表、还是历史数据归档?”
答:“没事,我准备把数据放在ES里一份,不会有性能问题。”
问:“为什么存ES就不会有性能问题?”
答:“。。。”
场景二:
问:“你们现在系统的QPS越来越高了,后续准备怎么优化?”
答:“我准备把数据放在ES里一份,用ES扛。”
问:“为什么存ES里,就能扛住更大的QPS?”
答:“再多加服务器。”
问:“。。。”
场景三:
问:“你们的系统被上游系统依赖的越来越多,查询维度越来越多,你准备如何解决?”
答:“我准备把数据放在ES里一份,用ES解决查询维度的问题。”
问:“实时查询场景怎么办?”
答:“。。。”
我在想,现在的ES是不是热水化了,脑海里浮现了这些场景:
女:“亲爱的,我肚子疼。”
男:“多喝点儿热水。”
女:“亲爱的,我今天心情不好。”
男:“多喝点儿热水。”
女:“亲爱的,我感觉很孤独。”
男:“多喝点儿热水。”
ES确实是一件利器,作为MySQL和Redis的补充,解决很多复杂查询场景的问题。但是,ES本身也是需要根据适用场景的进行调优,才能使其优势最大化。
下面说几个实用的Elasticsearch优化策略:
force merge
索引是由若干分片构成,一个分片是一个物理上的Lucene索引,一个分片包含若干个索引段(Segment)。
当写入一条文档的时候,实际是写入到内存缓冲区中,而默认每秒钟一次的refresh,则是将内存buffer中的数据写入到一个新的段中,使其搜索查询可见(by id查询可做到实时)。
一个段被写入后,就不能再被删除和更新。因此,被删除的文档则是在.del文件中被标记删除。被更新的文档,实际上是删除旧文档,再添加新的文档。在查询时,需要从返回结果中过滤掉已删除的文档。
- 如果数据更新或删除的数据比较多,那么每个段中的有效数据密度会变低,降低了查询的性能。
- 每个搜索请求都必须轮流检查每个段,所以段越多,搜索也就越慢。
因此,通过段合并,可以将小的段合并成大的端,并将已删除的数据从段中物理删除,以此提高搜索性能。
不过,由于段合并对系统资源,IO和CPU产生巨大消耗,建议在凌晨业务低峰期进行。
操作指令:POST /${index_name}/_forcemerge?max_num_segments=1
自定义路由
自定义路由能够很好地提升查询性能,当我们在执行一个搜索请求的时候:
1、请求会被随机发送到索引的一个节点。
2、该节点再将这个查询请求分发到这个索引的每个分片上,可能是主分片,也可能是副本分片。
3、每个分片执行这个搜索查询并返回结果到最初接受请求的节点。
4、该节点在进行合并和排序后并返回给客户端。
而自定义路由模式,可以使我们的查询更具目的性,我们不必盲目地去分发查询请求,取而代之的是:我们要告诉ES我们的数据在哪个分片上,这样就不用查询多个分片,并最终归并查询结果。
操作指令:
PUT /index/student/1?routing=key1
{
"name":"Nancy",
"age":10
}
GET /index/student/_search?routing=key1
{
"query": {
"term": {
"name": "Nancy"
}
}
}
分片数量
ES采用过度分配机制,默认5个分片和一个副本。
其在单节点环境也是如此分配,优势在于,在扩容时仅需要增加服务器,ES会重新平衡集群,将部分分片移到新机器上;否则的话,需要重新创建一个拥有更多分片的新索引,并重新索引数据。
如果我们可以根据一些已知场景进行分片分配,则是一种非常行之有效的优化手段。
举个例子:我们预知索引数据增长有限,那么我们可以不分配这么多的分片,甚至只使用一个分片,这样在进行查询请求分发和查询结果归并的时候,执行速度会变得更快。
也就是说,分片的数量不是越多越好,而是越适合于索引的数据量越好。
refresh_interval
如前面所说,当写入一条文档的时候,实际是写入到内存缓冲区中,而默认每秒钟一次的refresh,则是将内存buffer中的数据写入到一个新的段中,使其搜索查询可见(by id查询可做到实时)。所以,ES被称之为 —— 近实时搜索引擎。
当我们在大批量写入文档的时候,且此时不对索引进行搜索查询,可将refresh_interval设置为-1,禁用内存自动刷新,可以有效提升文档的写入性能。写入结束后,当我们开始使用该索引时,再把参数调回来为默认值1秒。
另:refresh_interval设置为-1的时候,当内存缓冲区满了,会进行refresh操作。\
translog
ES增加了translog(事务日志),在每一次对ES进行写操作时均进行了日志记录,其作用完全等于同MySQL的redo log,实现了ACID中的持久性。
index.translog.durability:默认设置为request,即每次写请求完成之后执行磁盘的fsync操作。
但在很多场景下,我们ES的数据其实是可以接受有损的,比如日志场景,那么每次写请求都进行刷盘,对IO资源造成极大的消耗,是完全得不偿失的。
这种场景下,我们可以将index.translog.durability设置为async,同时,index.translog.sync_interval这个参数,代表进行刷盘的间隔,默认5秒(在index.translog.durability = async时有效),我们也可以酌情进行设置。
执行偏好
查询执行偏好,查询url里添加preference=_local,这样可以优先在本地节点执行操作,最小化网络延时。
其实很多时候,所谓的优化,无非是根据实际的使用场景,在性能和实时性上的一种取舍,在数据持久性和性能上的一种取舍,在读性能和写性能上的一种取舍,以及在业务高低峰期性能上的一种取舍。还是那句话,没有银弹,只有取舍,架构师就是要正确地选择取舍。
因此,我们不但要理解ES的实现原理,更要理解当前的业务场景,才能有针对性地进行优化。以及,把ES和MySQL、Redis结合起来使用,才能整体地满足于业务系统所需。