做为数据库核心成员,如何让淘宝不卡顿?

简介:TDDL(Tabao Distributed Data Layer)是淘宝开源的一个用于访问数据库的中间件,集成了分库分表,主备,读写分离,权重调配,动态数据库配置等功能。本文以2007年TDDL初诞生时的视角,介绍TDDL是如何一步步设计成型的,但愿能帮助同窗们简单收获:常规数据库效率问题解决思路、TDDL框架设计基本思路以及分布式数据库设计思路等。

image.png

时间倒转穿越回2007年年末

一觉醒来,我仍是照常去上班,走到西溪湿地附近,马路没有,高楼没有,有的是小山坡和金色的稻田。一番打听以后,才知道此时没有什么西溪园区。没办法,硬着头皮去滨江上班,一刷卡,才发现我并非我,我如今的身份是淘宝数据库团队的核心成员。html

此时全国上下在迎接着奥运的到来,一片祥和。淘宝网成交额突破400亿,日活用户达1000万。工程师们都很是兴奋,磨刀霍霍。可是也遇到了棘手的问题。mysql

一 分析当前的现状

1.1 现有业务背景

  • 淘宝网给中国市场提供了全新的购物形式,在互联网的大潮下,用户暴增,成交量暴增,公司持续飞速增加。
  • 截止2007年,淘宝网成交额突破400亿,日活用户达1000万。
  • 全天有1000万用户访问淘宝。而绝大多数用户都是在网上逛,什么也不买。

1.2 当前的问题

1.2.1 用户体验与反馈git

用户广泛反馈逛淘宝卡顿,操做延迟特别明显。web

1.2.2 分析核心缘由redis

  • 大量的用户在浏览商品,并不下单。这我的数和场景的比例有20:1。
    • 说明:数据库模式事务,写操做会对表或者行加写锁,阻塞读操做。
  • 业务数据集中在一张表里,如user表。一张表里数据破几千万。查询一条数据须要好几秒(单表数据量太大)。
    • 说明:一张表数据提高,必然会致使检索变慢, 这是必然事实。不论如何加索引或者优化都没法解决的。
  • 全部表集中在一个库里,全部库集中在一个机器里。数据库集中在一台机器上,动不动就说硬盘不够了(单机单库)。
    • 说明:全部业务共用一份物理机器资源。机器存在瓶颈:磁盘和CPU不够用且后期拓展性不佳。

1.2.3 总结问题算法

  • 20:1读写比例场景。
  • 单表单库数据量太大。
  • 小型机与单机场景,抗不住当前规模。

image.png

二 我要作什么?

  • 如何知足当前天天1000万用户逛淘宝的需求,且用户体验好(最基本要求:响应快)。
  • 如何知足将来有上亿用户的访问,甚至是同时访问,且用户体验好(最基本要求:响应快)。

高筑墙,广积粮,积极作好准备。sql

提炼核心:数据库

  • 提升数据库操做速度。
  • 同时能应对将来规模变化。

三 我能作什么?

为实现以上两大目标,我能作什么?缓存

3.1 提升数据库操做速度,通用方法

提炼常见的通用方法:服务器

sql优化

  • 排除语法问题,烂sql
  • 下推优化

下推的目的:提早过滤数据 -> 减小网络传输、并行计算。

  • 提早过滤数据
  • 小表驱动大表等

创建索引

  • 查询频率高的热点字段
  • 区分度高的(DISTINCT column_name)/COUNT(),以主键为榜样(1/COUNT())
  • 长度小
  • 尽可能能覆盖经常使用查询字段
  • 注意索引失效的场景

分库分表

  • 垂直分库分表
  • 水平分库分表

读写分离

缓存的使用

等等。

3.2 如何应对将来的持续变化?

必须支持动态扩容。

必须走分布式化路线,百分百不动摇。

3.3 结合定位,分析本身能作的

3.3.1 分析咱们的架构定位

(1)大前提

  • 咱们要作通用型框架,不参与业务。
  • 从软件设计原则出发,开闭原则:对扩展开放,对修改关闭。

说明:大修改就意味着不稳定,所以:咱们要作到尽量少的修改原来的代码。在程序须要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

(2)当前架构现状

淘宝网主要使用hibernate/ibatis传统框架:

image.png

(3)分析咱们的架构定位

淘宝数据库团队当时使用映射框架(hibernate/ibatis)做为数据库交互入库,为了避免让他们修改代码,那咱们只能在ibatis/hibenate这类映射框架之下。

同时jdbc是与底层数据库交互的Java数据库链接驱动程序,是基础能力,咱们要使用它,而不是改造它。

结论:我得把TDDL安插于ibatis/jdbc之间,因而有了第一张架构图:

image.png

3.4 总结,咱们能作什么?

image.png

结合咱们的目标,通用方法,大前提以及架构定位,分析下咱们能作和不能作的。

不能作的:

  • 索引,由于这个是设计阶段,强业务相关。与大前提冲突:咱们不参与业务。

能作的:

  • 语法优化
    • 排除sql问题
    • 下推优化
  • 分表分库(自动水平分表,水平分库)
    • 读写分离(读写分离/分布式化与动态扩容)

四 咱们如何作?

4.1 语法优化

为达到语法优化的目的,咱们须要具有什么能力?

简单来讲:

  • 咱们须要认识这个别人提交给个人sql。
  • 我能拆解sql。
  • 优化与重组这个sql。

image.png

专业点来讲:语义分析能力。

  • sql解析
  • sql规则制定
  • sql优化
  • sql重组

image.png

所以:咱们须要设计一个sql解析器,sql优化器。

4.1.1 解析器

解析器的核心是词法分析、语法语义分析,也就是说来了一条 select/update/insert/delete语句,你能认识它,并且你知道下一步该怎么处理,同时为后面的优化器打下基础。

核心:将sql解析为一棵语法树。

例:

SELECT id, member_id FROM wp_image WHERE member_id = ‘123’

image.png

sql语法树:

image.png

4.1.2 优化器

核心:

  • 在sql解析成sql语法树后,使用sql优化规则(1. 语法优化 2. 下推优化), 经过对树进行左旋,右旋,删除子树来对语法树进行重构sql语法树。
  • 将重构的语法树进行遍历获得优化后的sql。

(1)语法优化

  • 函数提早计算
a. id = 1 + 1  => id = 2
  • 判断永真/永假式
1 = 1 and id = 1 => id = 1
0 = 1 and id = 1 => 空结果
  • 合并范围
id > 1 or id < 5 => 永真式
id > 1 and id = 3 => id = 3
  • 类型处理
id = ‘1’  => id为数字类型,自动Long.valueof(1)
create=‘2015-02-14 12:12:12’ => create为timestamp类型,解析为时间类型

(2)下推优化

  • Where条件下推
select from (A) o where o.id = 1
=>
select from (A.query(id = 1))

说明:提早条件过滤,提早获取数据,减小后期计算/IO/网络成本。

  • JOIN中非join列的条件下推
A join B on A.id = B.id where A.name = 1 and B.title = 2
=> 
A.query(name = 1) join B.query(title = 2) on A.id = B.id

说明:提早过滤,减轻后期join计算成本,达到“小表驱动”的目的。

  • 等值条件的推导
A join B on A.id = B.id where A.id = 1 => B.id = 1
=> 
A join B.query(B.id=1) on A.id = B.id

说明:同理,提早过滤。

4.1.3 总结

  • sql解析器
    • 负责将sql语句化为sql语法树。
  • sql优化器
    • 负责将sql语法树利用sql优化规则,重构sql语法树。
    • 将sql语法树转化为sql语句。

image.png

4.2 分表分库

单库单表的问题:

几年前,业务简单,应用的数据比较少,表结构也不复杂。只有一个数据库,数据库中的表是一张完整的表。而到了今天,2007年了,业务复杂起来了,数据量爆增,单表数据破千万甚至上亿条,一条DML语句,死慢死慢的。这种状况下加索引已再也不有显著的效果。

这个时候,数据库效率瓶颈不是靠加索引,sql优化能搞定的。

正确出路:分表分库,经过将表拆分,来下降单表数据量,进而提升数据库操做效率。

分表分为:

  • 垂直分表
  • 水平分表

分库分为:

  • 垂直分库
  • 水平分库

因为TDDL不参与业务,而垂直分库分表是强业务相关的,所以TDDL暂不参与垂直分库分表,只在水平分库分表方向上努力。

4.2.1 垂直分表

垂直拆分是将一张表垂直拆成多个表。每每是把经常使用的列独立成一张主表。不经常使用的列以及特别长的列拆分红另外一张拓展表。
image.png

简单垂直分表举例
核心要素:

  • 冷热分离,把经常使用的列放在一个表,不经常使用的放在一个表。
  • 大字段列独立存放,如描述信息。
  • 关联关系的列紧密的放在一块儿。

它带来的提高是:

  • 为了不IO争抢并减小锁表的概率,查看详情的用户与商品信息浏览互不影响。
  • 充分发挥热门数据的操做效率,商品信息的操做的高效率不会被商品描述的低效率所拖累。

4.2.2 水平分表

水平分表是在同一个数据库内,把同一个表的数据按必定规则拆到多个表中。

image.png

简单点的技巧:按照枚举类型区分。

做用总结:

  • 库内的水平分表,解决了单一表数据量过大的问题,分出来的小表中只包含一部分数据,从而使得单个表的数据量变小,提升检索性能。
  • 避免IO争抢并减小锁表的概率。

4.2.3 垂直分库

垂直分库是指按照业务将表进行分类,分布到不一样的数据库上面,每一个库能够放在不一样的服务器上,它的核心理念是专库专用。
image.png

做用总结:

  • 解决业务层面的耦合,业务清晰。
  • 高并发场景下,垂直分库必定程度的提高IO、数据库链接数、下降单机硬件资源的瓶颈。
  • 能对不一样业务的数据进行分级管理、维护、监控、扩展等。
  • 垂直分库经过将表按业务分类,而后分布在不一样数据库,而且能够将这些数据库部署在不一样服务器上,从而达到多个服务器共同分摊压力的效果,可是依然没有解决单表数据量过大的问题。

4.2.4 水平分库(TDDL 核心)

水平分库是把同一个表的数据按必定规则拆到不一样的数据库中,每一个库能够放在不一样的服务器上。

image.png

做用总结:

  • 解决了单库单表数据量过大的问题,理论上解决了高并发的性能瓶颈。

水平分库核心要解决的问题:

  • 如何知道数据在哪一个库里?- 路由问题
  • 结果合并
  • 全局惟一主键ID
  • 分布式事务(暂时不支持)

4.2.5 水平分库——问题解决

(1)自动路由算法

sql转发:在水平拆分后,数据被分散到多张表里。原来的一个sql须要拆解,进行转发路由。

例:

select * from tb1 where member_id in ('test1234', 'pavaretti17', 'abcd');
=>
select * from tb1 where member_id in ('test1234', 'pavaretti17', 'abcd');
select * from tb1 where member_id in ('abcd');

image.png

其中拆分和寻找的算法:怎么知道对应哪一个表?即自动路由算法。常见的有:固定哈希算法和一致性哈希算法。

a)固定哈希算法

image.png

b)一致性哈希算法

一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。

一致性哈希算法的优点:

  • 极好的应对了服务器宕机的场景。
  • 很好的支持后期服务器扩容。
  • 在引入虚拟节点后:能很好的平衡各节点的数据分布。

因为一致性哈希算法的优点,此算法几乎是全部分布式场景下使用的方案,包括mysql的分布式、redis的分布式等。

image.png

(2) 结果合并

image.png

升华:引入fork-Join,提高操做速度(多线程并发重点场景,代码中也很经常使用哦)。

  • 任务拆分
  • 多路并行操做
  • 结果合并

image.png

(3)全局惟一主键

算法:基于数据库更新+内存分配。在数据库中维护一个ID,获取下一个ID时,会对数据库进行ID=ID+100 WHERE ID=XX,拿到100个ID后,在内存中进行分配。

  • 优点:简单高效。
  • 缺点:没法保证自增顺序。

例:

水平分库分表:一拆三场景。
主键分隔值:1000。
  • 表1新增一条数据,因而给表1分配1000个主键ID, 直到它用完。
  • 同理,表二、表3在新增数据时,也给它们分配1000个主键ID。直到它用完。
  • 当它们的1000个主键ID用完后,继续给它们分配1000个便可。
  • 重复下去,可保证各库表上的主键不重叠,惟一。

image.png

这种产生全局惟一id的方式至关有效,保证基本的全局惟一特性和高性能的同时,能够对生成id的数据库分机架分机房部署达到容灾的目的。

4.2.6 分表分库总结

image.png

架构师角度:

  • 优先考虑缓存下降对数据库的读操做。
  • 再考虑读写分离,下降数据库写操做。
  • 最后开始数据拆分,切分模式:首先垂直(纵向)拆分、再次水平拆分。
    • 首先考虑按照业务垂直拆分。
    • 再考虑水平拆分:先分库(设置数据路由规则,把数据分配到不一样的库中)。
  • 最后再考虑分表,单表拆分到数据1000万之内。

我的开发角度:

  • 优先使用分表分库框架(直接使用)。
  • 优先考虑缓存下降对数据库的读操做。
  • 本身垂直分表。
  • 本身水平分表。

之因此先垂直拆分才水平拆分,是由于垂直拆分后数据业务清晰并且单一,更加方便指定水平的标准。

4.3 分布式化

分布式化是大潮,是大规模服务器最后都要走的一步。
image.png

4.3.1 读写分离

设计读写分离的数据库,有两大意义:

  • 主从只负责各自的写和读,极大程度的缓解X锁和S锁的竞争。
  • 从库可配置myisam引擎,提高查询性能以及节约系统开销。

说明:myisam查询效率高于默认的innodb效率。参考:myisam和innodb的区别。

image.png

核心问题:

  • 数据的备份同步问题:参考4.4.3。
  • 读写比例支持动态设置:结合业务,如淘宝可设置为20:1。

4.3.2 容灾

主备倒换:提升可靠性 > 应对个别数据库宕机场景,尤为主库宕机。
image.png

说明:DB2主库宕机后,自动将主库转为DB3。

核心问题:

  • 数据的备份同步问题:binlog 参考4.4.3。
  • 检测数据库的在线状态:心跳机制。

4.3.3 数据备份与同步

当只有单机或者一份数据时,一但数据库出问题,那么总体服务将不可用,并且更严重的是会形成数据损害丢失不可逆。

在读写分离与主备倒换的场景下,核心要解决的是多个数据库的数据同步与备份问题。

当前主流的是采用日志备份方式(redis也相似)。

实现原理:binlog日志备份。

image.png

说明:

  • 主库负责写操做,在数据变动时,会写入binlog,同时通知各从库。
  • 从库收到通知后,IO线程会主动过来读取主库的binlog,并写入本身的log。
  • 写完从库log后,通知sql线程,sql线程读取本身的日志,写入从库。

4.3.4 动态扩容

动态扩容的意义在于:随着后期业务量增大,数据库个数能够经过增多的方式来应对,而相对的改造代价很小。

扩容前:

image.png

扩容后:
image.png

核心内容:

  • 在添加新库时
    • 注册机器与库
    • 路由算法调整:固定哈希算法-调整模数/一致性哈希算法自然支持扩容
  • 可选的权重调整
    • 修改权重,数据插入偏向于新库5。
    • 在各库数量平衡时,触发修改回原来平衡的权重,以保证后续的均衡分配。

五 架构成型

sql流向

下图介绍sql从流入TDD到流入数据库,期间TDDL各模块对Sql的处理。

image.png

架构图

image.png

下图介绍了TDDL三层的位置以及做用。
image.png

核心能力图

TDDL 核心能力,核心组建示意图,其中标出了各模块核心要解决功能,核心算法等。

image.png

参考

TDDL 官方文档
http://mw.alibaba-inc.com/products/tddl/_book/
TDD产品原理介绍
http://gitlab.alibaba-inc.com/middleware/tddl5-wiki/raw/master/docs/Tddl_Intro.ppt
TDDL(07-10年)初始版本介绍
https://wenku.baidu.com/view/9cb630ab7f1922791788e825.html
阿里云SQL调优指南
https://help.aliyun.com/document_detail/144293.html
一致性哈希算法原理
http://www.javashuo.com/article/p-tzgogqcu-cr.html
TDDL初期源码(码云)
https://gitee.com/justwe9891/TDDL
MyISAM与InnoDB 的区别(9个不一样点)
http://www.javashuo.com/article/p-ggggexrz-dx.html

原文连接:https://developer.aliyun.com/article/773227?

本文内容由阿里云实名注册用户自发贡献,版权归原做者全部,阿里云开发者社区不拥有其著做权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。若是您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将马上删除涉嫌侵权内容。