摘要:若是企业计划构建高性能的SaaS应用,仅凭云服务基础设施是不够的。如何基于云服务平台设计并实施符合自身业务特色的系统架构,是决定产品性能的关键。本文将讲述咱们如何利用云服务,解决海量用户的数据库使用问题。算法
杭州湖畔网络技术有限公司是一家专业提供SaaS化电商ERP服务的创业公司,主要用户群体为经营淘宝、天猫、京东等主流电商平台、自建商城、线下渠道的商家及中小企业。做为SaaS服务提供商,服务数万乃至数十万级用户是业务架构初期就必须考虑的问题。庞大的用户群以及海量的用户数据意味着基础设施的构建必须兼顾高效与稳定,而按照通用的基础设施建设方案的话,须要面对成本太高、实现复杂、须要投入太多精力等问题,这对当时的湖畔网络这样的初创公司来讲,彻底不能承受。所以,更经济、更方便扩展的云服务平台成为首选。在对比现有各家云服务后,咱们选择了稳定性与成熟度都通过大量用户检验的阿里云。数据库
但要构建高性能的SaaS应用,仅凭云服务基础设施是不够的。如何基于云服务平台设计并实施符合自身业务特色的系统架构,也是决定产品性能的关键。本文将讲述咱们如何利用云服务,使用相对经济的方案,解决海量用户的数据库使用问题。后端
架构缓存
咱们的SaaS化电商ERP服务的总体架构是基于阿里云服务平台实施的,如图1所示。安全
图1 系统架构精简示意图性能优化
经过该方案,不只发挥了阿里云的优点(不涉及物理机器的维护和折损,灵活地配置升级,成熟的备份与快照方案),并且经过集群,避免了系统可能会遇到的单点故障,提升了系统弹性扩容的灵活性和可用性。服务器
做为一个SaaS化、数据更集中、数据体量庞大的企业应用,数据库是咱们总体架构中的关键节点,如何保证其稳定与性能,是本文讲述的重点。网络
当用户进入快速增加期后,随着业务量迅速增长,核心业务表的存量数据和增加速度绝对不是单个DB所能承受的(几乎全部单DB配置都存在性能物理上限瓶颈,即便选择升级配置也会受到成本和资源上限的约束)。所以,咱们一开始就将数据库分库分片(Sharding)做为一个可行方案优先考虑,主要分析以下。数据结构
考虑到业务特性,咱们最终采用了行业比较通用的水平拆分+垂直拆分策略,并自主完成DAO与JDBC之间的数据访问封装层开发工做。架构
水平拆分:按用户将数据拆分到多个库的相同表中
水平拆分的思路,就是将本来存放在单个RDS数据库中的数据,根据业务ID不一样,拆分到多个数据库中(参见图2)。拆分后,各库的表数量及表结构都保持一致。水平拆分首先须要确立惟一的业务主表,即其余全部表的数据都与主表ID(前文所说的业务ID)存在直接或间接的主从关系,能够经过主表ID对所有数据作很好的切分。咱们选择的业务主表为用户表,其余业务表或表的父表都包含一个用户ID。所以,咱们切分的目标就是将不一样用户数据存放到不一样的数据库中。
图2 水平拆分示意图
肯定了拆分规则后,下一步是着手封装Sping数据访问封装层(DBWrapper)。DBWrapper介于DAO与JDBC之间,每一个业务DAO进行数据库基本操做,都会通过DBWrapper。它的主要做用是将数据库架构的变化对业务层透明,业务层能够如同操做单个DB同样,调用DBWrapper提供的数据库操做接口,而判断操做哪一个数据库的逻辑,则所有交由DBWrapper封装完成(参见图3)。
图3 水平拆分架构示意图
DBWrapper主要提供新用户初始化和数据库操做接口。在新增用户初始化到系统时,需先动态判断系统各库的负载分布状况。粗略一点的算法就是判断各库的用户数,如共有4个库,能够根据user_id%4的状况决定目标库;再精细一点能够挖掘下核心业务数据的分布状况,具体分配算法须要基于业务设定(如考虑不一样用户的平均订单量)。经过各库压力综合计算后,分析出压力最小的目标数据库,并将该新增用户数据存放到指定的目标库,同时更新路由信息(Router)。
当用户完成初始化进行业务操做时,则需由业务层调用DBWrapper的操做接口。DBWrapper接收到请求后,会根据业务层传入的User_id匹配Router,判断最终须要操做的RDS实例和数据库。判断完成后,只须要循序渐进地开链接执行就能够了。具体的代码实现,须要结合自身的持久层框架,找一名研究过持久层框架实现的开发人员便可完成。
这样就将系统用户总体数据压力,相对均匀地分布到多个RDS实例与数据库上。事实证实,这确实是一个很是有效的方案,尤为是对于数据量大、增加迅猛的表。只是在后续实施过程当中,咱们发现有时会有单个用户的业务压力比较突出,针对这种状况,咱们能够经过一些人工干预(如迁移数据到单独的库)进行微调,固然最终的解决方案仍是要不断调优路由算法。
切分后,不可避免地须要考虑数据字典(DD)和数据路由(Router)的处理。暂时咱们采用的方法是将全部数据字典与路由放入独立的库,这也是后文中垂直拆分的一种应用。须要说明的是,数据库仅是这两个业务的一种实现方式,通常还能够经过或结合分布式缓存来处理这些业务(咱们选用了OCS)。而对于可能出现的单点障碍,预留的扩展方案为水平拆分或建立只读节点(只读节点可使用RDS最新提供的只读实例,目前还在内测阶段)。
垂直拆分:按业务将表分组拆分到多个库中
与水平拆分相比,垂直拆分要更简单一些。其基本思路就是将存放在单个数据库的表分组,把其中业务耦合度较高、联系紧密的表分为一组,拆分到其余DB中(参见图4)。拆分后,各库的表结构及其业务意义将彻底不一样。虽然规则简单、实施方便,但垂直拆分老是需打断些关联,由于实际操做中,基础资源经常出如今各个业务场景,在切分时又不得不切分到两个库中,此时就须要业务层屡次查询后,在内存处理数据,实现数据库Join的效果。
图4 垂直拆分示意图
垂直拆分一样须要DBWrapper,但封装规则与水平拆分略有不一样,须要针对不一样的业务,创建不一样的DBWrapper。此时再也不是彻底业务层无感知,须要业务层根据业务场景有针对性使用。单个DBWrapper的实现与水平拆分一致。
垂直拆分的好处在于,将总体业务数据切分红相对独立的几块,隔离了不一样业务之间的性能影响。而因为拆分后的数据库业务比较集中,也更容易找到业务主表,更有利于水平拆分。
对于垂直拆分,目前咱们主要用于解决数据路由(包含了用户的基本信息)、数据字典模块,以及常见的冷数据问题。冷数据的处理一直是行业的常见问题(其实对于冷数据的划分,也是水平拆分),目前咱们采用的方案是集中存储,即按本身的冷数据切分方式,经过自行开发的迁移程序将断定的冷数据增量迁移到一个库中。这个方案既可以分离冷数据对热点数据的操做影响,也能够为大数据的挖掘提供比较便利的条件。使用相对独立的冷数据存储结构,能方便之后采用更高效、成本更低廉的存储介质。固然该方案存在一些潜在问题,若是冷数据库满了该怎么办?目前咱们预留的设计方案是,历史库的水平拆分,也能够考虑其余存储形式。
水平拆分与垂直拆分组合使用
拆分一直是数据库优化的关键词(不管是库表结构仍是SQL写法),它是每一个高并发产品最终都要经历的一步。拆分方案的核心主要在于能够经过添加更多RDS实例和数据库(经常为了节约成本,多个数据库能够部署在一个RDS实例上),灵活扩容系统的负载能力。在数据库架构中,水平拆分和垂直拆分通常都是搭配使用的,二者的前后顺序视具体状况而定。通常而言,垂直拆分更容易,也能够为水平拆分作铺垫,一是业务集中,便于提取主表,二是垂直拆分后,能够只水平拆分压力高的表,而业务增加缓慢的表则能够保留单DB,从而提升拆分效率以及下降实施成本(参见图5)。
图5 数据库Sharding方案示意图
咱们之因此优先水平拆分,主要缘由仍是成本和效率及当时的一些局限性。只按业务ID(用户)作好路由配置,这样各个库中的结构彻底一致,保留了本来的业务逻辑与实现,避免了跨库关联,能大大节省实现成本。
尽管拆分有种种好处,但因为分布式事务及跨库Join的实现复杂度较高及可用性较差,因此分布式事务通常都经过业务层使用乐观锁控制。而跨库的表间关联必定要打断,不然性能和实现复杂度都会超出可接受范围。对于跨库的Join、Group by等问题,都须要在业务层处理。目前咱们采用的是分批查询,在业务层组装结果的方式。
有些遗憾的是,因为咱们早期使用RDS时,阿里云还没有推出DRDS(分布式数据库)产品,因此上述拆分的数据库底层架构均是由咱们自行研发的,投入了大量的精力。而如今有了DRDS,正准备作拆分的团队,则无需再本身造轮子,直接拿来用便可,这样团队能够将更多的精力放在业务上。
小处大有可为
虽然咱们在架构上作了优化,但在产品发展过程当中仍是会出现性能不太理想的状况。在阿里云支持中心和论坛上,也能够看到其余业务型团队反馈使用RDS时遇到相似的状况。最初你们都怀疑是否是RDS的底层资源隔离有问题,多个用户共享资源时发生争抢,致使RDS的性能问题。但在阿里云DBA的指导和协助下,发现是因为产品设计时对数据库的使用太“不拘小节”,而随着并发压力与数据量增长,大量细小的性能问题被放大,集中暴露出来。
解决灯下黑:修正业务层的数据库操做陋习
在数据库的优化过程当中,研发团队最容易忽视的每每是业务层中的数据库使用。一些优化方案能够做为开发的常态化准则。下面仅列举几个经常使用的优化方案。
挤掉海绵里的水:优化数据库执行计划
因为执行计划的优化每每涉及到数据库的运行机制与底层设计,此处实难三言两语说清。因此下面仅列举几个咱们受益颇深的优化方案。建议你们优化执行计划时,多关注、分析iDB Cloud控制台中的性能报告和建议,也尽可能多向阿里云DBA们请教,通常能够经过提工单的方式。有条件或兴趣的话,DBA能够经过预定到阿里云现场学习。另外,执行计划的优化须要大量的调试工做,经过在阿里云控制台建立生产数据库的临时实例,能够准确模拟当前系统的数据结构、分布与压力。
字段类型选择
选择合理的字段,每每能够大大减小数据库行数据的大小,并提升索引匹配的效率,进而大大提高数据库性能。使用更小的数据类型,如日期采用date代替datetime、类型或标记使用tinyint代替smallint和int、使用定长字段代替非定长字段(如char代替varchar),都能或多或少减小数据行大小,提升数据库缓冲池的命中率。而做为表字段中特殊的一员—主键,其选型更会对表索引的稳定和效率带来很大的影响,通常建议考虑数据库自增或自主维护的惟一数值。
高分离度字段创建索引
对于查询来说,高分离度字段每每意味着精准或部分精准的条件。相对来说是最好优化的一种场景,只须要对分离度较高的字段单独创建索引便可。固然实际使用中会有更多细节须要摸索。精确条件在各业务中基本都会用到,在越复杂的业务场景中,精确条件优先的原则,将是最有效的优化方案。须要注意的是,尽管高分离度字段单独创建索引效率很高,但过多的索引会影响表写入的效率,因此须要谨慎添加。这一点iDB Cloud中有大表索引的建议能够参考。
覆盖索引(Covering Index)
通俗一点理解,就是执行计划能够经过索引完成数据查找和结果集获取,而无需回表(去缓冲池或磁盘查找数据)。而因为MySQL的索引机制限制,一次查询时,将只用到一个索引或将两个索引聚合(index_merge)起来使用,因此意味着复杂的业务场景中,单独对每一个字段创建索引可能没有什么用处。因此对于一些特定的查询场景,创建合适的组合索引,应用覆盖索引方法能够避免大量随机I/O,是更为推荐的优化方案(若是执行计划Explain的Extra中有Using Index,就说明使用了覆盖索引)。但实际业务老是会比索引自己更复杂,业务中须要查找或者获取的字段信息每每是不少的,而组合索引并不能涵盖全部的字段(不然咱们将拥有一个比数据还要庞大的索引)。此时,为了应用覆盖索引,就须要使用主键延迟关联(Deferred Join),即先经过组合索引中包含的字段条件,初步查询出相对较小的结果集(面向结果集原则),该结果集只包含主键字段;而后经过获取到的这个主键队列,再对数据表作关联。
适当妥协
见招拆招:升配置
通常业务型的研发团队,很难有额外的精力投入到数据库方面,也没有专业的DBA来不断调优数据库配置、优化数据库服务器性能。因此早期团队能够选择的方案很少,也很难在技术上深挖下去,只能用成本换时间:性能配置不够,那就升级服务器配置。
那么问题来了:本身部署的数据库要升级配置,除了调整数据库配置参数,还会受到物理机的限制,所以就要考虑更加复杂的数据库备份和同步策略。但这是业务团队所不能接受,甚至短时间内没法实现的,升配置也就变成了一个复杂的问题。不过咱们使用了RDS,其弹性升级策略,正是这个问题的最佳解决方案。
二八原则
在长期的数据库乃至整个产品的优化过程当中,我感觉最深入的就是:完美的方案可遇不可求。选择方案时,若是能解决80%的问题,并规避或保留剩下的20%,则将大大提高团队的总体效率。产品与架构都是在不断优化演变的,咱们要按部就班、不断努力,将今天的终点留做明天的起点。
总结与展望
做为一位创业公司的技术开发人员,经过实际使用阿里云产品,我总结了几点关于使用云计算产品的优点。
1. 便利的服务器弹性升级功能,可随时应付像“双十一”这样的大促。而经过使用传统IDC托管模式,物理机的维护、升级以及升级后的数据迁移都是比较头疼的。
2. 成熟可靠的数据备份与快照、数据库主从分离与同步的底层方案。创业团队无须承受本身造轮子的代价,可专一于业务开发。
3. 云计算产品通过检验、值得信赖的安全防御。
4. 精简了创业团队人员规模。云计算平台具有专业的技术支持与服务,使得创业团队再也不须要数据库和服务器管理员。
除了使用云产品的心得,数据库调优实践是本文的重点。在数据库的架构设计与性能优化方面,我秉承的原则是解决主要问题,按先分而击之、再挖掘细节的步骤,周返往复不断进行,同时系统架构也在这个过程当中不断演变。相信随着时间推移,会有更多优秀的方案出现。尤为随着云服务不断发展,业务研发团队投入到基础设施的精力与成本,将会无限减小。会有愈来愈多专一于业务研发的团队,推出更多优秀的互联网产品,用互联网服务推进企业创新,重塑中小企业信息化形态。