图12(图片来源:Google Code)
最后,是Redis Pipeline命令的应用。Pipeline可以将多个Redis命令打包成一个请求,一次性发送给Redis服务器,从而减少了网络延迟和服务器负载。Redis Pipeline的主要作用是提高Redis的吞吐量和降低延迟,尤其是在需要执行大量相同Redis命令的情况下,效果更加明显。
以上优化最终给我们带来了一半的Redis容量的节省和5倍左右的性能提升,但同时也增加了大概10%的额外CPU消耗。
3.2 数据库
数据库相对于应用服务,在高并发系统更容易成为系统的瓶颈。它无法做到和应用一样便利的横向扩容,所以数据库的规划工作一定要打提前量。
3.2.1 读写分离
帐号业务特点是读多写少,所以最早遇到的压力是数据库读的压力,而读写分离架构(图13)可以有效降低主库的负载。读写分离方案中由主库承担全部写流量,从库和主库共同承担读流量。从库同时可以配置多个,通过多个从库来分担高并发的查询流量
图13
保留主库的读能力,是因为 “主从同步延迟” 问题存在,对不能接受数据延迟的场景继续查询主库。 读写分离方案的好处是简单,几乎没有代码改造成本,只需要新增数据库的主从关系。缺点也比较多,比如无法解决TPS(写) 高的问题,从库也不能无节制添加,从库数量过多会加重延迟问题。
3.2.2 分表分库
读写分离肯定是解决不了所有的问题,一些场景需要结合分表分库的方案。分表分库的方案分为垂直拆分和水平拆分两种,vivo互联网技术公众号有过分库分表方案的详解,这边不在赘述,有兴趣的可以前往阅读 详谈水平分库分表 。在这边和大家聊聊分表分库动机及一些辅助决策的经验总结。
(1)分表解决什么问题
笼统的回答就是解决大表带来的性能问题。具体影响在哪里?怎么判断是不是要分表?
① 查询效率
大表最直接给人的感受是会影响查询效率,我们以 MySQL-InnoDB为例分析下具体影响。InnoDB存储引擎是以B Tree结构组织索引,以主键索引(聚簇索引)为例,它的特性是叶子节点存放完整数据,非叶子节点存放键值 页地址指针。这边的节点,对应到存储就是数据页的概念。数据页是InnoDB最小存储单元,默认大小为16k。一个聚簇索引的示意图(图14)如下:
图14
聚簇索引树上做数据的查询操作,是从根节点出发,节点内做二分查找来确定树下一层的数据页位子,到达叶子节点后同样通过二分查找来定位数据。从这个查找过程,我们可以看出对查询的影响,主要取决于索引树的高度。多一个层高,会多出一次数据页的load(内存不存在发生)和一次数据页内的二分查找。
想评估数据量对查询的影响,可以通过估算索引树的高度和数据量的关系来达成。前面提到非叶子节点存放键值 页地址指针,页地址指针大小固定是6个字节,那么一个非叶子节点存储量计算公式大概是 pagesize/(index size 6)。叶子节点存储的是具体数据,存储的数量公示可以简化为pagesize/(data size),这样树的高度和数据量的关系如下:
根据公式,我们以自增BIGINT字段做主键,单行数据大小1k,数据页大小为默认16K为例,3层的树结构容纳的数据量大概在两千万样子。这个方式只是辅助你做估算,如果要确定真实值,是可以借助一些工具直接在数据页中获取。
了解了这些后再看分表方案背后的逻辑。水平拆分是主动控制表中的数据量,来达到控制树高度的目的。而表的垂直拆分是增加叶子节点的容量,这样相同高度的树,可以容下更多数据。
② 表结构调整效率
业务变更偶尔会牵扯到表结构调整,例如:新增字段、调整字段大小、增加索引等等。你会发现表的数据量越大,一些ddl 的执行时间会越来越长,有些线上大表增加字段的执行时间可能会花费数天。具体哪些DDL会比较耗时呢?可以参考mysql官网关于online-ddl的操作说明(详情),关注操作是否涉及Rebuilds Table,如果涉及,数据量越大越大越费时。
除了表结构调整、数据查询这些影响外,数据量越大对于失误的容错性越差,这对于稳定性保障工作是个隐患。
基于上面的原因描述,业务中劲量把索引树的高度控制在3层,这时候表数据量级大概在千万级别。如果数据量增长超过这个预期后,就要评估数据表对业务的重要程度、使用场景等,然后适时进行表的拆分。
(2)分库解决什么问题
分库通常理解解决的是资源瓶颈的问题。单个数据库,即使硬件再强大,它也是有连接数、磁盘空间等上限问题。分库后就可以将不同的实例部署在不同的物理机上,突破磁盘、连接数等资源瓶颈,同时能提供更好的性能表现。
分库的处理除了基于资源限制的考虑外,帐号中还会结合可靠性等述求,进行数据库的拆分。这样可以把核心模块和非核心模块隔离,减少之间的相互影响。目前帐号系统的拆后情况示意如下(图15)。