- CPU:首先,区分 多核处理器 与 多处理器,简单理解多处理器对应多CPU,多核处理器(core)是一个处理器(CPU)上集成多个核心,每个核心(core)是真正运行线程的物理单位,即一个CPU上有多个core,可以多线程并发执行。其次,了解多处理器的架构,主要包括CPU、core、L1Cache、L2Cache、L3Cache、内存、总线组成,其中L1Cache分为L1iCache(指令缓存)、L1dCache(数据缓存)。一个CPU上有多个core,每个core独享L1Cache、同一个CPU的多个core共享L2Cache、L3Cache、多个CPU共享内存、总线。最后,理解SMP架构,目前业界大多数使用对称架构(SMP架构),特点是CPU间是对等关系、资源共享,没有主次或从属关系,这种架构带来的问题就竞争,竞争内存、总线等共享资源。SMP架构中资源竞争导致扩展性较差,折衷方案NUMA(非一直存储访问),每个SMP架构(可以是多个CPU)有自己的内存、总线,通过NUMA互联互通模块访问其他SMP架构。
- IO总线:总线架构的思路——分级。内存、高端SSD连接到北桥芯片,通过总线与CPU相连。网卡、磁盘、低端SSD连接到南桥芯片通过DMI总线(1GB/s带宽)连接到北桥芯片。
- 网络:两种架构。第一种,思科主张的分层架构,问题是同一个交换机内带宽有保障,跨交换机访问带宽较低,在系统设计阶段需要考虑跨交换机的数据拷贝。第二种,Google主张的扁平化架构,可以解决上述问题,但成本较高,需要更多的交换机。
- 性能参数:
L1 Cache | 0.5ns | 分支预测 | 5ns | L2 Cache | 7ns | Mutex加锁、解锁 | 100ns | 内存访问 | 100ns | 内存顺序读1MBS数据 | 250ns | SATA寻道时间 | 10ms | SATA顺序读1MB数据 | 20ms | SSD访问 | 100-200ns | 千兆网卡发送1MB数据 | 10ms | 注:除此以外还需要知道,同机房访问耗时、同地域跨机房访问耗时、跨地域访问耗时。在了解耗时量级的基础上,理解耗时主要消耗在那个环节,例如访问磁盘的主要耗时在磁盘IO(寻道和读取--受物理影响)、跨机器访问的主要耗时在网络。 - 存储层次架构
对外接口角度看各单机存储引擎
分布式系统对外接口大致分为:增、伤、改、查,查找即读取,读又分为顺序读和随机读。每种数据引擎尤其自身的特点导致对上述操作的支持程度是不一样的。Hash存储引擎,不支持顺序读(即扫描)。B树存储引擎,支持顺序读,但是批量写的性能较差。LSM存储引擎,不仅支持扫描,对写进行了优化,批量追加写的性能也很好,但是随机读的性能没那么好。
Hash存储引擎
1. 数据结构:内存中存储数据的索引,真实的数据存储在文件中。
2. 定期合并:更新、删除操作都是追加写,用新数据的索引覆盖旧数据的索引,需要立即回收机制保证旧数据及时删除。
3. 快速恢复:服务器断电、服务重启等操作仅仅靠垃圾回收使数据紧凑从而保证启动速度是不够的。需要ckpt机制dump内存索引到文件中。
注:一套完整的Hash存储引擎必须具备合并 和 快速恢复的机制,否则重启过程十分缓慢可以达到小时甚至天级别。在设计阶段如果没有完备的考虑到这两点,后续优化十分困难,因为需要新增存储引擎的命令(即语义),考虑到分批升级(保证可用性),新增命令需要兼容两个版本,大大的增加了复杂度。即使能够升级,代码层面也很难做到优雅。
B树存储引擎:
B树主要用于MySQl数据库(实际是B+树,B树的一种,为与标题对应后续写为B树),例如InnoDB存储引擎-聚集索引,InnoDB对B树有很多优化,其中最主要的是B树节点对应一个物理页,在B树中叶子节点存储真实的数据(很好地支持了扫描),非叶子节点存储索引,页面的换入换出极大的影响了引擎的性能,一般用LRU、LIRS(类似Q2缓存防止扫描时对缓存数据污染)。
另外,需要了解B树的复杂度,O(h)=O(logd^N),h为B树高度,d为出度,N为元素个数。
LSM树存储引擎
1. 数据结构
内存中双Memtable,用于转储时切换。磁盘上包括SST文件、Manifest文件、Current文件。
2. 存储模型
写入操作追加写到MemTbale,达到上限后Dump 到Level 0的SST文件中。SST文件分层管理,Level 0 是内存MemTable直接Dump出来的,内部是乱序的。其它层的SST都是根据key排序,SST文件名、对应层次、最大、最小key等元信息都会记录在Manifest文件中。当有SST文件名的变化时(新文件加入、旧文件删除),Manifest文件的变化是通过生成新文件实现的,在新旧文件的变化阶段,通过Current文件记录当前那个Manifest文件是可用的。
3. 合并
当某一层的SST文件达到配置的数量时,进行合并,上层的SST文件与涉及到的所有下层文件进行多路合并形成新的SST文件。
4. 优化
RocksDB主要从两个方面对LevelDB进行了优化,第一多线程合并提高合并速度,第二支持更多种数据压缩方式(SST文件)。
注:LSM通过追加写方式优化了写性能,自然读操作尤其是随机读的性能就会比较差(由于SST内key是有序存储,扫描性能还可以)。在实际的应用中,如果追加写过快(SSD追加写到1w qps,数据仅供参考,其他参数这里没介绍比如value大小、CPU情况等)合并不及时可能会堵塞写。
数据模型是针对用户来说系统支持哪种数据模型,主要包括三种:文件数据模型,关系型数据模型、NoSQL数据模型。
文件数据模型:遵循统一的API接口,但是考虑到跨机器的性能,一般不会完全遵守。对象系统是文件系统系统的一种简化,她弱化了目录的概念,不支持随机写,对于一个对象要么删除,要么作为新的数据重新写入。例如,S3只支持一级目录,TFS不支持目录
关系型数据模型:最符合用户习惯的数据模型,业界有统一的接口标准,并且生态链完整。但是集群环境下,性能、扩展性很差。与其他数据模型相比支持索引和事务。
NoSQL数据模型:表格系统属于常见的NoSQL系统,支持no-schema模型,不同的行可以包含不同的列。对于事务支持较差,大多是同表或者同行的事务。基本不支持跨表操作,例如级联等,也不支持二级索引。
MySQL VS. NoSQL
MySQL在海量数据下的问题:事务、联表、单机性能。
NoSQL面临的问题:缺少统一的标准、使用以及运维相对复杂。
注:MySQL 与 NoSQL并不存在孰好孰不好的问题,两者都在不断的发展,甚至在不断的融合(NewSQL)。
事务与并发控制的关系:事务是一组SQL语句,需要满足ACID,其中隔离级别尤为重要。并发控制是事务实现的手段,主要包括锁、写时复制、多版本并发控制(MVCC)等方式。
隔离级别
读取未提交数据(RU):最低级别的隔离,一个事务读到另一个未提交的事务的修改。
读取已提交数据(RC):一个事务读到了另一个已提交的事务,如果一个事务的两次读在另一个事务提交的前后,读的数据不一致。
可重复读(RR):一个事务的两次读,数据是一致的。
可序列化(S):两个事务看似是穿行执行的。
引入的问题
第一类丢失更新(LU):一个事务回滚,导致另一个事务的更新丢失。
脏读(DR):一个事务读到另一个未提交事务的数据。
不可重复读(NRR):一个事务先后两次读到的数据不一致。
第二类丢失更新(SLU):两个事务同时读取数据,后面的修改覆盖前面的修改。
幻读(PR):一个事务的两次读取,发现有新增的数据出现。
隔离级别与异常对应关系
RU不能解决DR、NRR、SLU、PR。
RC不能解决NRR、SLU、PR。
RR不能解决PR。
S可以解决全部。
数据库锁
本质上是读写锁,写锁堵塞读锁,读锁之间不堵塞。锁的级别可以是表、行、数据块等。
锁的问题是可能出现死锁,解决办法有两个,第一是设置超时,第二个是死锁检测(是否存在依赖回路)。
写时复制
在B+树中,所有的写都需要复制从跟节点到叶子节点路径上的所有节点,期间的读都读旧数据(所有有的读不加锁),更新节点指针是加锁的。
问题是写成本较大,需要复制很多额外数据,且写并发度受限。
多版本控制(MVCC)
数据加上一个版本号,每个事物都能读到正确的版本数据,大大的提升了并发度。
问题是需要额外的空间和垃圾回收机制。
回滚日志:记录操作之前的数据状态,用于回滚。
重做日志:记录操作会后的数据状态,用于重做。
优化手段:批量提交 + ckpt。
数据压缩是一个比较大的课题,在大部分的系统设计中,压缩是上游业务的任务。在这里着重说一下列式存储,表格系统大部分采用列式存储。
列式存储,比较适合稀疏矩阵,且大部分操作只涉及到某些列,并不需要整行的读取。比较适合OLAP类业务。在BigTable中还引入了列组的概念,即规定某些列存在一个节点,在一定程度上支持了OLTP业务。由于列式存储的数据有很大的相似度,大大的提高了压缩比,可以很好地节省空间,据说BigTable的压缩大可以达到15。
本章以单机的视角介绍了分布式存储涉及到的技术点。硬件基础部分,
需要着重对性能参数由概念、有意识、在实际开发和维护中需要掌握,
CPU、总线架构需要了解,在后续的开发和学习中需要逐步细化补充,硬件架构的理解对于软件实现至关重要(CPU、总线架构可以很好地帮助理解多线程等)。单机存储引擎部分,
需要着重了解各引擎的优缺点以及完备的解决方案,在注释中重点介绍了实际开发中遇到的一些坑。数据模型部分,
需要站在用户的视角下对分布式存储系统进行分类,很多公司的组织架构划分都是根据这部分知识点。事务与并发控制、故障恢复是存储系统的核心,重要且基础。
|