工作中总会遇到数据存储相关的bug,在新需求的开发设计中或多或少都会遇到数据模型设计和存储相关的问题。经过几次存储方案设计选择和讨论,发现需要一个更全面的思维框架。日常开发中常用的存储方案,很多都是根据“用途”来选择,根据经验和习惯来选择,但对其详细特征或约束条件的研究却很少。除了手边会用到的收纳方案,你还应该关注市面上更合适的收纳方案。一定的技术预研和储备,有助于日后设计出更好的技术方案。因此,我写了这篇文章,抛出我的观察和思考,希望未来能在公司业务中引入一些更先进(适合)的技术,助力业务发展。存储选型的考虑因素存储选型的目的还是为了我们的使用场景和用户服务,所以我们在选型之前需要先回答一些业务指标和技术指标的问题,这样才能清楚的了解存储选型的应用环境。用户量:预估用户量是多少?几十万还是几亿?数据量:估计的数据量是多少?日均增量能达到多少?读写偏好:数据是读多还是写多?数据场景:强烈的交易或分析需求?运行性能需求:并发量是多少?什么是峰值、平均值和谷值估计值?存储引擎分类和特征数据库分类方法非常多样,由于参考维度不同,差异也很大。以下是一些常见的分类。下面以我们最熟悉的关系型数据库为例。它有很多优点。选择关系型数据库的原因可以简单归纳为:容易理解,可以用二维表结构进行逻辑表达。更容易理解。严格遵循数据格式和长度规范,数据以行为单位,一行数据代表一个实体信息,每行数据的属性相同。事务特性支持ACID特性,可以保持数据之间的一致性,这是使用关系型数据库的一个很重要的原因。操作简单通用的SQL语言使得操作关系型数据库非常方便,支持join等复杂查询。Sql+二维关系是关系型数据库最无法比拟的优势。这种易用性非常贴近开发者。数据稳定性数据持久化到磁盘,不存在数据丢失风险。服务稳定最常用的关系型数据库产品MySql和Oracle服务器,性能优良,服务稳定,通常很少出现异常宕机。然而,在享受关系型数据库带来的便利的同时,我们也不得不面对很多棘手的问题:在高并发下,数据库的瓶颈是显而易见的。数据存储在行中。即使只用一列来计算,整行数据也会从存储设备中读入内存,造成高IO。在写更新频繁的情况下,数据库经常会出现CPU高、Sql执行慢、客户端上报数据库连接池不足等异常情况,性能瓶颈是通过加CPU、换固态硬盘、继续购买服务器和数据库作为分库其他方式的ROI并不高,受限于自身特点,可能需要大量资金,也未必能达到预期的效果。所以,比如在秒杀几万人的场景下,我们是绝对不可能通过数据库直接扣库存的,需要做好流量漏斗。维护数据一致性所付出的代价大数据一致性是关系型数据库的核心,但维护数据一致性的成本也非常高。SQL标准为事务定义了不同的隔离级别。从低到高分别是读未提交、读已提交、可重复性和序列化。事务隔离级别越低,可能引发的并发异常越多,但能提供的并发能力越强。那么,为了保证事务的一致性,数据库需要提供两种技术:并发控制和故障恢复。前者用于减少并发异常,后者可以保证系统异常时事务和数据库状态不被破坏。对于并发控制,核心思想是加锁。无论是乐观锁还是悲观锁,只要提供的隔离级别越高,读写性能就必然受到影响。维护索引的成本很高。为了提供丰富的查询能力,通常热表都会有多个二级索引。一旦有了二级索引,数据的增加必须伴随着所有二级索引的增加和数据的更新。也必然伴随着所有二级索引的更新,这必然会降低关系数据库的读写能力,而且索引越多,读写能力越差。除了数据文件不可避免的占用空间外,索引占用的空间其实也不少。横向扩展带来的各种问题难以处理。随着业务规模的扩大,一种方式是拆分数据库。拆分之后,数据迁移(一个数据库的数据按照一定的规则转移到两个数据库)、跨库join、分布式事务处理都是需要考虑的问题,尤其是分布式事务处理,目前业界还没有有特别好的解决办法。全文搜索功能较弱。比如像“%新年快乐%”只能搜索“新年快乐,我爱你”,不能搜索“新年好开心,我爱你”这样的文字,即不具备分词能力,并且like查询在“%新年快乐”这样的搜索条件下无法命中索引,会大大降低查询效率。表结构不方便扩展,因为数据库存储的是结构化数据,所以表结构schema是固定的,不方便扩展。如果需要修改表结构,需要执行DDL(数据定义语言)语句修改。在修改过程中,表将被锁定。某些服务不可用。如上所述,关系型数据库具有明显的优势,但其劣势也不容忽视。因此,当企业规模不断扩大时,不会一味期望通过增强数据库的能力来解决数据存储问题,而是会引入其他存储。这就是我们所说的NoSql。NoSql的全称是NotOnlySQL,泛指非关系型数据库。取长补短,在合适的场景下选择合适的存储引擎才是正道。下面我们就来看看常用的NoSql及其代表产品,分析一下各个NoSql的优缺点和适用场景,熟悉各个NoSql的特点,方便技术选型。1、KV型NoSql(代表——Redis)KV型NoSql,顾名思义,是以键值对形式存储的非关系型数据库,是最常见的NoSql类型。Redis和MemCache是??其中的代表,Redis是KV型NoSql中应用最为广泛的NoSql。以Redis为例,KV型数据库,最大的优势可以概括为两点:数据基于内存,读写效率高。KV类型数据,时间复杂度为O(1),查询速度快。因此,KV型NoSql最大的优势就是高性能。使用Redis自带的BenchMark作为基准测试,TPS可以达到10万级别,性能很强。同样的Redis也有所有KV类型的NoSql都有的明显缺点:内存有限,无法支持海量数据存储。只能根据K查V,不能根据V查K。查询方式单一,只有KV方式,不支持条件查询。多条件查询的唯一方法就是数据冗余,但这会浪费大量的存储空间。由于KV型NoSql的存储是基于内存的,存在数据丢失的风险(有持久化存储方案)。总结一下,最适合KV型NoSql的场景就是缓存场景:读远多于写。不需要持久化,可以容忍数据丢失。对于那些读的远多于写的数据,引入了一层缓存。每次读取都是从缓存中读取的。如果在缓存中读不出来,就从数据库中取,取完之后再写入缓存,数据准备好故障机制通常不是大问题。一般来说,缓存是性能优化的首选,也是最有效的解决方案。2、搜索型NoSql(代表——ElasticSearch)传统关系型数据库主要利用索引来达到快速查询的目的,但在全文搜索的场景下,索引无能为力,like查询无法满足所有的模糊匹配需求,而useistoolimited大且使用不当很容易造成查询慢的问题。搜索型NoSql是为解决关系型数据库全文搜索能力弱的问题而诞生的。ElasticSearch是搜索型NoSql的代表产品。全文搜索的原理是倒排索引。让我们看看什么是倒排索引。它是从关键字到文档的映射。例如这里有四个短句:“汤姆是汤姆”“汤姆是我的朋友”“谢谢你,贝蒂”“汤姆是贝蒂的丈夫”搜索引擎会按照一定的分词规则将一个句子分成多个关键词,并在关键字维度中维护每个文本中关键字的出现次数。这样,下次搜索关键词“Tom”时,由于“Tom是Tom”、“Tom是我的朋友”、“Tom是Betty的丈夫”这三个句子中都出现了Tom这个词,所以这三个记录将被删除。检索,由于“Tom”在“TomisTom”这句话中出现了两次,所以这条记录与“Tom”这个词的匹配度最高,最先显示。这就是搜索引擎倒排索引的基本原理。假设一个关键词出现在一篇文档中,倒排索引中有两部分:关键词在文档中出现的位置对应的文档ID,我们搜索“BettyTom”这两个词时也是如此。搜索引擎将“BettyTom”分为“Tom”和“Betty”两个词。根据开发者指定的满意率,比如满意率=50%,那么记录中只要出现这两个词之一,就会根据匹配度来检索并展示这条记录。搜索型NoSql以ElasticSearch为例。它的优点是:1)支持分词场景和全文搜索,这是区别于关系型数据库的最大特点。2)写文件时没有丢失数据的风险,在集群环境下可以轻松横向扩展,可以承载PB级数据。3)支持条件查询,支持聚合操作,类似于关系型数据库的GroupBy,但功能更强大,适用于数据分析。4)高可用性,自动发现新的或故障的节点,重组和重新平衡数据,确保数据安全和可访问。同样,ElasticSearch也有明显的缺点:1)性能完全依赖于内存,这也是使用时最重要的一点。它非常消耗内存。64G+SSD基本是大数据量的标配,同样配置翻倍。内存,差不多一个月就要花不少钱。至于ElasticSearch内存,主要用在以下几个地方:IndexingBuffer----ElasticSearch基于Luence。Lucene的倒排索引首先在内存中生成,然后以SegmentFile的形式周期性的刷盘。每个SegmentFile其实就是一个完整的倒排索引。各种缓存----FilterCache、FieldCache、IndexingCache等,用于提高查询分析性能,例如FilterCache用于缓存使用过的Filter的结果集。SegmentMemory----倒排索引就是前面提到的基于关键字的。Lucene4.0之后,所有的关键字都会以FST数据结构的形式全量加载到内存中,以加快查询速度。速度,官方建议至少预留一半的系统内存给Lucene。ClusterStateBuffer----ElasticSearch是为了让每个Node都能响应用户的请求而设计的,所以每个Node的内存中都包含了一份集群状态,大规模集群的状态信息可能会非常庞大??。2)数据结构的灵活性不高。一旦创建了字段,就不能修改类型。如果创建的数据表中某个字段没有全文索引,想添加,那么只能删除整个表,重建。3)读写之间有延时,写入的数据1s左右会被读出(写入数据时需要维护很多索引)。因此,搜索型NoSql最适合的场景是条件搜索,尤其是全文搜索场景。作为关系型数据库的替代方案,搜索型NoSql通常被用作预缓存层来保护关系型数据库。.此外,搜索数据库还有一个非常重要的应用场景。我们可以想一想,一旦数据库分库分表,原来可以在单表中进行的聚合操作和统计操作会不会失效呢?比如我把订单表分成16个数据库1024张表,那么订单数据就分散在1024张表中。我想统计昨天浙江省单笔交易金额最高的订单,怎么办?这是搜索型NoSql的另一大作用。我们可以将分表后的数据统一到搜索型NoSql中,利用搜索型NoSql的搜索和聚合能力来完成全量数据的查询。3.ColumnarNoSql(代表——HBase)ColumnarNoSql与关系型数据库有相同的主键概念。不同之处在于关系数据库根据行组织数据。数据字段即使没有值也会占用空间。列式存储完全是另一种方式。它按列组织数据。好处是查询时只会读取指定的列,不会读取所有列。存储上节省空间,空值不会存储,有时候一列会出现很多重复的数据(尤其是枚举数据,性别,状态等字段),这类数据可以压缩。列数据组织在一起,一次磁盘IO可以一次性将一列数据读入内存。HBase是大数据时代最具代表性的技术之一,是列式NoSQL的产品实现。它的主要优点是:海量数据存储,PB级数据可以随便存储,底层基于HDFS(Hadoop文件系统),数据持久化。读写性能不错。只要没有滥用造成的数据热点,读写基本没有问题。水平扩展是关系数据库和非关系数据库中最方便的扩展之一。只需要增加新的机器就可以实现数据容量的线性增长,而且可以用在便宜的服务器上,节省成本。可以存储结构化或半结构化数据。它没有单点故障和高可用性。列数理论上是无限的。HBase本身只需要columnfamily的个数,推荐1~3个。主要缺点是:HBase是Hadoop生态的一部分,所以是一个比较重的产品,依赖很多Hadoop组件,数据规模没有必要,运维还是有点复杂。不支持分页查询,因为无法统计数据总数。KV式存储,条件查询很弱,HBase在Scan扫描一批数据的时候还是提供了前缀匹配的API,条件查询除非定义多个RowKey实现数据冗余。所以HBase更适合KV类型的存储,未来的数据增长无法预估。另外,HBase的使用还是需要一定的经验,主要体现在RowKey的设计上。4.Document-typeNoSql(representing-MongoDB)文档型NoSql是指一种将半结构化数据存储为文档的NoSql。文档型NoSql通常存储的是JSON或者XML格式的数据,所以文档型NoSql是没有Schema的,因为没有Schema特性,我们可以随意存储和读取数据,所以文档型NoSql的出现就是为了解决关系型数据库表结构扩展不方便的问题。MongoDB是基于文档的NoSql的代表产品,也是所有NoSql产品中的明星产品之一。它的许多概念类似于关系数据库。因此,对于MongDB,我们只需要将其理解为Free-Schema关系型数据库即可。现在,它的优点主要是:没有预定义的字段,容易扩展字段。与关系型数据库相比,读写性能更胜一筹。命中二级索引的查询不会比关系数据库慢,非索引字段的查询是全能赢家。缺点是不支持事务操作。Mongodb4.0虽然号称支持事务,但效果还有待观察。不支持多表关联查询(虽然有嵌入文档的方式),join查询还是需要多次操作。它占用了很多空间。这是MongDB的设计问题。空间预分配机制+删除数据后空间不释放。用db.repairDatabase()修复后才能释放。目前还没有找到像MySql的Navicat等MongoDB的关系型数据库等成熟的运维工具。总而言之,MongDB的使用场景在很大程度上可以类比为关系型数据库,但它更适合处理没有join、没有强一致性要求、表schema变化频繁的数据。通过以上的讨论和分析,我们心中已经有了一个基本的选型框架指导。其实选择数据库只要回答两个核心问题就够了:什么时候选择关系型数据库,什么时候选择非关系型数据库。如果选择非关系数据库,则使用哪个非关系数据库。NoSQL数据库通过牺牲ACID特性来获得更高的性能。假设表数据有很强的事务性需求,这样的数据不适合非关系型数据库。另外,在选择NoSQL数据库时,还需要根据公司的技术栈框架、业务特点、运维成本等因素考虑是否采用。总结关系型数据库和NoSQL数据库的选型,往往需要考虑几个指标:数据量、并发、实时一致性、需求、读写分布、类型安全。运营和维护成本。常见的软件系统数据库选择参考如下:中后台管理型系统——比如操作系统,数据量小,并发量小,首选关系型数据库。对于高流量的系统——比如电商单品页面,可以考虑后端选择关系数据库,前端选择内存数据库。日志系统——原始数据选择列式数据库,日志搜索选择搜索引擎。搜索系统——比如站点搜索,非通用搜索,比如产品搜索,后台考虑选择关系型数据库,前台考虑选择搜索引擎。事务型系统——比如存货、交易、记账,考虑选择关系型数据库+K-V数据库(作为缓存)+分布式事务。离线计算——比如海量数据分析,考虑列式或关系型数据库。实时计算——比如实时监控,可以考虑选择内存数据库或者列式数据库。在设计实践中,应该基于需求和业务驱动的架构。无论选择RDB/NoSQL,都必须以需求为导向,最终的数据存储方案必须是综合设计,各种权衡。
