数据库作为业务的核心,在整个基础软件栈中是非常重要的一环。近几年社区也是新的方案和思想层出不穷,接下来我将总结一下近几年一些主流的开源数据库方案和背后的设计思想以及适用场景。本人才疏学浅如有遗漏或者错误请见谅。本次分享聚焦于数据库既结构化数据存储 OLTP 及 NoSQL 领域,不会涉及 OLAP、对象存储、分布式文件系统。
开源RDBMS与互联网的崛起
很长时间以来,关系型数据库一直是大公司的专利,市场被 Oracle / DB2 等企业数据库牢牢把持。但是随着互联网的崛起、开源社区的发展,上世纪九十年代 mysql 1.0 的发布,标志着关系型数据库的领域社区终于有可选择的方案。
MySQL第一个介绍的单机 RDBMS 就是 MySQL 。相信大多数朋友都已经对 MySQL 非常熟悉,基本上 MySQL 的成长史就是互联网的成长史。我接触的第一个 MySQL 版本是 MySQL 4.0,到后来的 MySQL 5.5 更是经典――基本所有的互联网公司都在使用。MySQL 也普及了「可插拔」引擎这一概念,针对不同的业务场景选用不同的存储引擎是 MySQL tuning 的一个重要的方式。比如对于有事务需求的场景使用 InnoDB;对于并发读取的场景 MyISAM 可能比较合适;但是现在我推荐绝大多数情况还是使用 InnoDB,毕竟 5.6 后已经成为了官方的默认引擎。大多数朋友都基本知道什么场景适用 MySQL(几乎所有需要持久化结构化数据的场景),我就不赘述了。
另外值得一提的是 MySQL 5.6 中引入了多线程复制和 GTID,使得故障恢复和主从的运维变得比较方便。另外,5.7(目前处于 GA 版本) 是 MySQL 的一个重大更新,主要是读写性能和复制性能上有了长足的进步(在5.6版本中实现了SCHEMA级别的并行复制,不过意义不大,倒是MariaDB的多线程并行复制大放异彩,有不少人因为这个特性选择MariaDB。MySQL 5.7 MTS支持两种模式,一种是和5.6一样,另一种则是基于binlog group commit实现的多线程复制,也就是MASTER上同时提交的binlog在SLAVE端也可以同时被apply,实现并行复制)。如果有单机数据库技术选型的朋友,基本上只需要考虑 5.7 或者 MariaDB 就好了,而且 5.6、5.7 由 Oracle 接手后,性能和稳定性上都有了明显的提升。
PostgreSQLPostgreSQL 的历史也非常悠久,其前身是 UCB 的 Ingres ,主持这个项目的 Michael Stronebraker 于 2015 年获得图灵奖。后来项目更名为 Post-Ingres,基于 BSD license 下开源。 1995 年几个 UCB 的学生为 Post-Ingres 开发了 SQL 的接口,正式发布了 PostgreSQL95,随后一步步在开源社区中成长起来。和 MySQL 一样,PostgreSQL 也是一个单机的关系型数据库,但是与 MySQL 方便用户过度扩展的 SQL 文法不一样的是,PostgreSQL 的 SQL 支持非常强大,不管是内置类型、JSON 支持、GIS 类型以及对于复杂查询的支持,PL/SQL 等都比 MySQL 强大得多,而且从代码质量上来看,PostgreSQL 的代码质量是优于 MySQL 的。相对于MySQL 5.7以前的版本,PostgreSQL 的 SQL 优化器比 MySQL 强大很多,几乎所有稍微复杂的查询PostgreSQL 的表现都优于 MySQL。
从近几年的趋势上来看,PostgreSQL 的势头也很强劲,我认为 PostgreSQL 的不足之处在于没有 MySQL 那样强大的社区和群众基础。MySQL 经过那么多年的发展,积累了很多的运维工具和最佳实践,但是 PostgreSQL 作为后起之秀,拥有更优秀的设计和更丰富的功能。PostgreSQL 9 以后的版本也足够稳定,在做新项目技术选型的时候,是一个很好的选择。另外也有很多新的数据库项目是基于 PostgreSQL 源码的基础上进行二次开发,比如 Greenplum 等。
我认为,单机数据库的时代很快就会过去。摩尔定律带来的硬件红利总是有上限的,现代业务的数据规模、流量以及现代的数据科学对于数据库的要求,单机已经很难满足。比如,网卡磁盘 IO 和 CPU 总有瓶颈,线上敏感的业务系统可能还得承担 SPOF(单点故障) 的风险,主从复制模型在主挂掉时到底切还是不切?切了以后数据如何恢复?如果只是出现主从机器网络分区问题呢?甚至是监控环境出现网络分区问题呢?这些都是单机数据库面临的巨大挑战。所以我的观点是,无论单机性能多棒(很多令人乍舌的评测数据都是针对特定场景的优化,另外甚至有些都是本机不走网络,而大多数情况数据库出现的第一个瓶颈其实是网卡和并发连接……),随着互联网的蓬勃发展和移动互联网的出现,数据库系统迎来了第一次分布式的洗礼。
分布式时代:NoSQL的复兴和模型简化的力量
在介绍 NoSQL 之前,我想提两个公司,一个是 Google,另一个是 Amazon。
GoogleGoogle 应该是第一个将分布式存储技术应用到大规模生产环境的公司,同时也是在分布式系统上积累最深的公司,可以说目前工业界的分布式系统的工程实践及思想大都来源于 Google。比如 2003 年的 GFS 开创了分布式文件系统,2006 年的 Bigtable 论文开创了分布式键值系统,直接催生的就是 Hadoop 的生态;至于 2012 年发表论文的 Spanner 和 F1 更是一个指明未来关系型数据库发展方向的里程碑式的项目,这个我们后续会说。
Amazon另一个公司是 Amazon。2007 年发表的 Dynamo的论文 尝试引入了最终一致性的概念, WRN 的模型及向量时钟的应用,同时将一致性 HASH、merkle tree 等当时一些很新潮的技术整合起来,正式标志着 NoSQL 的诞生。 NoSQL 对后来业界的影响非常大,后来的 Cassandra、RiakDB、Voldemort 等数据库都是基于 Dynamo 的设计发展起来的。
新思潮另外这个时期(2006 年前后持续至今)一个比较重要的思潮就是数据库(持久化)和缓存开始有明确的分离――我觉得这个趋势是从 memcached 开始的。随着业务的并发越来越高,对于低延迟的要求也越来越高;另外一个原因是随着内存越来越便宜,基于内存的存储方案渐渐开始普及。当然内存缓存方案也经历了一个从单机到分布式的过程,但是这个过程相比关系型数据库的进化要快得多。这是因为 NoSQL 的另外一个重要的标志――数据模型的变化――大多 NoSQL 都抛弃了关系模型,选择更简单的键值或者文档类型进行存储。数据结构和查询接口都相对简单,没有了 SQL 的包袱,实现的难度会降低很多。另外 NoSQL 的设计几乎都选择牺牲掉复杂 SQL 的支持及 ACID 事务换取弹性扩展能力,也是从当时互联网的实际情况出发:业务模型简单、爆发性增长带来的海量并发及数据总量爆炸、历史包袱小、工程师强悍等等。其中最重要的还是业务模型相对简单。
嵌入式存储引擎在开始介绍具体的开源的完整方案前,我想介绍一下嵌入式存储引擎们。
随着 NoSQL 的发展,不仅仅缓存和持久化存储开始细分,存储引擎也开始分化并走上前台。之前很难想象一个存储引擎独立于数据库直接对外提供服务,就像你不会直接拿着 InnoDB 或者 MyISAM甚至一个 B-tree 出来用一样(当然,bdb 这样鼎鼎大名的除外)。人们基于这些开源的存储引擎进行进一步的封装,比如加上网络协议层、加上复制机制等等,一步步构建出完整的风格各异的 NoSQL 产品。
这里我挑选几个比较著名存储引擎介绍一下。
TC我最早接触的是 Tokyo Cabinet(TC) 。TC 相信很多人也都听说过,TC 是由日本最大的社交网站 Mixi 开发并开源的一个混合 Key-Value 存储引擎,其中包括 HASH Table 和 B+ Tree 的实现。但是这个引擎的一个缺陷是随着数据量的膨胀,性能的下降会非常明显,而且现在也基本不怎么维护了,所以入坑请慎重。与 TC 配合使用的 Tokyo Tyrant(TT) 是一个网络库,为 TC 提供网络的接口使其变成一个数据库服务,TT + TC 应该是比较早的 NoSQL 的一个尝试。
LevelDB在 2011 年,Google 开源了 Bigtable 的底层存储引擎: LevelDB 。LevelDB 是一个使用 C++ 开发的嵌入式的 Key-Value 存储引擎,数据结构采用了 LSM-Tree,具体 LSM-Tree 的算法分析可以很容易在网上搜索到,我就不赘述了。其特点是,对于写入极其友好,LSM 的设计避免了大量的随机写入;对于特定的读也能达到不错的性能(热数据在内存中);另外 LSM-Tree 和 B-tree 一样是支持有序 Scan 的;而且 LevelDB 是出自 Jeff Dean 之手,他的事迹做分布式系统的朋友一定都知道,不知道的可以去 Google 搜一下。
LevelDB 拥有极好的写性能,线程安全,Batch Write 和 Snapshot 等特性,使其很容易的在上层构建 MVCC 系统或者事务模型,这对数据库来说非常重要。另外值得一说的是,Facebook 维护了一个活跃的 LevelDB 的分支,名为 RocksDB。RocksDB 在 LevelDB 上做了很多的改进,比如多线程 Compactor、分层自定义压缩、多 MemTable 等。另外 RocksDB 对外暴露了很多 Configuration,可以根据不同业务的形态进行调优;同时 Facebook 在内部正在用 RocksDB 来实现一个全新的 MySQL 存储引擎:MyRocks,值得关注。RocksDB 的社区响应速度很快也很友好,实际上 PingCAP 也是 RocksDB 的社区贡献者。我建议新的项目如果在 LevelDB 和 RocksDB 之间纠结的话,请果断选择 RocksDB。
B-tree 家族当然,除了 LSM-Tree 外, B-tree 的家族也还是有很多不错的引擎。首先大多数传统的单机数据库的存储引擎都选择了 B+Tree ,B+Tree 对磁盘的读比较友好,第三方存储引擎比较著名的纯 B+Tree 实现是 LMDB 。首先 LMDB 选择在内存映像文件 (mmap) 实现 B+Tree,而且使用了 Copy-On-Write 实现了 MVCC 实现并发事务无锁读的能力,对于高并发读的场景比较友好;同时因为使用的是 mmap 所以拥有跨进程读取的能力。不过我并没有在生产环境中使用过 LMDB ,所以并不能给出 LMDB 的一些缺陷,见谅。
混合引擎还有一部分的存储引擎选择了多种引擎混合,比如最著名的应该是 WiredTiger ,大概是2014年被 MongoDB 收购,现在成为了 MongoDB 的默认存储引擎。WiredTiger 内部有 LSM-Tree 和 B-tree 两种实现,对外提供相同的接口,根据业务的情况可自由选择。另外一些特殊数据结构的存储引擎在某些特殊场合下非常抢眼,比如极高压缩比 TokuDB ,采用了名为分形树的数据结构,在维持一个可接受的读写压力的情况下,能拥有 10 倍以上的压缩率。
NoSQL说完了几个比较著名的存储引擎,我们来讲讲比较著名的 NoSQL。在我的定义中,NoSQL 是Not Only SQL 的缩写,所以可能包含的范围有内存数据库,持久化数据库等,总之就是和单机的关系型数据库不一样的结构化数据存储系统。
我们先从缓存开始。
memcached前面提到了 memcached 应该是第一个大规模在业界使用的缓存数据库,memcached 的实现极其简单,相当于将内存用作大的 HASH Table,只能在上面进行 get/set/ 计数器等操作,在此之上用 libevent 封装了一层网络层和文本协议(也有简单的二进制协议),虽然支持一些 CAS 的操作,但是总体上来看,还是非常简单的。但是 memcached 的内存利用率并不太高,这是因为 memcached 为了避免频繁申请内存导致的内存碎片的问题,采用了自己实现的 slab allocator 的方式。即内存的分配都是一块一块的,最终存储在固定长度的 chunk 上,内存最小的分配单元是 chunk,另外 libevent 的性能也并没有优化到极致。但是这些缺点并不妨碍 memcached 成为当时的开源缓存事实标准。另外,八卦一下,memcached 的作者 Brad Fitzpatrick 现在在 Google,大家如果用 Golang 的话,Go 的官方 HTTP 包就是这哥们写的,是个很高产的工程师。
Redis如果我没记错的话,在 2009 年前后,一位意大利的工程师 Antirez ,开源了 Redis 。从此彻底颠覆了缓存的市场,到现在大多数缓存的业务都已用上 Redis,memcached 基本退出了历史舞台。Redis 最大的特点是拥有丰富的数据结构支持,不仅仅是简单的 Key-Value,还包括队列、集合、Sorted Set 等等,提供了非常丰富的表达力,而且 Redis 还提供 sub/pub 等超出数据库范畴的便捷功能,使得几乎一夜之间大家纷纷投入 Redis 的怀抱。
Twemproxy但是随着 Redis 渐渐的普及,而且越用越狠,另外内存也越来越便宜,人们开始寻求扩展单机 Redis 的方案,最早的尝试是 twitter 开源的 twemproxy 。twemproxy 是一个 Redis 中间件,基本只有最简单的数据路由功能,并没有动态的伸缩能力,但是还是受到了很多公司的追捧,因为确实没有其他替代方案。随后的 Redis Cluster 也是难产了好久,时隔好几年,中间出了 7 个RC 版本,最后才发布;2014 年底,我们开源了 Codis ,解决了 Redis 中间件的数据弹性伸缩问题,目前广泛应用于国内各大互联网公司中,这个在网上也有很多文章介绍,我也就不展开了。所以在缓存上面,开源社区现在倒是非常统一,就是 Redis 及其周边的扩展方案。
MongoDB在 NoSQL 的大家庭中, MongoDB 其实是一个异类,大多 NoSQL 舍弃掉 SQL 是为了追求更极致的性能和可扩展能力,而 MongoDB 主动选择了文档作为对外的接口,非常像 JSON 的格式。Schema-less 的特性对于很多轻量级业务和快速变更的互联网业务意义很大,而且 MongoDB 的易用性很好,基本做到了开箱即用,开发者不需要费心研究数据的表结构,只需要往里存就好了,这确实笼络了一大批开发者。
尽管 MongoDB 早期的版本各种不稳定,性能也不太好(早期的 Mongo 并没有存储引擎,直接使用了 mmap 文件),集群模式还全是问题(比如至今还未解决的 Cluster 同步带宽占用过多的问题),但是因为确实太方便了,在早期的项目快速迭代中,Mongo 是一个不错的选择。但是这也正是它的问题,我不止一次听到当项目变得庞大或者「严肃」的时候,团队最后还是回归了关系型数据库。Anyway,在 2014 年底 MongoDB 收购了 WiredTiger 后,在 2.8 版本中正式亮相,同时 3.0 版本后更是作为默认存储引擎提供,性能和稳定性有了非常大的提升。
但是,从另一方面讲,Schema-less 到底对软件工程是好事还是坏事这个问题还是有待商榷。我个人是站在 Schema 这边的,不过在一些小项目或者需要快速开发的项目中使用 Mongo 确实能提升很多的开发效率,这是毋庸置疑的。
HBase说到 NoSQL 不得不提的是 HBase ,HBase 作为 Hadoop 旗下的重要产品, Google Bigtable 的正统开源实现,是不是有一种钦定的感觉 :)。 Bigtable 是 Google 内部广泛使用的分布式数据库,接口也不是简单的Key-Value,按照论文的说法叫:multi-dimensional sorted map,也就是 Value 是按照列划分的。Bigtable 构建在 GFS 之上,弥补了分布式文件系统对于海量、小的、结构化数据的插入、更新以及随机读请求的缺陷。
HBase 就是这么一个系统的实现,底层依赖 HDFS。HBase 本身并不实际存储数据,持久化的日志和 SST file (HBase 也是 LSM-Tree 的结构) 直接存储在 HDFS 上,Region Server (RS) 维护了 MemTable 以提供快速的查询,写入都是写日志,后台进行 Compact,避免了直接随机读写 HDFS。数据通过 Region 在逻辑上进行分割,负载均衡通过调节各个 Region Server 负责的 Region 区间实现。当某 Region 太大时,这个 Region