MyException - 我的异常网
当前位置:我的异常网» 数据库 » 分布式数据库——从线性扩张谈分布式JOIN

分布式数据库——从线性扩张谈分布式JOIN

www.MyException.Cn  网友分享于:2013-08-22  浏览:0次
分布式数据库——从线性扩展谈分布式JOIN

原文地址

 

在首届阿里巴巴中间件峰会上,来自阿里巴巴DRDS团队的梦实分享了《分布式数据库——从线性扩展谈分布式JOIN》。他主要从OLTP数据库的线性扩展、水平扩容、IN查询、分布式JOIN四个方面进行了分享。在分享中,他主要通过买家与订单场景、家庭与孩子场景介绍了IN查询,通过同维度的JOIN、广播表的JOIN、Nested Loop Join详细介绍了分布式JOIN的坑与填坑。

 

以下内容根据直播视频整理而成。

 

在数据库的使用过程中,我们难免会问到这样的问题,为什么分库分表?答案是为了达到线性扩展。在本次分享中,我们能够知道分布式数据库中线性扩展的含义,学会判定一个系统与查询能否达到线性扩展的目的,达到使用分布式数据库的目标。

OLTP数据库的线性扩展

数据库主要有两类:OLAP数据库,SQL一般比较复杂,执行时间可能在秒级至分钟级,响应时间越快越好(单SQL占据更多的资源,例如Map Reduce模型),提供尽可能高的并发度;OLTP数据库,SQL一般比较简单,执行时间一般在毫秒级,响应时间在可接受范围(例如10ms)内即可(单SQL一般只有一个线程执行),提供尽可能高系统容量。

对于OLTP,来说,是否机器越多,SQL执行越快?答案是否定的。对于OLTP数据库中的线性扩展,增加机器数,单SQL的响应时间基本不会发生太大变化;增加机器数,能线性增加整个系统的容量(并发度、吞吐量、TPS)。并且,在资源一定的情况下,从单机到分布式并不能带来更高的系统容量。

水平扩容

比如做一个全表的count(*)操作,每个分库上的时间可能是10ms,如果不带拆分键的话则需要到所有的表上去执行一个count(*)操作,如果将这些SQL并行的发下去,则会发现查询也只消耗了10ms的时间,与只在一个分库上执行的时间差不多。那为什么还要带拆分键?

比如我们有两台DB实例,如果带拆分键的话,往分布式数据库里面提交一个查询,到DB这边也是一个查询,那么提交6条查询之后,系统容量是6QPS。我们把提交到分布式数据库中的查询称为逻辑查询,把分布式数据库到底层所执行的查询称为物理查询。对于业务来说,分布式数据库是相对透明的,它关注的容量指的是逻辑QPS。

a616d9a2a11a977246b3133f2e79aade4acc5d16

发现系统容量不够时,需要进行扩容,我们需要加1台DB,成本提升了50%。如图所示,性能确实提高了50%。

e5bc6ac02280bc86091935c1786a434a609428f9

假如我们的SQL没有带拆分键,那么它需要路由到所有的分片上去执行。一条逻辑SQL会生成2条物理SQL。扩容之后,成本提升了50%,一条逻辑SQL会生成3条物理SQL,系统性能也没有变化。所以得出一个结论:系统一定要带拆分键,否则没有可扩展性。

IN查询

买家与订单场景

具体场景是:提供一个买家的所有订单ID,查出这些订单的信息,订单表按照订单ID进行拆分,SELECT * FROM ORDER WHERE ORDER_ID IN (?,?,?,?...),假设单DB容量是1000QPS。

cc1eca17b7f031b854d191116423cc78f4c90c8e

比如,2005年数据分片数为16,平均一个买家的订单数:2,IN查询涉及的分片数:期望值接近2,系统容量:16*1000/2=8000。到了2015年,数据分片数为64,平均一个买家的订单数:200,IN查询涉及的分片数:期望值接近64≈全表扫描,系统容量:64*1000/64=1000。在这种场景下,IN查询不具备线性扩展能力。

家庭与孩子场景

具体场景是:提供一个家庭的所有孩子ID,查出这些孩子的信息,孩子表按照孩子ID进行拆分,SELECT * FROM CHILD WHERE CHILD_ID IN (?,?),假设单DB容量是1000QPS。跟前面的区别在于,订单数会随着时间的推移飞速发展,但是孩子数不会随着时间发生太明显的变化。

cdd95f701788f4b1f71e2f6a87202d692b91a1c4

假设,2005年,数据分片数:16,平均一个家庭的孩子数:1-2,IN查询涉及的分片数:期望值接近2,系统容量:16*1000/2=8000。2015年,数据分片数:64,平均一个家庭的孩子数:1-2,IN查询涉及的分片数:期望值接近2,系统容量:64*1000/2=32000。在这种情况下,成本提升了50%,系统容量也提升了50%,IN查询实现了线性扩展。

线性扩展

对IN查询来说,必须满足下述条件才能做到线性扩展:IN的值的数目远远小于分片数;一般情况下,IN的值的数目在2-3个;IN的值的数目不会随着业务的发展而增长。相反的,只要有一条不满足,那么IN查询就无法做到线性扩展。

分布式JOIN

分布式JOIN有很多种情况,主要分为两大类:可下推的JOIN,JOIN操作由存储完成,DRDS层针对JOIN的结果进行处理,效率会高一些,因为存储和计算在一起;不可以下推的JOIN,存储层只做单表的查询,DRDS完成JOIN的操作。

同维度的JOIN

可下推的JOIN中很重要的是同维度的JOIN,JOIN的两个表是按照拆分键做的JOIN,简单例子如下:

 

SELECT* FROM
user JOIN user_address
ON user.user_id= user_address.user_id

85fd8a5021cce5f04acbc32c59120c549b77608f

user与user_address需要JOIN,并且均以user_id为拆分键。这样的结果是,对于同一个用户,其地址与它在同一个分片内。所以只需要在存储层把JOIN做好就可以了。对于DRDS来说,这个JOIN操作由下面的MySQL或者RDS来完成。DRDS层只需要把JOIN结果返回去就可以了。这种JOIN线性判断的标准是与单表SQL相同。

广播表的JOIN

另外一个比较常用的JOIN是广播表的JOIN。有些表具有以下特点:比较小,总会与其他表进行关联,这时候就不适合将其放在其中一个库上面,那么,这个JOIN就没有办法下推了。广播表是指所有分片中都存在一个完整的副本,一般用于变更比较少,容量比较小,需要频繁与其表发生关联的表。一张拆分表JOIN一张广播表的简单例子如下:

SELECT* FROM
user JOIN user_address
ON user.user_id= user_address.user_id

 

fd9ee6aaf9e1b50cf27ad1fea1a0831c4e0c089f

此时,选择把level表作为广播表复制到每一个分片上去,这样的JOIN也可以做下推,只要做分片内的JOIN就可以了。这样一个查询的线性判断标准与单表SQL相同,由拆分表上的查询决定。

Nested Loop Join

Nested Loop Join是不可下推的分布式JOIN。具体例子如下:

 

SELECT* FROM
order JOIN user
ON order.buyer_id=user.user_id

c5a0f1db429dbe943f1c4c60a6d53b06d6d94b2a

user以user_id为拆分键,order(订单)以order_id为拆分键。JOIN不能由存储来完成,只能由DRDS层完成。具体的算法等价于在左表把需要的数据拿出来然后再去右表做应用查询,即以小表驱动(经过WHERE条件过滤后数据量较少的表为小表)。

对于这种JOIN,线性判断标准直观来说是对两个表的查询均需要能够线性。即对驱动表的判断与单表查询相同;对被驱动表的判断:被驱动表的JOIN列是否是拆分键,被驱动表做的IN查询的数目。

实例分析

主要有两张表,order表拆分键为order_id,user表拆分键为User_id。

SELECT* FROM order JOIN user ON order.buyer_id = user.user_id

结果分析:两张表均是全表扫描。

SELECT* FROM order JOIN user ON order.buyer_id = user.user_id WHERE order.id = 1

结果分析:order为驱动表,user表的JOIN列是拆分键,一个order只有唯一一个user。

SELECT* FROM user JOIN order ON user.user_id = order.buyer_id WHERE user.id = 1

结果分析:user为驱动表,并且带了拆分键上的等值条件,order表的JOIN列不是拆分键,对于order表是全表扫描。

SELECT* FROM user_oders JOIN order ON user_oders.order_id = order.order_id WHERE user_oders.user_id = 1

结果分析:user_orders记录了一个用户有哪些订单,拆分键为user_id,user_oders表为驱动表,并且带了拆分键上的等值条件,order表的JOIN列是拆分键,一个user对应的order会随业务的增长而增长,在order表做的IN查询也会增长。

买卖家问题

具体场景是:一个订单,包含买家id(buyer_id)与卖家id(seller_id),买家希望根据买家来查,而卖家则希望根据卖家来查。那么一张表通过买家来拆还是通过卖家来拆?

d40f7e110f4f10ae6d775bf15a13a1753ac92623

传统的解法是空间换时间,模仿单机数据库中建索引的这种方式,将数据存两份,一份按买家id拆分,一份按照卖家id拆分。这个方案的缺点是占的空间太大,因为把整个表都冗余起来了。

c7f608e149509440f586be838423a4b71deb8c5d

或许有人认为,一种较好的解决方案是只冗余id列,只需要买家id到卖家id的映射关系和卖家id到买家id的映射关系。在JOIN查询时,随着时间发展,一个买家对应的卖家会越来越多,那么这就是一个错误的方案。更好的方案是把关联关系做一个全冗余,使其避免出现这样的JOIN查询,用更多的空间来换取时间、线性扩展的能力。

DRDS

如何快速判断一个正在使用DRDS的系统是否能做到线性扩展?在DRDS监控页面里面有两个监控指标,一个是物理QPS,一个是逻辑QPS。判断的方法是,看这两个指标是否接近1:1。如果接近1:1说明系统很大概率是可以线性扩展的。若远远大于1:1,那么就不能线性扩展。

原文地址

文章评论

如何成为一名黑客
如何成为一名黑客
编程语言是女人
编程语言是女人
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
我是如何打败拖延症的
我是如何打败拖延症的
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
老程序员的下场
老程序员的下场
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
代码女神横空出世
代码女神横空出世
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
 程序员的样子
程序员的样子
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
程序员和编码员之间的区别
程序员和编码员之间的区别
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
鲜为人知的编程真相
鲜为人知的编程真相
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
总结2014中国互联网十大段子
总结2014中国互联网十大段子
一个程序员的时间管理
一个程序员的时间管理
每天工作4小时的程序员
每天工作4小时的程序员
10个调试和排错的小建议
10个调试和排错的小建议
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
程序员都该阅读的书
程序员都该阅读的书
我的丈夫是个程序员
我的丈夫是个程序员
为什么程序员都是夜猫子
为什么程序员都是夜猫子
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
旅行,写作,编程
旅行,写作,编程
那些争议最大的编程观点
那些争议最大的编程观点
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
中美印日四国程序员比较
中美印日四国程序员比较
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
程序员的鄙视链
程序员的鄙视链
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
程序员应该关注的一些事儿
程序员应该关注的一些事儿
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有