日前国内没有一套比较完整的NoSQL数据库资料,有很多先驱整理发表了很多,但不是很系统。不材尝试着将各家的资料整合一下,并书写了一些自己的见解。本书写了一些目前的NoSql的一些主要技术,算法和思想。同时列举了大量的现有的数据库实例。读完全篇,相信读者会对NoSQL数据库了解个大概。另外我还准备开发一个开源内存数据库galaxydb.本书也是为这个数据库提供一些架构资料。
下面以上面的场景来描述下不同程度的一致性:
说起来很有趣,BASE的英文意义是碱,而ACID是酸。真的是水火不容啊。
你是否需要改变对应用和硬件的思维方式,最终取决于你要用它们完成的工作。但似乎公论认为,开发者解决性能和可伸缩性的思路已经到了该变一变的时候。
S(n)=1/(K+(1-K)/n)=n/(1+(n-1)K)
Gustafson定律的加速比:S(n)=使用n个处理器的并行计算量/使用1个处理器的串行计算量
S(n)=K+(1-K)n有点冷是不是?通俗的讲,Amdahl定律将工作量看作1,有n核也只能分担1-K的工作量;而Gustafson定律则将单核工作量看作1,有n核,就可以增加n(1-K)的工作量。这里没有考虑引进分布式带来的开销,比如网络和加锁。成本还是要仔细核算的,不是越分布越好。控制算法的复杂性在常数范围之内。
优点:发生单点故障时负载会均衡分散到其他所有节点,程序实现也比较优雅。
……
然而,在最后的八个月左右,他们“盔甲”内的漏洞开始呈现出来了。第一个弱点前兆是,新加入的AmazonSMALL实例的性能出现了问题。根据我们的监控,在服务器场中新添加的机器,与原先的那些相比性能有所下降。开始我们认为这是自然出现的怪现象,只是碰巧发生在“吵闹的邻居”(NoisyNeighbors)旁边。根据随机法则,一次快速的停机和重新启动经常就会让我们回到“安静的邻居”旁边,那样我们可以达到目的。
……然而,在最后的一两个月中,我们发现,甚至是这些“使用高级CPU的中等实例”也遭受了与小实例相同的命运,其中,新的实例不管处于什么位置,看起来似乎都表现得一样。经过调查,我们还发现了一个新问题,它已经悄悄渗透到到Amazon的世界中,那就是内部网络延迟。
Gossip协议是一个Gossip思想的P2P实现。现代的分布式系统经常使用这个协议,他往往是唯一的手段。因为底层的结构非常复杂,而且Gossip也很有效。
Gossip协议也被戏称为病毒式传播,因为他的行为生物界的病毒很相似。
在状态转移到模式下,每个重复节点都保持的一个Vectorclock和一个stateversiontree。每个节点的状态都是相同的(basedonvectorclockcomparison),换句话说,stateversiontree包含有全部的冲突updates.Atquerytime,theclientwillattachitsvectorclockandthereplicawillsendbackasubsetofthestatetreewhichprecedestheclient'svectorclock(thiswillprovidemonotonicreadconsistency).Theclientwillthenadvanceitsvectorclockbymergingalltheversions.Thismeanstheclientisresponsibletoresolvetheconflictofalltheseversionsbecausewhentheclientsendstheupdatelater,itsvectorclockwillprecedealltheseversions.
Atupdate,theclientwillsenditsvectorclockandthereplicawillcheckwhethertheclientstateprecedesanyofitsexistingversion,ifso,itwillthrowawaytheclient'supdate.
Replicasalsogossipamongeachotherinthebackgroundandtrytomergetheirversiontreetogether.
Whenanupdateoperationisreceived,thereplicawillbuffertheupdateoperationuntilitcanbeappliedtothelocalstate.Everysubmittedoperationwillbetagwith2timestamp,V-clientindicatestheclient'sviewwhenheismakingtheupdaterequest.V-@receiveisthereplica'sviewwhenitreceivesthesubmission.Thisupdateoperationrequestwillbesittinginthequeueuntilthereplicahasreceivedalltheotherupdatesthatthisonedependson.ThisconditionisreflectedinthevectorclockViwhenitislargerthanV-client
Onthebackground,differentreplicasexchangetheirlogforthequeuedupdatesandupdateeachother'svectorclock.Afterthelogexchange,eachreplicawillcheckwhethercertainoperationcanbeapplied(whenallthedependentoperationhasbeenreceived)andapplythemaccordingly.Noticethatitispossiblethatmultipleoperationsarereadyforapplyingatthesametime,thereplicawillsorttheseoperationincausalorder(byusingtheVectorclockcomparison)andapplythemintherightorder.
Theconcurrentupdateproblematdifferentreplicacanalsohappen.Whichmeanstherecanbemultiplevalidsequencesofoperation.Inorderfordifferentreplicatoapplyconcurrentupdateinthesameorder,weneedatotalorderingmechanism.Oneapproachiswhoeverdotheupdatefirstacquireamonotonicsequencenumberandlatecomersfollowthesequence.Ontheotherhand,iftheoperationitselfiscommutative,thentheordertoapplytheoperationsdoesn'tmatterAfterapplyingtheupdate,theupdateoperationcannotbeimmediatelyremovedfromthequeuebecausetheupdatemaynotbefullyexchangetoeveryreplicayet.WecontinuouslychecktheVectorclockofeachreplicasafterlogexchangeandafterweconfirmthaneveryonehasreceivethisupdate,thenwe'llremoveitfromthequeue.
GoogleChubby的作者MikeBurrows说过,“thereisonlyoneconsensusprotocol,andthat’sPaxos”–allotherapproachesarejustbrokenversionsofPaxos.意即“世上只有一种一致性算法,那就是Paxos”,所有其他一致性算法都是Paxos算法的不完整版。相比2PC/3PC,Paxos算法的改进P1a.每次Paxos实例执行都分配一个编号,编号需要递增,每个replica不接受比当前最大编号小的提案P2.一旦一个valuev被replica通过,那么之后任何再批准的value必须是v,即没有拜占庭将军(Byzantine)问题。拿上面请客的比喻来说,就是一个参与者一旦accept周六2pm-5pm的proposal,就不能改变主意。以后不管谁来问都是accept这个value。一个proposal只需要多数派同意即可通过。因此比2PC/3PC更灵活,在一个2f+1个节点的集群中,允许有f个节点不可用。另外Paxos还有很多约束的细节,特别是Google的chubby从工程实现的角度将Paxos的细节补充得非常完整。比如如何避免Byzantine问题,由于节点的持久存储可能会发生故障,Byzantine问题会导致Paxos算法P2约束失效。以上几种方式原理比较如下
Noticethattheupdatehappensinanappend-onlymodewherethemodifieddataisappendedtothefileandtheolddatabecomesgarbage.Periodicgarbagecollectionisdonetocompactthedata.Hereishowthemodelisimplementedinmemoryanddisks
InGoogleBigTablemodel,thedataisbrokendownintomultiplegenerationsandthememoryisusetoholdthenewestgeneration.Anyquerywillsearchthememdataaswellasallthedatasetsondisksandmergeallthereturnresults.Fastdetectionofwhetheragenerationcontainsakeycanbedonebycheckingabloomfilter.
Whenupdatehappens,boththememdataandthecommitlogwillbewrittensothatifthe
Noticethatvirtualnodescanjoinandleavethenetworkatanytimewithoutimpactingtheoperationofthering.Whenanewnodejoinsthenetwork
Noticethatothernodesmaynothavetheirmembershipviewupdatedyetsotheymaystillforwardtherequesttotheoldnodes.Butsincetheseoldnodes(whichistheneighborofthenewjoinednode)hasbeenupdated(instep2),sotheywillforwardtherequesttothenewjoinednode.Ontheotherhand,thenewjoinednodemaystillintheprocessofdownloadingthedataandnotreadytoserveyet.Weusethevectorclock(describedbelow)todeterminewhetherthenewjoinednodeisreadytoservetherequestandifnot,theclientcancontactanotherreplica.Whenanexistingnodeleavesthenetwork(e.g.crash)
Wehaven'ttalkedabouthowthevirtualnodesismappedintothephysicalnodes.ManyschemesarepossiblewiththemaingoalthatVirtualNodereplicasshouldnotbesittingonthesamephysicalnode.OnesimpleschemeistoassignedVirtualnodetoPhysicalnodeinarandommannerbutchecktomakesurethataphysicalnodedoesn'tcontainreplicasofthesamekeyranges.Noticethatsincemachinecrasheshappenatthephysicalnodelevel,whichhasmanyvirtualnodesrunsonit.SowhenasinglePhysicalnodecrashes,theworkload(ofitsmultiplevirtualnode)isscatteredacrossmanyphysicalmachines.Thereforetheincreasedworkloadduetophysicalnodecrashesisevenlybalanced.
数据库以行、列的二维表的形式存储数据,但是却以一维字符串的方式存储,例如以下的一个表:
这个简单的表包括员工代码(EmpId),姓名字段(LastnameandFirstname)及工资(Salary).
这个表存储在电脑的内存(RAM)和存储(硬盘)中。虽然内存和硬盘在机制上不同,电脑的操作系统是以同样的方式存储的。数据库必须把这个二维表存储在一系列一维的“字节”中,又操作系统写到内存或硬盘中。
行式数据库把一行中的数据值串在一起存储起来,然后再存储下一行的数据,以此类推。1,Smith,Joe,40000;2,Jones,Mary,50000;3,Johnson,Cathy,44000;
列式数据库把一列中的数据值串在一起存储起来,然后再存储下一列的数据,以此类推。1,2,3;Smith,Jones,Johnson;Joe,Mary,Cathy;40000,50000,44000;
我发明的新概念,就是称不上数据库但有一些数据库的特征。可以指缓存。
Memcached是danga.com(运营LiveJournal的技术团队)开发的一套分布式内存对象缓存系统,用于在动态系统中减少数据库负载,提升性能。
Memcached处理的原子是每一个(key,value)对(以下简称kv对),key会通过一个hash算法转化成hash-key,便于查找、对比以及做到尽可能的散列。同时,memcached用的是一个二级散列,通过一张大hash表来维护。
Memcached有两个核心组件组成:服务端(ms)和客户端(mc),在一个memcached的查询中,mc先通过计算key的hash值来确定kv对所处在的ms位置。当ms确定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。因为这之间没有交互以及多播协议,所以memcached交互带给网络的影响是最小化的。
同时,ms对key和data都有相应的限制,key的长度不能超过250字节,data也不能超过块大小的限制---1MB。因为mc所使用的hash算法,并不会考虑到每个ms的内存大小。理论上mc会分配概率上等量的kv对给每个ms,这样如果每个ms的内存都不太一样,那可能会导致内存使用率的降低。所以一种替代的解决方案是,根据每个ms的内存大小,找出他们的最大公约数,然后在每个ms上开n个容量=最大公约数的instance,这样就等于拥有了多个容量大小一样的子ms,从而提供整体的内存使用率。
当ms的hash表满了之后,新的插入数据会替代老的数据,更新的策略是LRU(最近最少使用),以及每个kv对的有效时限。Kv对存储有效时限是在mc端由app设置并作为参数传给ms的。
同时ms采用是偷懒替代法,ms不会开额外的进程来实时监测过时的kv对并删除,而是当且仅当,新来一个插入的数据,而此时又没有多余的空间放了,才会进行清除动作。
现在memcached最流行的一种使用方式是缓存数据库查询,下面举一个简单例子说明:
App需要得到userid=xxx的用户信息,对应的查询语句类似:
“SELECT*FROMusersWHEREuserid=xxx”
App先去问cache,有没有“user:userid”(key定义可预先定义约束好)的数据,如果有,返回数据;如果没有,App会从数据库中读取数据,并调用cache的add函数,把数据加入cache中。
当取的数据需要更新,app会调用cache的update函数,来保持数据库与cache的数据同步。
从设计角度上,memcached是没有数据冗余环节的,它本身就是一个大规模的高性能cache层,加入数据冗余所能带来的只有设计的复杂性和提高系统的开支。
当一个ms上丢失了数据之后,app还是可以从数据库中取得数据。不过更谨慎的做法是在某些ms不能正常工作时,提供额外的ms来支持cache,这样就不会因为app从cache中取不到数据而一下子给数据库带来过大的负载。
同时为了减少某台ms故障所带来的影响,可以使用“热备份”方案,就是用一台新的ms来取代有问题的ms,当然新的ms还是要用原来ms的IP地址,大不了数据重新装载一遍。
有了缓存的支持,我们可以在传统的app层和db层之间加入cache层,每个app服务器都可以绑定一个mc,每次数据的读取都可以从ms中取得,如果没有,再从db层读取。而当数据要进行更新时,除了要发送update的sql给db层,同时也要将更新的数据发给mc,让mc去更新ms中的数据。
Memcached写速度平均速度:16222次/秒最大速度18799次/秒Memcached读速度平均速度:20971次/秒最大速度22497次/秒Memcachedb写速度平均速度:8958次/秒最大速度10480次/秒Memcachedb读速度平均速度:6871次/秒最大速度12542次/秒
Yahoo!的PNUTS是一个分布式的数据存储平台,它是Yahoo!云计算平台重要的一部分。它的上层产品通常也称为Sherpa。按照官方的描述,”PNUTS,amassivelyparallelandgeographicallydistributeddatabasesystemforYahoo!’swebapplications.”PNUTS显然就深谙CAP之道,考虑到大部分web应用对一致性并不要求非常严格,在设计上放弃了对强一致性的追求。代替的是追求更高的availability,容错,更快速的响应调用请求等。
在云之外,也有一些可以独立安装的键/值数据库软件产品。大部分都还很年轻,不是alpha版就是beta版,但大都是开源的;通过看看它的代码,比起在非开源供应商那里,你也许更能意识到潜在的问题和限制。
CouchDB一种半结构化面向文档的分布式,高容错的数据库系统,其提供RESTFulHTTP/JSON接口。其拥有MVCC特性,用户可以通过自定义Map/Reduce函数生成对应的View。在CouchDB中,数据是以JSON字符的方式存储在文件中。
应用场景在我们的生活中,有很多document,比如信件,账单,笔记等,他们只是简单的信息,没有关系的需求,我们可能仅仅需要存储这些数据。这样的情况下,CouchDB应该是很好的选择。当然其他使用关系型数据库的环境,也可以使用CouchDB来解决。
根据CouchDB的特性,在某些偶尔连接网络的应用中,我们可以用CouchDB暂存数据,随后进行同步。也可以在Cloud环境中,作为分布式的数据存储。CouchDB提供给予HTTP的API,这样所有的常见语言都可以使用CouchDB。
TC是日本第一大SNS网站mixi开发的,而Flare是日本第二大SNS网站green.jp开发的,有意思吧。Flare简单的说就是给TC添加了scale功能。他替换掉了TT部分,自己另外给TC写了网络服务器,Flare的主要特点就是支持scale能力,他在网络服务端之前添加了一个nodeserver,来管理后端的多个服务器节点,因此可以动态添加数据库服务节点,删除服务器节点,也支持failover。如果你的使用场景必须要让TC可以scale,那么可以考虑flare。flare唯一的缺点就是他只支持memcached协议,因此当你使用flare的时候,就不能使用TC的table数据结构了,只能使用TC的key-value数据结构存储。
BeansDB是一个主要针对大数据量、高可用性的分布式KeyValue存储系统,采用HashTree和简化的版本号来快速同步保证最终一致性(弱),一个简化版的Dynamo。
它采用类似memcached的去中心化结构,在客户端实现数据路由。目前只提供了Python版本的客户端,其它语言的客户端可以由memcached的客户端稍加改造得到。
2009.12.29第一个公开版本0.3
在小数据集上,它跟memcached一样快:#memstorm-slocalhost:7900-n1000NumofRecords:10000Non-BlockingIO:0TCPNo-Delay:0Successful[SET]:10000Failed[SET]:0TotalTime[SET]:0.45493sAverageTime[SET]:0.00005sSuccessful[GET]:10000Failed[GET]:0TotalTime[GET]:0.28609sAverageTime[GET]:0.00003s
1.万事皆异步我们在编码的过程中走了一些弯路,同步的操作在高并发的情况下带来的性能下降是非常恐怖的,于是乎,Nuclear系统中任何的高并发操作都消除了Block。nowaiting,nodelay。2.根据系统负载控制后台线程的资源占用Nuclear系统中有不少的后台线程默默无闻的做着各种辛苦的工作,但是它们同样会占用系统资源,我们的解决方案是根据系统负载动态调整线程的运行和停止,并达到平衡。
当你需要查询或更新一个值的一部分时,Key/value模型是最简单有效实现。面向文本数据库是Key/value的下一步,允许内嵌和Key关联的值.支持查询这些值数据,这比简单的每次返回整个blob类型数据要有效得多。Neo4J是唯一的存储对象和关系作为数学图论中的节点和边.对于这些类型数据的查询,他们能够比其他竞争者快1000sScalaris是唯一提供跨越多个key的分布式事务。
内存数据库是非常快的,(Redis在单个机器上可以完成每秒100,000以上操作)但是数据集超过内存RAM大小就不行.而且Durability(服务器当机恢复数据)也是一个问题Memtables和SSTables缓冲buffer是在内存中写(“memtable”),写之前先追加一个用于durability的日志中.但有足够多写入以后,这个memtable将被排序然后一次性作为“sstable.”写入磁盘中,这就提供了近似内存性能,因为没有磁盘的查询seeks开销,同时又避免了纯内存操作的durability问题.(个人点评其实Java中的Terracotta早就实现这两者结合)B-Trees提供健壮的索引,但是性能很差,一般和其他缓存结合起来。
Twitter的监控后台几乎都是图表(criticalmetrics),类似驾驶室的转速表,时速表,让操作者可以迅速的了解系统当前的运作状态。联想到我们做的类似监控后台,数据很多,但往往还需要浏览者做二次分析判断,像这样满屏都是图表的方法做得还不够,可以学习下这方面经验。据John介绍可以从图表上看到系统的瓶颈-系统最弱的环节(web,mq,cache,db)根据图表可以科学的制定系统容量规划,而不是事后救火。
每个系统都需要一个自动配置管理系统,越早越好,这条一整理发到Twitter上去之后引起很多回应。
配置界面可以enable/disable高计算消耗或高I/O的功能,也相当于优雅降级,系统压力过大时取消一些非核心但消耗资源大的功能。
Twitter做了一个”Seppaku”patch,就是将Daemon在完成了n个requests之后主动kill掉,以保持健康的lowmemory状态,这种做法据了解国内也有不少公司是这样做。
Twitter将CPU由AMD换成Xeon之后,获得30%性能提升,将CPU由双核/4核换成8核之后,减少了40%的CPU,不过John也说,这种升级不适合自己购买硬件的公司。
大部分的人都坚持在单一的设备上部署我们的应用,因为这样部署的费用会比较低,但是我们要清楚任何的硬件设备都会有失败的风险的,这种单点失败会严重的影响用户体验甚至是拖垮你的应用,因此除非你的应用能容忍失败带来的损失,否则得话应该尽量的避免单点风险,比如做冗余,热备等。
同步调用在任何软件系统中都是不可避免的,但是我们软件工程师必须明白同步调用给软件系统带来的问题。如果我们将应用程序串接起来,那么系统的可用性就会低于任何一个单一组件的可用性。比如组件A同步调用了组件B,组件A的可用性为99.9%,组件B的可用性为99.9%,那么组件A同步调用组件B的可用性就是99.9%*99.9%=99.8%。同步调用使得系统的可用性受到了所有串接组件可用性的影响,因此我们在系统设计的时候应该清楚哪些地方应该同步调用,在不需要同步调用的时候尽量的进行异步的调用(而我这里所说的异步是一种基于应用的异步,是一种设计上的异步,因为J2EE目前的底层系统出了JMS是异步API以外,其它的API都是同步调用的,所以我们也就不能依赖于底层J2EE平台给我们提供异步性,我们必须从应用和设计的角度引入异步性)
日志记录对于一个成熟稳定的系统是非常重要的,如果我们不进行日志记录,那么我就很难统计系统的行为。
随着系统规模的慢慢变大,我们就需要打破单一数据的限制,需要对其进行切分。
系统在规模小的时候,也许感觉不出无切分的应用带来的问题,但是在目前互联网高速发展的时代,谁能保证一个小应用在一夜或者是几夜以后还是小应用呢?说不定哪天,我们就发现应用在突如其来的访问量打击的支离破碎。因此我们就需要让我们的系统和我们一样具有生命力,要想让系统具有应付大负载的能力,这就要求我们的应用具有很好的伸缩性,这也就要求应用需要被良好的切分,只有进行了切分,我们才能对单一的部门进行伸缩,如果应用是一块死板的话,我们是没有办法进行伸缩的。就好比火车一样,如果火车设计之初就把他们设计为一体的,那么我们还怎么对火车的车厢进行裁剪?因此一个没有切分的应用是一个没有伸缩性和没有可用性的应用。
如果我们的应用系统的伸缩性依赖于第三方的厂商,比如依赖于数据库集群,那么我们就为系统的伸缩性埋下了一个定时炸弹。因为只有我们自己最清楚我们自己的应用,我们应该从应用和设计的角度出发去伸缩我们的应用,而不是依赖于第三方厂商的特性。
联机分析处理(OLAP)的概念最早是由关系数据库之父E.F.Codd于1993年提出的,他同时提出了关于OLAP的12条准则。OLAP的提出引起了很大的反响,OLAP作为一类产品同联机事务处理(OLTP)明显区分开来。
通过对数据进行分区,我们最小化了失效带来的影响,也将读写操作的负载分布到了不同的机器上。如果一个节点失效了,只有该节点上存储的数据受到影响,而不是全部数据。
大部分NOSQL实现都基于数据副本的热备份来保证连续的高可用性。一些实现提供了API,可以控制副本的复制,也就是说,当你存储一个对象的时候,你可以在对象级指定你希望保存的副本数。在GigaSpaces,我们还可以立即复制一个新的副本到其他节点,甚至在必要时启动一台新机器。这让我们不比在每个节点上保存太多的数据副本,从而降低总存储量以节约成本。
你还可以控制副本复制是同步还是异步的,或者两者兼有。这决定了你的集群的一致性、可用性与性能三者。对于同步复制,可以牺牲性能保障一致性和可用性(写操作之后的任意读操作都可以保证得到相同版本的数据,即使是发生失效也会如此)。而最为常见的GigaSpaces的配置是同步副本到被分界点,异步存储到后端存储。
要掌控不断增长的数据,大部分NOSQL实现提供了不停机或完全重新分区的扩展集群的方法。一个已知的处理这个问题的算法称为一致哈希。有很多种不同算法可以实现一致哈希。
一个算法会在节点加入或失效时通知某一分区的邻居。仅有这些节点受到这一变化的影响,而不是整个集群。有一个协议用于掌控需要在原有集群和新节点之间重新分布的数据的变换区间。
另一个(简单很多)的算法使用逻辑分区。在逻辑分区中,分区的数量是固定的,但分区在机器上的分布式动态的。于是,例如有两台机器和1000个逻辑分区,那么每500个逻辑分区会放在一台机器上。当我们加入了第三台机器的时候,就成了每333个分区放在一台机器上了。因为逻辑分区是轻量级的(基于内存中的哈希表),分布这些逻辑分区非常容易。
Map/Reduce是一个经常被用来进行复杂分析的模型,经常会和Hadoop联系在一起。map/reduce常常被看作是并行汇聚查询的一个模式。大部分NOSQL实现并不提供map/reduce的内建支持,需要一个外部的框架来处理这些查询。对于GigaSpaces来说,我们在SQL查询中隐含了对map/reduce的支持,同时也显式地提供了一个称为executors的API来支持map/reduce。在质疑模型中,你可以将代码发送到数据所在地地方,并在该节点上直接运行复杂的查询。
NOSQL实现分为基于文件的方法和内存中的方法。有些实现提供了混合模型,将内存和磁盘结合使用。两类方法的最主要区别在于每GB成本和读写性能。
最近,斯坦福的一项称为“TheCaseforRAMCloud”的调查,对磁盘和内存两种方法给出了一些性能和成本方面的有趣的比较。总体上说,成本也是性能的一个函数。对于较低性能的实现,磁盘方案的成本远低于基于内存的方法,而对于高性能需求的场合,内存方案则更加廉价。
近来我见到的最多的问题就是“NOSQL是不是就是炒作?”或“NOSQL会不会取代现在的数据库?”
“伸缩性优先应用是那些必须具备无限可伸缩性的应用,能够不受限制的扩展比更丰富的功能更加重要。这些应用包括很多需要高可伸缩性的网站,如Facebook,MySpace,Gmail,Yahoo以及Amazon.com。有些站点实际上使用了关系型数据库,而大部分实际上并未使用。这些服务的共性在于可扩展性比功能公众要,他们无法泡在一个单一的RDBMS上。”
总结一下——我认为,现有的SQL数据库可能不会很快淡出历史舞台,但同时它们也不能解决世上的所有问题。NOSQL这个名词现在也变成了NotOnlySQL,这个变化表达了我的观点。
本书不求利,只图学术之便。感谢诸位大牛写了那么多的资料,如果您不愿意被引用,学生会重写相应的章节。
引用网志多篇,由于涵盖太广难以一一校队,特此致歉。
V0.1版本在2010.2.21发布,提供了本书的主题框架v0.2版本在2010.2.24发布,因为一些外界原因,提前发布。完善各个示例,勘误,翻译部分内容。v0.3版本将在3月份或之后发布