存储中间件
数据库
关系型数据库(Relational Database)
以 MySQL、Oracle、SQLSever 为代表。
这类数据库的特点是强 schema,每个项目(column)有明确的数据类型。从业务状态的角度看,可以把一个表(table)理解为一个结构体,当遇到结构体里面套结构体,那么就定义一个子表,用id来关联。
文档型数据库(Document Database)
以 MongoDB 为代表。是无 schema 的。无 schema 的含义是无需定义表字段(即MongoDB中的collection),且一个collection可以存储不同的document,同一个 collection 下不同的记录可以存储完全不同的字段。
这个特性使得业务功能可以更快的实现,但也导致了一个缺点:有过多的冗余数据。关系型数据库按照第三范式建表可以减少数据的冗余,而 MongoDB 无 schema 无建表范式,这样会导致修改数据的时候要对应修改多条记录。
键值存储(KV Storage)
以 Cassandra 为代表。键值存储可以认为是数据库的特例。数据库往往是允许设定多个索引字段的,而键值存储明确只有唯一索引。
事务(Transaction)
事务是把由多个基础操作组合而成的复杂操作包装成一个原子操作。事务的特性简称为 ACID。
主从结构
当数据存在多个副本时,就有数据一致性的问题,因为不同副本的数据可能值不一样。
解决这个问题的方法之一是采用主从(Master-Slave)结构。主从结构采用的是一主多从模式,所有写操作都发往主(Master),所有从(Slave)都从主这边同步数据修改的操作。
这样,从(Slave)的数据版本只可能因为同步还没有完成,导致版本会比较旧,而不会出现比主(Master)还新的情况。
从(Slave)可以帮主(Master)分担一定的读压力。但是不是所有的读操作都可以被分担。大部分场景的读操作必须要读到最新的数据,否则就可能会出现逻辑错乱。只有那些纯粹用于界面呈现用途,而不是用于逻辑计算的场景,非敏感场景(比如财务场景是敏感场景)下能够接受读的旧版本数据,可以从从节点读。
从(Slave)最重要的是和主(Master)形成了互备关系。在主挂掉的时候,某个从节点可以替代成为新的主节点。这会发生一次选举行为,系统中超过一半的节点需要同意某个节点成为主,那么选举就会通过。
选择谁成为新的主是有讲究的,因为从的数据有可能不是最新的。一旦选择了没有最新数据的从作为新的主节点,就意味着版本回退,也就意味着发生了数据丢失。
这是不能接受的事情。为了避免版本回退,写操作应该确保至少有一个从节点收到了最新的数据。这样在主挂掉后才可以确保能够选到一个拥有最新数据的节点成为新的主节点。
数据库选型
必须要求数据强一致性,有严格 ACID 要求的场景,用关系型数据库。
由于文档型数据库是无 schema 的,可以轻松支持字段变更,支持变化的数据,这是一个重大的优势,因此适用于原型快速验证,敏捷开发,MongoDB 还有原生设计的高可用优势(复制集),高水平扩展能力(轻松添加sharding分片扩展),适合处理海量数据的场景。但 MongoDB 不支持复杂事务和 join,所以要求不能有过多需要关联 collection 查询的场景。
一个复杂系统的构建,通常需要同时使用多种数据库,相辅相成。比如物流场景,用 MongoDB 存储订单信息,订单状态在运送过程中会不断变更,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来,查询效率更高;但支付的时候有严格的 ACID 要求,因此在支付功能的实现上又需要用关系型数据库。
非结构化数据的存储
文件系统
比较典型的非结构化数据,如图片、音视频、Office 文档等多媒体文件一般把它们放在文件系统里。但是单机时代诞生的文件系统,真的是最适合存储这些多媒体数据的吗?
不,文件系统需要改变,因为:
第一,伸缩性问题。单机文件系统的第一个问题是单机容量有限,在存储规模超过一台机器可管理的时候,应该怎么办的问题。
第二,性能瓶颈。单机文件系统通常在文件数目达到临界点后,性能快速下降。在 10TB 的大容量磁盘越来越普及的今天,这个临界点相当容易到达。
第三,可靠性,更严谨来说是持久性(Durability)问题。单机文件系统通常只是单副本的方案。但是,今天单副本的存储早已经无法满足业务的持久性要求。数据需要有冗余(比较经典的做法是 3 副本),以便在磁盘损坏时及早修复丢失的数据,以避免所有的副本损坏造成数据丢失。
第四,可用性要求。单机文件系统通常只是单副本的方案,在该机器宕机后,数据就不可读取,也不可写入。
Google GFS 是第一份分布式存储的论文,这篇论文奠定了 3 副本在分布式存储系统里的地位。随后 Hadoop 参考此论文实现了开源版的 GFS —— HDFS。
对象存储 (Object Storage)
非结构化数据的存储方式,不是分布式文件系统,而是键值存储(KV Storage)。用于存储非结构化数据的键值存储,有一个特殊的名字,叫对象存储。但它和结构化数据的键值存储,实现机制上往往有极大的差异。
对象存储的 Key,看起来也像一个文件系统的路径(Path),但仅仅是像而已。对于对象存储来说,Key 中出现的 “/” 字符,只是一个普通字符。
在对象存储中,并不存在目录(Directory)这样的概念。
既然对象存储是一个键值存储,就意味着我们可以通过对 Key 做 Hash,或者对 Key 按 Key Range 做分区,都能够让请求快速定位到特定某一台存储机器上,从而转化为单机问题。
缓存(Cache)
缓存是存储(Storage)的加速器。最常见的是用更高速的硬件来加速。比如,用 SSD 缓存加速 SATA 存储,用内存缓存加速基于外存的存储。
memcached
这个示例采用的是简单 Hash 分片的方法,但是一旦要对 memcached 集群扩容,取memcached
实例的操作:i := hash % countOf(memcaches)
,i
值将发生变化,这样就取不到之前缓存下来的数据了,从而导致大量的缓存未命中(Cache Miss)。
缓存雪崩
雪崩怎么形成?首先是部分缓存实例宕机,导致缓存命中率(Cache Hit Rate)下降,大量的请求落到后端存储上,导致后端存储过载,也出现宕机。
这时就会出现连锁反应,形成雪崩现象。后端存储就算重新启动起来,又会继续被巨大的用户请求压垮,整个系统怎么启动也启动不了。
应该怎么应对雪崩?最简单的办法,是后端存储自己要有过载保护能力。一旦并发的请求超过预期,就要丢弃部分请求,以减少压力。
存储与缓存数据一致性问题
考虑如下场景:
缓存刚好过期失效,读和写2个请求同时打进来
写操作还没执行完,读操作查询数据库已经得到了一个旧值
写操作将新值写入数据库
读操作把旧值写到了Redis缓存
那么如何解决呢?方案一.延迟双删。方案二.同步数据库binlog到缓存中间件(最推荐本方案)。下面是第三种方案.groupcache,不过它的使用场景非常的局限。
groupcache
groupcache并不运行在单独的server上,而是作为library和app运行在同一进程中。
为什么需要引入 group 的概念?
在同一个缓存集群,可能会需要缓存多个复杂操作,比如 F(x)、G(x)。如果没有 group,那么就不能只是记录 x => y 这样的键值对,而是要记录 F#x => y,G#x => y 这样的键值对。中间的 # 只是一个分隔符,换其他的也可以。看起来好像也还可以?
其实不然,因为 F(x)、G(x) 在同一个内存缓存集群就意味着当内存占满时,会触发缓存淘汰算法,相互淘汰对方。这里的淘汰规则不是我们能够控制的,很难保证结果符合我们的预期。
那么有 group 会变成什么样?首先可以创建 F、G 两个独立的 group,每个 group 可以设定独立的内存占用上限(cacheBytes)。
这样,每个 group 就只淘汰自己这个 group 内的数据,相当于有多个逻辑上独立的内存缓存集群。
另外,在 group 中只需要记录 x => y 这样的键值对,不再需要用 F#x、G#x 这种手工连接字符串的方式来模拟出名字空间。
groupcache 是如何解决缓存一致性问题的?
groupcache值是不可修改的,那么自然就没有更新缓存导致的缓存一致性问题。
使用场景
因为groupcache只能get,不能update和delete,也不能设置过期时间,所以它不适用非静态资源的场景,而适用于静态资源缓存,比如:图片。
Redis
Redis 的确可以当作缓存来用,可以设置内存上限,当内存使用达到上限后,Redis 就会执行缓存淘汰算法。只不过,如果我们把它当作内存缓存,那么其实它只需要是一个简单的键值存储(KV Storage)就行。
但是 Redis 实际上是 key => document,它的值可以是各类数据结构,比如:字符串,哈希表,列表,集合,有序集合(支持 Range 查询),等等。
不仅如此,Redis 还支持执行 Lua 脚本来做存储过程。
这些都让 Redis 看起来更像一个数据库类的存储中间件。
Redis存储的问题
当我们把 Redis 看作存储,它的持久化策略对于一个服务端的存储系统来说是不合格的。因为如果发生宕机,上一次持久化之后的新数据就丢了。
能用Redis做消息队列吗?
要求消息传达精准一次性的场景是不能。
用 Redis list 类型的 lpush 和 rpop 来充当消息队列时,由于 Redis 没有 ACK 机制,在网络抖动需要重试时很可能就往 list 中添加了多个相同的元素进去。
Redis的 pub/sub 适用于消息的广播,不过也没有 ACK 机制。
Redis的优势
Redis数据都存储在内存中,性能远超数据库
支持多种数据类型
单线程模型,原子性操作
消息队列(Message Queue)
大数据
在稍微大一点的互联网企业,需要计算处理的数据量常常以 PB 计(1015 Byte),一个程序所能调度的网络带宽(通常数百 MB)、内存容量(通常几十 GB )、磁盘大小(通常数 TB)、CPU 运算速度是不可能满足这种计算要求的。
那么如何解决 PB 级数据进行计算的问题呢?
跟大型网站的分布式架构思路是一样的,采用分布式集群的解决方案。
移动计算比移动数据更划算
既然数据是庞大的,而程序要比数据小得多,将数据输入给程序是不划算的,那么就反其道而行之,将程序分发到数据所在的地方进行计算。
发展历程
Google 在 2004 年前后发表的三篇论文,也就是我们经常听到的“三驾马车”,分别是分布式文件系统 GFS、大数据分布式计算框架 MapReduce 和 NoSQL 数据库系统 BigTable。
对前2篇论文的具体实现就成了Hadoop,其中分布式文件系统 GFS 就变成了 HDFS。
随后觉得用 MapReduce 进行大数据编程太麻烦了,于是 Facebook 又发布了 Hive。Hive 支持使用 SQL 语法来进行大数据计算,比如说你可以写个 Select 语句进行数据查询,然后 Hive 会把 SQL 语句转化成 MapReduce 的计算程序。Hive 出现后极大程度地降低了 Hadoop 的使用难度,迅速得到开发者和企业的追捧。
随后,UC 伯克利 AMP 实验室(Algorithms、Machine 和 People 的缩写)开发的 Spark 开始崭露头角。当时 AMP 实验室的马铁博士发现使用 MapReduce 进行机器学习计算的时候性能非常差,因为机器学习算法通常需要进行很多次的迭代计算,而 MapReduce 每执行一次 Map 和 Reduce 计算都需要重新启动一次作业,带来大量的无谓消耗。
一般说来,像 MapReduce、Spark 这类计算框架处理的业务场景都被称作批处理计算/大数据离线计算。
而在大数据领域,还有大数据流计算/大数据实时计算的场景,有 Storm、Flink、Spark Streaming 等流计算框架来满足此类大数据应用的场景。
除了大数据批处理和流处理,HBase 是从 Hadoop 中分离出来的、基于 HDFS 的 NoSQL 系统。
应用场景
大数据处理的主要应用场景包括数据分析与机器学习。
数据分析主要使用 Hive、Spark SQL 等 SQL 引擎完成;机器学习则有专门的机器学习框架 TensorFlow、PyTorch等。
Last updated
Was this helpful?