分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
海量存储检索原理系列文章
作者:WhisperXD
来源:http://qing.blog.sina.com.cn/whisperxd
海量存储之序言
今天玩微薄的时候有人问我有没有数据存储的相关资料,我想了想。。虽然在这个领域内也算有点积累,以前讲课的ppt有200多页,但毕竟ppt的信息量有限。所以在这里将这个系列的部分内容在这里进行重新编排
海量存储系列之一
http://qing.weibo.com/1765738567/693f0847330005sk.html 海量存储系列之序言
海量存储系列之二
http://qing.weibo.com/1765738567/693f0847330005sm.html 上一篇
海量存储系列之三
上一篇 http://qing.weibo.com/1765738567/693f0847330005v7.html
首先是回答上次的问题。
假设有这么一组数据,性别有4种,user_id是一对多的关系,如果我想查询
select * from tabwhere user_id in (?,?,?,?) and 性别='不明'
如何进行索引构建能够获得比较好的效果呢?
我个人认为,应该建立的是以user_id作为前导列,性别作为辅助列的索引,在大量单值查询时会有优势。
理由如下
1. 假定总数据量为N,user_id的区分度为N/10000 而性别的区分度为N/4
那么如果以user_id作为前导列,性别作为后列,那么查询的复杂度为O(logN+log(N/10000))。也就是说,第一次二分查找之后,下一次是在第一次的二分查找的基础上再去查找。而如果以性别作为前导,user_id作为后列,那么复杂度为
O(logN+log(N/4));
效率略差。
然后进入本次正题。上次介绍了关系模型,那么这次我们来介绍一下事务。
在一切之前,我想先给自己解嘲一下。。事务我自己也没有办法完全融汇贯通,因为每一个小的选择,都会导致效果的完全不同,所以有错请在后面一起探讨。
那么我们在这里,主要以单机事务作为入手点,然后阐述一下多机事务相关的知识点。我在这里只是想做一个引导,让大家能够对整个的知识体系有一个基本的认识,对于细节问题,会给出一些资料,而不会直接去进行讲解,因为篇幅所限.
一般来说,我们一提起事务,就会想到数据库,确实,事务是数据库的最重要的一个属性。但这似乎不是事务的本源,那么,让我们从更深层次去对事务进行一次思考吧:
事务,本质来说就是一组由一个人(或机器)发起的连续的逻辑操作,共同的完成一件事情,在完成整个事情之前,其所有的改动,都不应该对其他人可见和影响。而在事务结束之后,其一切的改动,都必须“全部”“立刻”对其他的人(或机器)可见。
然后,人们为了描述这一运作,使用了四个词汇,这也是很多面试的同学们折戟沉沙之处。J 不过这个以前我也不会,后来发现,理解了以后,确实有点用,所以这里也费一些笔墨吧。
原子性(Atomicity):也就是说,一组操作,要不就都成功,要不就都失败。不存在中间状态。
一致性(Consistency):一致性,也就是说,这个事务在提交或回滚的时候,对其他人(或机器)来说,数据的状态是同一的,不会出现中间的状态。最理想的状态下,就是说,数据提交后,所有的更改立刻同时生效,可惜,在计算机领域,这个做不到。。因为cpu运算,磁盘写入,内存写入,都是要时间的,内部一定是个顺序化的过程,所以不可能做到绝对的立刻同时生效。
所以一致性一般来说指代的是逻辑上的同时生效,比如,我要改A,B两行数据,那么,最简单的一致性保证就是,对A,B加锁,改A,B,然后对A,B解锁。
这样下一个人读到的一定是A,B的最新值啦。
(但这块有很多种解释,一般来说这是个最不明确的词汇)。
隔离性(Isolation):隔离,这是面试最容易挂的一个问题,其实我认为不怪我们,而是因为本身这个隔离性,是依托锁来进行设计的。
我们所知道的锁,主要有以下几种,1.读写锁,2. 排他锁
那么这四种级别其实就和这两种锁的实现有关,之所以要定义四个级别,其实原因也是因为,锁的范围越大,并行效率越低。而范围越小,那么并行的效率就越高。
读未提交: 其实就是什么锁也没有,所以数据的中间状态,是可能被其他人读到的。
读已提交:就是读写锁实现,读锁在查询之后会被释放掉,所以这样其他人可能会更改那些被释放了读锁的数据,这样当前事务再去读取的时候,就可能读取到被别人修改过的数据了,所以一个人在事务中读取到的某个数据,可能下次读取就变成别的数据啦。这就是不可重复读的意思。。
可重复读:也是个读写锁实现,读锁会阻塞其他人(或机器)的写,于是,只要是事务中读取到得数据,都被加了锁,其他人没办法改他们,于是就实现了可重复读咯。
最后是序列化,就是所有都顺序,一个大锁全部锁住J
持久性(Durability):持久性就是,事务执行后,就丢不了了,就算是整个中国被淹了,机器都没了,数据也不应该丢掉(不过基本做不到这个,也就是一个机器挂了不会丢数据而已。。)所有机房没了那数据也就没了。。
对于这块,给大家一些参考资料:
http://zh.wikipedia.org/wiki/%E4%BA%8B%E5%8B%99%E9%9A%94%E9%9B%A2
http://www.cnblogs.com/wangiqngpei557/archive/2011/11/19/2255132.html
这些讲的不错,浅显易懂是我的最爱.
好啦,为了保证我写的东西不会被"qing"这个大怪兽再次吃掉。。我先发这些。
在下一个章节,我们继续在事务这个领域徜徉,给大家介绍一下,在单机上面,事务是如何进行的。
http://qing.weibo.com/1765738567/693f08473300067j.html 下一篇 单机事务
海量存储系列之四
其实在上面介绍ACID的时候
我们已经提到了一种最简单的实现方式,就是锁的实现方式。
从原理来看,事务是个变态而复杂的事情。其实如果是序列化的话呢,那么实现起来一定是非常简单的。
但问题就在于,这样性能实在比较低,于是,就有了非常多的方案,为了能哪怕减少一个地方的锁,或者降低一个地方的锁的级别,就付出大量的时间和代码加以实现。
那么,让我们以崇敬的心情,去拜读一下他们的劳动成果吧~
--------------------------------------------------------------------------------
在上一篇中,我们谈了事务管理的四个核心要素,其中有两个要素是和性能紧密相关的,其实也就是需要涉及到锁的,一个是隔离性,一个是一致性。
一致性问题和隔离性问题,我们都可以归结为一个问题,他们都用于定义,什么时候数据可被共享,什么时候数据必须被独占。而这些决策,就最终决定了整个数据库系统的并行度,也就直接的决定了多线程并发时的性能指标。
如果要改一大批数据,又必须保证这些数据要么都出现,要么都不出现,这时候就有个难题了:因为这些数据不可能在同一个时间被选出,更不可能在同一个时间被更改。
于是就必须想个办法来假装达到这个状态,于是我们就需要一种方法,使得针对不同数据的更改,不同人(或机器)不打架。而如果出现对相同数据的更改,则要将更新进行排队。
这个排队可供选择的方法,就我知道的有:1,排他锁。2. 读写锁。3. Copy on write(MVCC) .4. 队列。5. 内存事务。这些方式。
从性能来说,排他锁最慢,而读写因为读可以并发,所以效率稍高,但写和读不能同时进行。3. Copy on write(MVCC) 则读取和写入之间可以互相不影响,所以效率更高。队列这种方式,内存时效果很好,省去中断上下文切换的时间。内存事务,目前还在研究阶段,具备很大潜力的东西。
排他锁,队列和内存事务,在目前的数据库中用的相对较少,我们就不在这里说了。
这里主要说两种实现,一种是读写锁,一种是MVCC.
先说读写锁,也是隔离性中“读已提交,可重复读”两种实现中最重要的底层实现方式。
简单来说,就是如果一个人在事务中,那么他所有写过的数据,所有读过的数据,都给他来个锁,让其他小样儿都只能等在外面,直到数据库能确定所有更改已经全部完成了,没有剩下什么半拉子状态的时候,就解开所有的锁,让其他人可以读取和写入。Hoho,就是这个了。
那么MVCC呢,其实是对读写锁的一个改进,有一批大牛们,说你们这读写锁,写的时候不能读,读的时候不能写,并行度太低了,我要做个更牛B的,写不阻塞读,读不阻塞写的东西来超越你们。
于是他们想起了copy-on-write.鼓捣了个MVCC数据库出来。。。
题外话,现在的甲骨文,之所以能在数据库领域保持优势地位,有个很重要的原因也是因为他们是很早就在商业数据库系统中实现了MVCC的数据写入引擎。
所以他们的Thomas Kyte 技术副总裁也就有了在他们的最牛逼的oracle专家编程里面有了吹嘘的资本 XD .
这里我们要着重的介绍一下MVCC,因为这东西看起来非常的精妙而美丽。。。现在大量的分布式类存储中,也都在借鉴这套模式中的很多部分来增加自己的并行度,以提升性能。比如megaStore.比如percolator。
我们在读写锁的实现中,提到了写读的相互阻塞问题,MVCC则使用copy-on-write来解决这个问题。
如果一个人在事务中,会先申请一个事务ID,这个ID是自增的,每个事务都有他自己的唯一的ID,那么他写过的数据,都会被转变为一次带有当前事务ID的新数据,在读取的时候,则只会读取小于等于自己事务ID的数据。这样实现的东东,语义上来说,与可重复读就一样了。而如果读小于等于全局ID的数据,那么这样的实现,就是读已提交了。
一般来说,MVCC只实现了四个级别中的第二级和第三级,其他的就没有啦,不过这两个是我们最常见的级别。所以也就大家同乐,同乐了~
有了这个东西,我们的一致性也就很容易保证了,因为一个事物和他对应的版本号对应,又有更改后的数据和更改前的数据,如果要提交,那么就只需要很简单的让更改后的数据生效可见即可,这样我们可以将大量的更新中要做的事情,都在事务过程中进行,这样,比原有的基于读写锁的必须在commit时候一起做掉来说,commit这个操作就轻量化了很多,于是,就可以支持更多的人(或机器)持有事务状态了。
很美妙吧?
我一致认为这是oracle当年的核心竞争力,不过现在基本上是个数据库就用了这一套,我们就不在多嘴啦~
解决了一致性和隔离性,剩下的是原子性和持久性,原子性么,一般来说就是要么都成功,也就是新版本数据都让他生效,要么就都失败,也就是让和自己事务ID对应的所有修改都无效即可。也很好就解决掉了。持久性。这个就是后面我们要在写入模型里面介绍的东西了,基本上来说就是写磁盘策略的事情。
到这里,我们单机ACID的实现大概思路,就给大家介绍过了。下一个章节,我们还要用很多的文字,来向大家介绍在分布式场景中我们面临的事务的难题,以及“我所知道的”百花齐放的解决方法。
http://qing.weibo.com/1765738567/693f0847330006ao.html?rnd=0.6134993201121688
海量存储系列之五
http://qing.weibo.com/1765738567/693f08473300067j.html 上一篇
需要说明的一点是,这里涉及到的权衡点非常的多。就我短短的工作经验里面,也只是能够简单的涉猎一部分,因为在事务这个领域,目前大家都在尝试提出各种各样的不同的方法,而在taobao,我们目前也没有完美的解决这个问题,更多的是在权衡,在金钱和开发成本之间,做出选择。
那么,我们就先从问题开始,来看一下原来的事务出了什么问题。
在事务中,有ACID四种属性。(见上篇文章)
在分布式场景中,我们看引入了什么因素,导致了什么样的新问题:
1. 延迟因素:光是我们所知最快的信息载体了,各位可能都会从潜意识里面认为光传输信息不就是一眨眼的事情而已。那我们做个简单的计算吧(感谢@淘宝叔度,第一次在分享中让我对这个问题有了个数值化的印象。):
北京到杭州,往返距离2600km ,光在真空中的传输速度是30wkm/s。在玻璃中的速度是真空的2/3。算下来,最小的请求和响应,之间的延迟就有13ms。并且,因为光在管子里走的不是直线,又有信号干扰等问题,一般来说要乘以2~3倍的因子值。
所以一次最小的请求和响应,时间就差不多有30ms左右了。
再想想TCP的时间窗口的移动策略,相信大家都能意识到,实际上延迟是不可忽略的,尤其在传输较多数据的时候,延迟是个重要的因素,不能不加以考虑。
并且,延迟 不是 带宽,带宽可以随便增加,千兆网卡换成万兆,但延迟却很难降低。而我们最需要的,是带宽,更是延迟的降低。因为他直接决定了我们的可用性。
2. 灾备因素:单机的情况下,人们一般不会去追求说一个机器物理上被水冲走了的时候,我的数据要保证不丢(因为没办法的嘛。。)。但在分布式场景下,这种追求就成为了可能,而互联网行业,对这类需求更是非常看重,恨不能所有的机器都必须是冗余的,可随意替换的。这样才能保证7*24小时的正常服务。这无疑增加了复杂度的因素。
3. Scale out的问题: 单机总是有瓶颈的,于是,人们的追求就一定是:不管任何一种角色的机器,都应该可以通过简单的增加新机器的方式来提升整个集群中任何一个角色的性能,容量等指标。这也是互联网行业的不懈追求。
4. 性能:更快的响应速度,更低的延迟,就是更好的用户体验。(所以google用了个“可怜”到家的简单input框来提升用户体验,笑)。
说道这里,大概大家都应该对在分布式场景下的广大人民群众的目标有了一个粗略的认识了。
那么我们来看一下原有ACID的问题吧。
在上次的章节中,我们也提到了ACID中,A和D相对的,比较容易达到。但C和I都涉及到锁实现,也就和性能紧密的相关了。
然后,人们就开始了纠结,发掘这个C和I,似乎不是那么容易了。
上次,我们谈到,目前主流的实现一次更新大量数据的时候,不同人(或机器)修改数据相互之间不会打架的方法有以下几种:
1. 排他锁
2. 读写锁
3. Copy-on-write
4. 队列
5. 内存事务
排他锁和读写锁,本身都是锁的实现,单机的锁实现,相对而言是非常简单的事情,但如果涉及到分布式锁,那么消耗就很高了,原因是,锁要在两边都达到一致,需要多次机器之间的交互过程,这个交互的过程,再考虑到延迟的因素,基本上一次加锁请求就要100~200+毫秒的时间了,那么去锁又要这样的时间。而要知道,我们在单机做内存锁操作,最慢也不过10毫秒。。
于是,有一批人就说了,既然这么难,我们不做了!~来个理论证明他很难就行了~。于是就有了CAP和BASE.
所谓CAP,我个人的理解是描述了一种: 在数据存了多份的前提下,一致性和响应时间,读写可用性不可兼得的“现象”而已。
在我这里来看CAP的证明过程就是个扯淡的玩意儿,他只是描述了一种现象而已。原因还是网络延迟,因为延迟,所以如果要做到数据同时出现或消失,那么按照锁的方式原来可能只需要10ms以内完成的操作,现在要200~400ms才能完成,那自然不能接受了。所谓CAP就是这个现象的英文简称,笑。
BASE呢,这个理论似乎更老,其实也是个现象,就是基本可用,软状态,最终一致的简称,也没个证明,其实就是告诉咱:要权衡一下,原来的ACID不太容易实现啦,我们得适当放弃一些啦。但请各位注意,ACID实际上是能够指导我们在什么情况下做什么样的事情能够获取什么样的结果的。而BASE则不行,这也说明BASE不是个经典的理论。
好啦。废话了这么多,其实就是想说,分布式场景没有银弹啦,你们自己权衡去吧。我们大牛们救不了你们啦的意思。。
既然大牛救不了咱,咱就只能自救了。。。
好,好的文章就要在关键的地方恰然而止,留下悬念,我们也就在这里留下点悬念吧。
在这篇中,主要是想给大家介绍一下,目前在分布式场景中,事务碰到了什么问题,出现这些问题的原因是什么。
在下一篇中,我将尝试从原理的角度,去分析目前的几类常见的在分布式场景中完成原有事务需求的方法。敬请期待 : )
海量存储系列之六
在我开始做淘宝数据层的时候,被问得最多的无非也就是:如何做事务,如何做join.至今仍然如此,我一般都会简单而明确的跟对方说:没有高效的实现方法。
虽然没有高效的实现,但实现还是有的。作为引子,我们先来介绍一下这种实现的方式。
我们仍然以上一次讲到的bob和smith为例子来说明好了。
开始的时候。Bob要给smith100块,那么实际上事务中要做的事情是
事务开始时查询bob有多少钱。如果有足够多的钱让bob的账户 -100 ,然后给smith 的账户+100 。最后事务结束。
如果这个事情在单机,那么事情可以使用锁的方式加以解决。
但如果bob在一台机器,smith在另外一台