作者简介
刘诚,携程酒店研发部技术专家。2014年加入携程,先后负责了订单处理多个项目的开发工作,擅长解决各种生产性能问题。
随着订单量的日益增长,单个数据库的读写能力开始捉襟见肘。这种情况下,对数据库进行分片变得顺理成章。分片之后的写,只要根据分片的维度进行取模即可。可是多维度的查询应该如何处理呢?
一片一片的查询,然后在内存里面聚合是一种方式。可是缺点显而易见,对于那些无数据返回分片的查询,不仅对应用服务器是一种额外的性能消耗,对宝贵的数据库资源也是一种不必要的负担。
至于查询性能,虽然可以通过开线程并发查询进行改善,但是多线程编程以及对数据库返回结果的聚合,增加了编程的复杂性和易错性。可以试想一下分片后的分页查询如何实现,便可有所体会。
所以我们选择对分片后的数据库建立实时索引,把查询收口到一个独立的web service,在保证性能的前提下,提升业务应用查询时的便捷性。那问题就来了,如何建立高效的分片索引呢
索引技术的选型
实时索引的数据会包含常见查询中所用到的列,例如用户ID,用户电话,用户地址等,实时复制分发一份到一个独立的存储介质上。查询时,会先查索引,如果索引中已经包含所需要的列,直接返回数据即可。如果需要额外的数据,可以根据分片维度进行二次查询。因为已经能确定具体的分片,所以查询也会高效。
为什么没有使用数据库索引
数据库索引是一张表的所选列的数据备份。
由于得益于包含了低级别的磁盘块地址或者直接链接到原始数据的行,查询效率非常高效。优点是数据库自带的索引机制是比较稳定可靠且高效的。缺陷是随着查询场景的增多,索引的量会随之上升。
订单自身的属性随着业务的发展已经达到上千,高频率查询的维度可多达几十种,组合之后的变形形态可达上百种。而索引本身并不是没有代价的,每次增删改都会有额外的写操作,同时占用额外的物理存储空间。索引越多,数据库索引维护的成本越大。所以还有其他选择么?
开源搜索引擎的选择
当时闪现在我们脑中的是开源搜索引擎Apache Solr和Elastic Search。
Solr是一个建立在JAVA 类库Lucene之上的开源搜索平台。以一种更友好的方式提供Lucene的搜索能力。已经存在十年之久,是一款非常成熟的产品。提供分布式索引、复制分发、负载均衡查询,自动故障转移和恢复功能。
Elastic Search也是一个建立在Lucene之上的分布式RESTful搜索引擎。通过RESTful接口和Schema Fee JSON文档,提供分布式全文搜索引擎。每个索引可以被分成多个分片,每个分片可以有多个备份。
两者对比各有优劣。在安装和配置方面,得益于产品较新,Elastic Search更轻量级以及易于安装使用。在搜索方面,撇开大家都有的全文搜索功能,Elastic Search在分析性查询中有更好的性能。在分布式方面,Elastic Search支持在一个服务器上存在多个分片,并且随着服务器的增加,自动平衡分片到所有的机器。社区与文档方面,Solr得益于其资历,有更多的积累。
根据Google Trends的统计,Elastic Search比Solr有更广泛的关注度。
最终我们选择了Elastic Search,看中的是它的轻量级、易用和对分布式更好的支持,整个安装包也只有几十兆。
复制分发的实现
为了避免重复造轮子,我们尝试寻找现存组件。由于数据库是SQL Server的,所以没有找到合适的开源组件。SQL Server本身有实时监控增删改的功能,把更新后的数据写到单独的一张表。但是它并不能自动把数据写到Elastic Search,也没有提供相关的API与指定的应用进行通讯,所以我们开始尝试从应用层面去实现复制分发。
为什么没有使用数据访问层复制分发
首先进入我们视线是数据访问层,它可能是一个突破口。每当应用对数据库进行增删改时,实时写一条数据到Elastic Search。但是考虑到以下情况后,我们决定另辟蹊径:
实时扫描数据库
初看这是一种很低效的方案,但是在结合以下实际场景后,它却是一种简单、稳定、高效的方案:
提高Elastic Search写的吞吐量
由于是对数据库的实时复制分发,效率和并发量要求都会较高。以下是我们对Elastic Search的写所采用的一些优化方案:
对于不关心查询结果评分的字段,可以设置为norms:false。
对于不会使用phrase query的字段,设置index_options: freqs。
提高Elastic Search读的性能
为了提高查询的性能,我们做了以下优化:
写:
查:
系统监控的实现
技术中心专门为业务部门开发了一套监控系统。它会周期性的调用所有服务器的Elastic Search CAT API,把性能数据保存在单独的Elastic Search服务器中,同时提供一个网页给应用负责人进行数据的监控。
灾备的实现
Elastic Search本身是分布式的。在创建索引时,我们根据未来几年的数据总量进行了分片,确保单片数据总量在一个健康的范围内。为了在写入速度和灾备之间找到一个平衡点,把备份节点设置为2。所以数据分布在不同的服务器上,如果集群中的一个服务器宕机,另外一个备份服务器会直接进行服务。
同时为了防止一个机房发生断网或者断电等突发情况,而导致整个集群不能正常工作,我们专门在不同地区的另一个机房部署了一套完全一样的Elastic Search集群。日常数据在复制分发的时候,会同时写一份到灾备机房以防不时之需。
整个项目的开发是一个逐步演进的过程,实现过程中也遇到了大量问题。项目上线后,应用服务器的CPU与内存都有大幅下降,同时查询速度与没有分片之前基本持平。在此分享遇到的问题和解决问题的思路,供大家参考。
参考