单机数据库
分布式数据库
TDDL原理与最佳实践
1. 数据库的结构
1.1. KV存储(id是K)
1.2. B+树与红黑树
B+树的特点是叶子节点是块状,一个叶子里面有多个数据,相邻数据是存在一起的,123,456起等,
而磁盘也是按块的,B+树的数据是按块存储的正好和磁盘的块的概念是相符的,
所以在数据库里面大多采用了B+树或者类似的一种结构来存储数据。
在java中实现treemap时选择是红黑树而不是B+树,B+面向的是磁盘的结构,
java的treemap面向的是内存,随机读写数据快;
另外一个原因是,B+树为了减少分裂的次数会在每个叶子结点中预留几个空洞来存入未来的数据,
这个特点在磁盘中是可以的,因为磁盘都是白菜价,浪费一点没有关系,但内存就不一样了;
红黑树本质是一颗二叉树,每个叶子只有一条数据,不需要预留任何的空间,
在内存中就不会造成空间的浪费,也就是这两点,才选择了红黑树来实现java中的treemap.
任何一种技术脱离了它的场景都是没有意义的;
有些数据库不是按行存的,是按列存的,这样可对列的特点进行压缩,减少存储空间,
进行max/min等运算也快,但这种特点不适合更新;
没有一种数据库读的快写的也快,读写性能的差距也是由于背后所使用的存储不同导致的,
如hbase使用的是lsm-tree这种存储,它的特点是写的很快,顺序写,但读的稍微有点慢,
所以hbase经常用来存日志,产生的量,对读的性能要求不高。
要判断读的多还是写的多,如果读的多,还要判断读的特点,是随机读还是范围的读,写的话,
是只写还是会有删除和更新;B树是面向磁盘的,红黑树是面向内存的;
2. 索引查询优化器
主表和索引表都是映射,查询如果使用到了索引,就会分为两步,第一步先查索引,第二步通过索引再查主表,
这一步叫回表。如果查询中的列(seletc a,b)都是索引中的列,那么就只有一步,只要查索引表就可以了,
就不需要回表了,这叫索引覆盖。 如果索引包含所有的列,也就是主键对记录的索引,就是聚簇索引。
如果索引包含部分的列,就是非聚簇索引, 非聚簇索引在查询的时候就可能需要做一个回表的操作才能查出所有的列来。
索引太多时有两个缺点, 占空间,对记录进行写操作时也要修改索引,写入性能的下降。
没有索引时就会全表扫描。
主表的每一条记录都是一个KV映射,K就是主键,V就是整条记录,而索引就是列对K的索引而已。
区分度比较高就比较适合做索引列。
从某种程度上说,索引也是一种关系型数据库的表。
关系型的数据库都有索引的概念,而NoSQL是没有索引的概念的,比如hbase就没有索引,只能按照rowkey来查,
不能按照其他字段来查。
sql语句会经过解析器,经过解析变成一个数据结构, 查询优化器会分析这个数据结构是什么意思,怎样执行会比较快,
会生成一个执行计划,这个计划就包括查字典的整个过程。
SQL->Optimizer->plan,底下的存储按照plan就会得到高效而且准确的结果。
那么查询优化器会做哪些事情呢?
1.索引选择
当where有两个或者多个列都有索引时,就会涉及到索引选择的问题。一般会根据区分度选择合适的索引。
2.下推
select t1.name,t2.name from t1 join t2 on t1.id = t2.id
就是说会让一些操作提前执行来减少中间过程的数据,也就是优化的过程。
3.join策略的选择(自己了解)
index netxt roup、block next roup,
mysql语句前加上extern就会展示这条语句的执行计划,能大概读懂
事务也是区分关系型数据库与nosql的指标
3. 分布式数据库
产生分布式数据库的原因,最重要的有两个:
1.单机的硬盘容量(可能还包括内存容量、cpu容量、网络容量)不够,需要存储到多块硬盘上
2.安全性原因,数据库存到多个地方备份
RAID磁盘阵列的出现与分布式数据库的出现相似,一个是空间,一个是安全,
RAID0的概念是把数据拆开存储在两块硬盘上,比如C盘数据存储在A上,D盘存储在B上,RAID1的概念是把数据复制存储在两个地方,比如CD盘在A盘上存一份,也在B盘上存一份。
RAID01或者RAID10就是把两者结合起来,数据既分块,又对每块数据进行备份。
对应到分布式数据库里面,就有了这样的概念:
RAID0对应分布式数据库里的Partition(扩展性,一个分区不够,继续扩)
RAID1对应分布式数据库里的Replication(安全性,一个挂了,不会掉数据)
如何将数据复制到不同的地方去?
1.异步?
2.同步?
假设有两块磁盘0和1(或者就是两个数据库),那如何保证两个数据是完全一样的?
一种方式是同时向两个地方写,写完就是成功了;
另一种方式是只写0,让0去写1,这就产生了如何返回数据写成功了?同步是0写完1才返回成功,
异步是写完0就返回成功(如果此时就去读1就会出现还没有写入的数据,就会出现延迟,好处是不用等所有的库都写完,它的写入的延迟会低一些),这是两个点的情况,如果有更多的点(就是把一个点的数据复制了好几份),则就不能接受只有同步或者只有异步(只写一个点就返回)的情况了,就要把同步和异步结合起来,即每次同步写数个点后返回成功,剩下异步写剩下的点。
那在分布式数据库中如何才能保证读到的数据是一致或者最新的呢?
W+R>N,W代表同步写的个数,N表示总结点个数,即写的少,读的就越多,写的多,读的就越少。
mysql是有主库和备库的区别的,主备库数据是完全一样的。内部的方案是读写都在主库,备库全部采用异步的方式备份。这是牺牲了备库的读性能来换取的,可以讲到同步的数据又不要同步的复制,来保证写的性能的提升。
如果主库挂掉的话,怎么从剩下的备库中选举主库呢?大多是用PAXOS算法或者衍生的算法来选举主库,主要原理是获取多数支持的那一个才能是主库。这样就是自动的的过程,内部往往简单粗暴的,当主库挂掉的时候人工选择一个备库当主库。
partition和replication在单机数据库中也是存在的,在单机数据库是也有分区表的概念,虽然是同一个机器,也会把表分成了好几个部分, 在单机数据库里面也会用磁盘陈列的方式做一些冗余,在单机数据库里也会这两种概念,那在分布式数据库要把这两点单独的拿出来说呢?最重要的是因为分布式数据库多了网络,延迟变大了,在单机里cpu总线的延迟是纳秒级,走网络的话延迟可能是毫秒级的。正是因为有延迟在在分布式数据库中才会把这两个东西单独拿出来说。
以上主要是replication的讲解;
partition的讲解在tddl里;
4. Taobao Distributed Data Layer
TDDL产生背景:
单一数据库无法满足性能需求(数据切分 读写分离(只写主库只读备库))
容灾(主备切换)
配置变更(动态数据源(不需要数据库的账号信息,哪天数据库ip地址换了也没关系 只需要依赖appname tddl会自动地读取数据库账号用户名密码 当这些发生变更的时候会自动的通知到tddl,对用户无感知))
切分有两种水平切分和垂直切分
垂直切分不能无限切分, 因为列是有限的,而水平切分是可以无限扩展的, 只要行数是无限的。tddl能做的就是水平拆分,因为垂直拆分就是sql中的join操作
主备切换也是tddl的功能,当主库挂掉的时候,会自动切换到备库上。
水平拆分不一定按照id字段来拆,也可以选择其他字段,比如某个字段全是字符串,可以先求出它的hash值,再对1024取模(假如拆了1024张表,此时也可以再对表进行分库,如0-155属于库1),总之要按照字段本身的特点。
这就是所谓的拆分算法。
分布式数据库都会有两个特点一个是partition,一个是replication.
那么hbase使用了一种B树的拆分方式,也就是说hbase各个存数据的节点(也就是分表的表)可以看作B树里面的一个节点,它在路由的时候就会根据树的算法来算出这条数据应该在哪一个节点里面,因为它使用了树,所以它继承了树的一些特点,所以在hbase中可以很方便来做一些范围的查询, 比如rowkey大于一个值或者小于一个值。但tddl默认使用的是一种hash算法,就很难做一个范围的查询, 比如1-1000在hbase中可能也就跨了几个分区布局,但是如果在tddl中可能就跨了所有的分区,因为hash算法是一种零散的算法。但为什么内部会默认使用hash算法来分表呢,因为内部经常做的操作是根据买家id\卖家id\羊肉串id\定单id来查,这样的话用hash算法就比较快。
拆分字段的选择跟选索引差不多,选拆分字段的时候基本会选查询都会带上的那种列,比如说买家id卖家id,还有就是区分度的概念,用这个字段会路由到比较少的表中,而不是会路由1000张表。但如果像定单这种东西,它既有买家id又有卖家id,同时买家和卖家都会去关注,那选个字段作为拆分字段呢? 答案是两个都选,数据复制一份,一份按买家id来路由分表,另一份按卖家id来进行路由分表,也就是数据会存两份,相对应的我们就可以把买家和卖家理解为两种索引,因为是两种映射嘛。并且这种索引也可以做成聚簇,也可以做成非聚簇的,比如说在冗余的时候按所有字段进行冗余一遍,这样在查的时候就会查的比较快。
规则路由—扩容
怎样扩容才能减少数据的移动,比如以前是模32,现在变成模40了,就要把以前的结果按40重新模一遍,怎样选择才能减少数据移动呢?
一般做法是一倍一倍的扩,比如以前是32,扩容后就变成64了。因为这种情况下只有一半的数据是需要移动的。
多维拆分实际是把数据按照两维度冗余了一份
组合索引就是先按一个列索引,再按另一个列索引
多个字段同时作为拆分字段,实际上数据只存了一份
两个字段就是二维,只提供一个字段就会定位到好几个张分表
三个字段就是三维
tddl三层数据源
最底层是atom层,对应的是一个数据库,对应的就是一个ip用户名密码,一主一备就构成了第二层,就会变成一个group,同一个group会包含多个atom,但atom数据是一样的,也就是说group会完成读写分离这样一个操作,也可以完成主备切换,这两层就是绝大数应用使用的模型,因为绝大多数应用是小应用不需要做拆分,只要做读分离和主备切换这样的功能。多个group之后会组成一个matrix层,当我们分库之后,多个库会组成一个matrix,matrix层负责来做切分这样一个操作,就会有规则计算这样一个功能,就会算出sql应该到哪个group去。这就是tddl三层数据源的一个概念。
TDDL5分为三层数据源结构
Matrix,主要负责数据的水平切分
Group,主要负责主备与读写分离
Atom,主要负责物理数据源的管理
TDDL5中一个查询操作在三层数据源中的流程如下图:
使用者通过JDBC,将SQL传递到TDDL5中,会按照以下流程进行执行:
SQL Parser会将SQL解析并生成关系查询树;
Query Optimizer对关系查询树进行优化,生成由KV查询组成的执行计划;
在这个过程中,还将根据切分规则,对条件id=2与id=3进行计算,得出数据所在的节点。如在此例中,根据id % 3,得出id=2的数据在Group1上,id=3的数据在Group0上;
Client会将执行计划发送给Matrix层,Matrix层会按照执行计划中指定的Group,将执行计划发送给对应的Group,在Group层,由于非强一致的读请求,会将读操作随机分配到数据节点Atom上;
最终数据在Matrix上进行合并之后,结果返回给Client。
matrix层:
Parser
Optimizer
语法解析
规则计算
查询优化
Executor
聚合/Join/函数计算
Repo(存储层)
Mysql
Group:主备切换,读写分离
Atom:动态数据源
Oceanbase/Hbase/Skiplist/Search(支持多存储)
TDDL的查询优化器有一个最为核心的理念:offload,也是单机优化器里面讲到的一个基本的概念主是下推,下推的意思就是说尽量多的操作会让mysql自己去执行,比如说一些条件的过滤,一些join能让mysql去做的话就让它先来做,比如说索引选择,列的过滤,一些聚合函数的计算,一些排序,一些distinct去重这样的操作,能让mysql来做就让它来做,为什么呢?和单机数据库一样的道理,尽量减少中间数据,减少跨网络的延迟,offload的思想也是贯穿tddl基本的理念。也是tddl最佳实践最基本的思想。