实在不知道起什么名字,打算写三个系列的文章,由于系统尽可能有状态和无状态分离,第一个想写下系统中涉及到数据存储/分析/选型/本身设计要怎么作,包含内存/持久,数据量单机到大量到海量分布式的存储和计算。第二个想写无状态系统如何作到高处理能力,涉及到网络/分流/通讯,进程/线程模型,操做系统IO,CPU调度等等。第三个为C++系列基础编码。
本篇为第一部分,先说下非海量数据存储选型应该考虑的点,本身设计也能够参考的点,对比了经常使用的非海量数据存储软件,这部分对数据模型,索引/存储,分布式的可靠性,可扩展,一致性给出总结性的解决方案。还介绍了海量数据(可能通常核心数据没这么多,但衍生数据量大)和流数据的存储和计算的总结性解决方案,这其中涉及众多组件,我只用过少部分,为了直观性,给出了几种常见衍生数据处理系统的应用设计。
系列文章涵盖了文中提到或未提到但常见的内容都分别单独篇章。node
存储应该考虑的点: ——性能(存储介质,数据格式,数据组织,索引,cache) ——功能(索引,事务) ——一致性 ——可靠性 ——扩展性(对于scale up共享内存和磁盘部分忽略) ——成本(物理,维护) 其中成本是须要再各个指标中关联的,好比性能里介质压缩大小,内存占用,可靠性才会冗余备份等,不单独讨论了。
影响性能的:存储介质,数据模型,索引,存储格式。mysql
存储介质 | 特色 | 数据组织 |
---|---|---|
磁带 | ||
内存 | map,set,list,skip-list,memory-table,stm(支持内存事务) | |
磁盘 | 顺序读写强,随机读写差,block | -Tress =>B+ 层数同样,性能稳定,中间节点只有索引,容易缓存,数据只在子节点,数据能够扫描 |
SSD | 随机性能高,并行度高,擦除影响寿命 | SST(sst为什么适合SSD?SSD是并发写,适合一次写入大量,擦除影响寿命,但愿更新少,由于并发随机读写能力更强) |
PCM |
散列表必需要内存中放得下
,不然须要大量随机访问,不支持范围查询2.内存放不下,hash索引=》稀疏索引+有序
SSTables:sorting string table 要求每一个键只在每一个段中出现一次且排序能够超出内存(随加载随merge),不须要保存全部键的索引(排序),范围查找
如何让数据有序:磁盘上B树,内存中更容易:红黑树/AVL树
LSM,日志合并,保存一系列在后台合并的SSTable,写入时,添加到内存的红黑树中。做为SSTable写入磁盘。关键在于会如何压缩和合并
LevelDB 水平,HBase 大小分层
3.从日志追加到就地更新=》B树
写操做用新数据覆盖磁盘页面,并发控制复杂。要等一整页一块儿写,须要用额外的磁盘(redo log)[二次写,若不这样作须要控制块对其和大小写入512一次或标识头部等,实际上由于内存延迟刷新,WAL只要有内存延迟都配了]。LSM只是追加
一些优化:修改页面写入不一样位置,父页面建立新版本(mongodb)。B+树等
比较B树与LSM
B 读取速度快。对于两个逻辑相近的页物理可能很远,随机读写多
LSM 写入速度快,非页单位写入量大,都是顺序写。碎片少。LSM因为压缩更小。SSTbales在合并时复写(有时应为磁盘写入带宽等有限,有影响,压缩和写入速率控制)。
4.其余索引:多列索引(B树和LSM都不能很好的支持);空间,二维填充曲线转为单个数字再用B树索引,或者用特殊化的空间索引R树;全文索引/模糊索引,lucene
5.内存中存储一切:性能+实现特殊的磁盘和索引难以实现的数据模型好比队列。=》反缓存web
功能各个系统本身针对不一样会有很大差异(排序,聚合,搜索等),通用的功能只有事务,这里作下讨论,其余特性能够查API,功能和使用通常文档都比较好查,不仔细说了redis
单独说可靠性指异常补偿,涉及到复制,故障发现,故障自动转移。比较简单。有些副本提供读服务还有多写副原本提高可用性,有时存储的可靠性和可用性能够一块儿解决,简单说下这种部署和延时问题,详细的一致性在下面说。算法
多活多数据中心与单活对比:单活每一个写入都要进入主库数据中心,增长写入时间;经过网络中心的写是同步的,对网络容忍度比多中心异步复制低不少;故障一个主从切换,一个换主。
处理写入冲突:写入检测两个主违背多主目的,写入都成功后同步时检测冲突用户没法补救。
并发写入:哪些须要覆盖(好比依赖关系的B知道A先发生,只须要覆盖便可),哪些是并发(B不知道A的发生,并发须要按版本解决或者容许丢失用时间或序号。)
=》
预防冲突:同一写入只入一个,在故障等时特殊处理(须要切换,保证不了同一个请求不丢失或不重复)。
冲突合并:每一个写入或副本一个惟一ID,a.覆盖丢弃,b.维护多版本,自动处理的数据类型:集合等,带多版本等。
b.版本向量:返回时将读取自的版本返回,再次写入时带该版本的就是依赖(原本知道有这个版本),可覆;不然是并发保留保本(原本不知道有这个版本)。sql
3.无主:所有多读多写,返回数量足够成功。(读写法定人数的确认)
单个落后如何恢复:读取时选择版本高的反更新(读修复)或 异步不断同步差别(反熵,不保证顺序)
写入冲突和多主同样mongodb
困难:丢包\延迟,时钟不一样步。会致使设计上再延时(同步,半同步,异步)和一致性(线性一致性,原子事务提交)上有必定取舍,现实中模型:同步模型,假设网络延迟,进程暂停,时钟偏差都有界限;部分同步:大多数同步,偶尔变得至关大;异步模型:不能用超时,没有时钟。崩溃-中止故障:中止后永远不回来\崩溃恢复故障:未知时间后再次开始响应,节点具备稳定的存储且会保留/内存会丢失。拜占庭故障:包括试图戏弄和欺骗其余节点。能够实现不一样等级的一致性:如ACID,最终一致性,sessioin一致性,单调一致性
CAP是针对一条数据的(同一个系统数据可根据要求作不一样处理)。一致性的定义应该是普遍的(不仅是副本之间数据相同),能够理解为对一条数据获取的一致性,多我的多同一条数据读结果一致,一个事务对同一数据读结果一致,惟一性读取一致(惟一被获取其余读应该失败)
写入顺序与实际一致 不属于一致性范畴但应尽可能保证才使得读有意义,也归到这里讨论。叫因果一致性
最强的一致性是线性一致性(一旦写入成功读取的就是该值,直到再次覆盖)
所以涉及到问题大概有:因果一致性保证,线性一致性保证,事务一致性保证,惟一性约束
一个客户端在多个写节点中的顺序保证
,多节点写入时保证同步时因果覆盖正确。解决同一客户端发出对同一操做两个有序请求,最终到两个主库上,序号(到达是有序的,但时钟不可靠)不必定哪一个在后,因此同步时有错的问题。办法就是每次请求带节点最大序号,更新落后的节点。(不需去取全局递增序号)只是提供同步序号问题,只有同步解决才会获得全局数据,读数据调的应用需阻塞在全局回复上数据库
全序广播
顺序在消息传递时被固化,不容许将消息插入到顺序中较早位置。全序广播保证以固定顺序可靠的发送,不保证消息什么时候被送达.
全序广播实现线性一致性存储apache
向全序日志中追加一个惟一的用户名,读日志,等待消息被送回时执行。用一个全序日志作同步(C读,A读,B写,A写、每一个写所有节点都执行,读要等写都执行完,是本身调的返回客户端) 写一致性:若该日志第一条消息全部权是本身的,确认提交(给客户端返回)。如有并发写入,全部节点会对最早到达者达成一致。非本身节点也执行的 读取一致性:当有读取时追加一条消息,消息回送时读取日志,执行实际的读取返回。用全序控制线性,串行。
一旦commit没法rollback
第一阶段:收集协商,若参与者返回OK,即保证undo落盘可提交可回滚了。第二阶段把redo落盘或者undo回滚
若是协调者在准备好后失败,不得不等待他从新恢复。协调者上的常规单点提交。协调者有问题,锁没法释放棘手问题.即便协调者从新第二阶段没法判断是否是要提交,1在线可是只能知道本身yes,若2失败没法获取2的投票和状态。
缺陷:协调者单点,引入协调者可能使得服务器再也不是无状态的不能随意扩容,当夸各类数据系统时,须要时全部系统的最小集不能检测系统间死锁,没法SSI等,数据库内部的分布式事务(其实非XA)没有3问题可是系统任何部分失败都会失败 扩大失效=》改用共识编程
2.三阶段分布式原子提交
只要有一个进入precommit就说明确定能够commit,不然不会commit。
二阶段严重依赖C,节点不能根据自身投票决定,C和CL1在图中位置都挂后,新C不知道是否是都赞成,会使得pre无心义。三阶段能够在加锁后不依赖C释放。Pre能说明投票的状况,因此有了p能够自动工作,新C根据P决定do,若是C2和C同时失败,此时C1收到pre则继续没收到C1会abort,C2确定也没有commit。Pre时间必定要设置很长,保证C能够判断集群p的信息,不然若是出现pre最终有收到有没挂没收到自动超时仍是有问题。
对于网络分区能够用共识解决,恢复重启须要同步日志
内存
,多种内存数据结构
无索引,基本不支持事务
1,代码中写;
2,redis Cluster。请求不在的key要两次,先返回ip再请求一次
3,代理分片,好比tuemproxy,codis
1.主从模式。一主多从
RDB文件
+缓冲区命令
写命令传播
给从服务器心跳检测
。检测网络和命令丢失。(主服务器配置min-slaves-to-write n, min-slaves-max-lag m当从服务器数量少于3个,或者延迟大于等于10将拒绝执行写命令根据replication_offset检测是否丢失命令,补发命令)replication_offset,复制积压缓冲区
,服务运行ID2.哨兵模式。哨兵系统也是一个或多个特殊的redis服务器,监视普通服务器,负责下线主服务器和故障转移
哨兵经过raft协议选主,主哨兵选择主服务器
)。3.集群模式。去中心化,增长可扩展性,每一个能够读写
gossip通讯,从节点发现故障,raft从新选主
16384个槽。以槽为单位,从新分片
读写到任意节点, 二次转移
redis-trib
分片:hash
元数据:codis-proxy中,用codis-dashboard控制,zk保持同步
扩展:固定1024个slot。迁移是按照slot的维度
迁移有两个阶段,第一阶段状态改成pre_m。若proxy都确认,将状态改m。向所在的redis-server发送迁移命
codis-proxy的用zookeeper保证
。client获取zk节点作负载均衡codis-group的主从用redis的哨兵模式
分片信息和元数据由zk保证一致性,group中主从由redis自身负责最终一致性
详细redis与codis见:
https://segmentfault.com/a/11...
磁盘,B+树索引(搜索性能高稳定,节点不包含数据能够包含更多地址,层高少,叶节点链表扫面呢)/主键聚簇索引,内存buffer(二级索引change buffer)
buffer到磁盘过程当中问题:
刷脏 flush
二次写 脏页落盘须要二次写,redolog块对其不须要
清理过时 purge
事务
A undo log,逻辑日志,受redo log保护 。涉及回滚
C (一个事务中间状态可见性一致)MVCC 每一个视图有readviewid。经过undo日志恢复旧视图
I (多个事物之间可见性/操做不干扰)MVCC
D redolog 物理位置+逻辑日志。每一个事务本身buffer=>公共buffer=>磁盘 涉及checkpoint
server和innodb的binlog和redolog的一致性保证:内部二阶段提交
分布式事务
没有本身的集群管理.须要自行实现
主从 用binlog复制(基于行,语句,混合);采起同步/半同步/异步;全局GTID代替文件名和物理偏移量使得slave在多线程并发复制时崩溃恢复不会重复执行相同事务操做(GTID这种方式能够避免重复发,可是找起来没有文件和物理位置方便,须要记录集合,用GTID-sets有序合并存储xx:1-120这种形式,并发避免不了不能用位置由于多线程不按是顺序执行)。
当主库支撑不了。水平扩展。拆表。
没法自身保证
同步策略影响。
XA分为外部和内部。对于外部。要应用程序或proxy做为协调者。(二阶段提交协调者判断全部prepare后commit)。对于内部,binlog控制。
同步和事务失败回滚会有问题,先提交发送网络异常致使主库有从库无。等发送返回后提交失败回滚致使从库有主库无。
详细:
https://segmentfault.com/a/11...
没有开源。基于cache+rocksdb。
介于redis和rocksdb以前。对rocksdb的热key多了一层cache。
想结合redis的性能,mysql的持久化。redis和mysql的集成。支持分布式。
master读写都在master.slave备份做用。同一个slave和master不在一台机器上
把全部结构都转为纯粹K-V。rocksdb只负责存储kv
分片和redis同样,1024个固定,每一个集群管理部分
迁移:rocksdb文件快照,内存快照。slot迁移,增量记录。当迁移结束直接返回false换路由(最后一个增量时间在一个qps就能够),增量后merge.
复制:用rocksdb扫描key+多线程发送,同步成功点记录
同步:WAL。采起同步成功删除的方式。同步后用redis命令放入slot中
主备切换:codis的proxy感知切换
proxy用zk保证一致性
主备一致性WAL同步保证
详细:https://segmentfault.com/a/11...
SSD+SST。在正常的读写性能写入14M/S(32核),写比读稍好
ACID。
原子:WAL
隔离:版本快,常规的读只会读sequenceid以前的,内存中内容用sid控制,文件中version来组织,每次sid引用的version。
leveldb没有任何分布式。bigtable是chubby(分布式锁)+单机lebeldb。
rocksdb提供了基本的备份,增量备份,恢复,同步,事务日志,两阶段提交的接口支持。能够本身搭建proxy
wiredTiger引擎
详细:https://segmentfault.com/a/11...
B树,buffer,文档(磁盘)
修改操做在持久化时在新页中,不会对旧页有影响,成块写入不须要二次写
比较占内存(一个链接一个线程,tcp链接500个就1G,引擎数据cache,新写数据,备节点差别buffer,长事务快照,夸多集合时的排序)
运行模型:每一个链接一个线程,限制栈1M。虽然线程多切换代价大,但后台都是IO操做,代价还好。请求调用引擎层的方法
K-V
单机AD WAL(journal)+checkpoint
CI 未提交事务快照(同mysql,只有读用,写仍是要最新的页)
全量同步+oplog增量同步
主从同步:oplog 幂等(incr会转为set),循环覆盖,
顺序保证:写入 oplog前,会先加锁给 oplog 分配时间戳,并注册到未提交列表里,正式写入 oplog,在写完后,将对应的 oplog 从未提交列表里移除,在拉取 oplog 时若未提交列表为空,全部 oplog 均可读,不然只能到未提交列表最小值之前的 oplog
Secondary 拉取到一批 oplog 后,在重放这批 oplog 时,会加一个特殊的 Lock::ParallelBatchWriterMode 的锁,这个锁会阻塞全部的读请求,直到这批 oplog 重放完成
存储/读写qps
集合分片:分片范围,hash,tag(机房)
hash能够预先分配多个。同一时刻抢锁以后又一个mongos会迁移。迁移期间原请求阻塞排队,返回给mongos从新请求
复制集模式:故障检测恢复。成员间心跳,选举primary(Bully算法)。driver与复制集合心跳
原数据的存储介绍了工具,介质,结构等。实际上系统中还包括对源数据处理,好比缓存,计算。计算涉及到分布式要并行处理,流数据计算等。这一章节说下分布式下数据的处理
并行执行SQL。缺点:仅支持sql,倾向于内存中保存尽可能多数据,最多分钟级别。分布式文件系统的map-reduce或其余优化计算方式,数据多样性,不只关系或文档;查询不限于SQL(HIVE在此之上封装了SQL);文件系统能够包含MPP风格的或OLTP如HBase
unix管道处理的思想,sort内存溢出放入磁盘,输入输出与逻辑分离/统一接口/可复用 =》map/reduce
输入输出为分布式上的文件HDFS
好处:数据网络传输和计算分离。永远数据完备后才执行mapper或reducer;重试,失败回滚都中间态存储。
针对数据倾斜,每次都要map数据+reduce逻辑处理有一些优化。好比
正常都在reduce链接。某些能够在mapper链接优化,好比大数据与小数据的连接,将小数据广播打到mapper内存,mapper和reduce分区相同时,mapper只须要单独单个分区。若分区原本有序,能够直接在能够在此完成reduce的工做
举例:mapper根据须要对文档集合分区,reducer建立该分区的索引,并将索引你文件写入分布式文件系统。增量索引写入新的段,并在后台压缩与合并。就不详细说了。
基于这种处理思想,整个批处理的构建系统hadoop:
hdfs
是存储系统(见https://segmentfault.com/a/11...)yarn
是资源管理(见https://segmentfault.com/a/11...)
hadoop的高级该工具hive
(见https://segmentfault.com/a/11...),能够自动组装多个mapreduce阶段
构建推荐系统等,在线使用,mapreduce入单独数据库。好比HBase
(见:https://segmentfault.com/a/11...)
把整个工做流做为单个做业处理。替代map/reduce用算子,算子之间的链接能够有:记录从新分区排序/分区/广播链接。若中间态丢失,从先前中间态或原始数据从新计算。spark用弹性分布式数据集抽象跟踪数据的谱系,而flink对算子状态存档,容许在执行过程当中你那个遇到错误的算子。许多不用排序的能够流水线方式执行。好比组合分区的map+reduce(groupby,sort等)做为一个subtask,另外一个分区map+reduce(groupby,sort等)+reduce2(sink组合)为subtask2。1与2并行,2的reduce2等1。在map-reduce模式下是map1,map2并行,reduce分为很groupby1,groupby2,而后再map1,map2。再sort1,sort2。Spark
的技术理念是基于批来模拟流的计算。而Flink
则彻底相反,它采用的是基于流计算来模拟批计算。
详细见:因为批处理和流处理的总体划分思路(对分布式数据任务的拆分方式)是同样的。所以批处理和流处理的spark,flink写在一块儿了,详细见:spark(https://segmentfault.com/a/11...、flink(https://segmentfault.com/a/11...
对分布式数据的处理方法和上面同样,只说下流里边特别的
上述基于数据有界=》数据无界,增量处理。批处理输入是数据文件,须要考虑流处理的存储和传递?上述的分布式文件系统再也不行:增量要轮询,轮询的越频繁,能返回新事件的请求比例就越低,而额外开销也就越高。 相比之下,最好能在新事件出现时直接通知消费者(数据库的触发器功能有限)=》
设计点:费速度跟不上?丢弃,缓冲,背压。节点故障?
1.1 直接发送:UDP组播,ZeroMQ
(https://segmentfault.com/a/11...),HTTP或者RPC(webhooks,一种服务器的url被注册到另外一个服务中)。容错能力有限
1.2 消息代理:负载均衡、扇出,并发确认重传
1)RabbitMQ
,代理将单调消息分配给消费者,确认后删除(https://segmentfault.com/a/11...
2)基于日志的消息代理:使用日志消息存储。kafka
。每一个分区有序,每一个消息单调递增偏移量,要记录消费取的偏移量(https://segmentfault.com/a/11...)
适用于消息吞吐量高,处理迅速,顺序很重要(能够单分区全分能给某个负载均衡的线程)
数据库/缓存/索引/数据仓库
双写:1.两个客户端两个系统相互覆盖=》版本向量检测并发写入;2.一个成功一个失败(原子)
选择一个为领导者,好比数据库,让其余系统做为追随者。
变动数据捕获,将其提取并替换为能够复制到其余系统中的形式的过程 事件溯源,事件仅追加。日志压缩仅保存最新版本。通常的删除都是使数据不能取回.能够链接数据库和衍生数据,使其做为主。 可是解决了覆盖但由于异步,还会有原子问题,使用分布式事务
1.事件事件仍是处理时间?
处理时间:处理有问题,重启后处理大量堆积的
事件时间:不知道1分钟内的最后到达会在几分钟后=》丢弃或发布更正
若要准确的事件时间(即事件在设备上发起时间,记录该时间,发往服务器时间,服务器收到时间),经过从第三个时间戳中减去第二个时间戳,能够估算设备时钟和服务器时钟之间的偏移(假设网络延迟与所需的时间戳精度相比可忽略不计)。而后能够将该偏移应用于事件时间戳,从而估计事件实际发生的真实时间(假设设备时钟偏移在事件发生时与送往服务器之间没有变化)。在spark中会介绍google基于水位线的事件发生事件
2.窗口
滚动窗口,按分钟,每一个事件仅属于一个窗口。
跳动窗口,有重叠固定。
滑动窗口,5分钟内任意时间开始。
会话窗口,无会话关闭
3.容错
没法等待任务完成后根据输出错误处理=》微批量spark,存档点flink
详细见spark(https://segmentfault.com/a/11...
flink(https://segmentfault.com/a/11...