【2.综述数据存储/计算】【精】

 阅读约 34 分钟

实在不知道起什么名字,打算写三个系列的文章,因为系统尽量有状态和无状态分离,第一个想写下系统中涉及到数据存储/分析/选型/自己设计要怎么做,包含内存/持久,数据量单机到大量到海量分布式的存储和计算。第二个想写无状态系统如何做到高处理能力,涉及到网络/分流/通信,进程/线程模型,操作系统IO,CPU调度等等。第三个为C++系列基础编码。
本篇为第一部分,先说下非海量数据存储选型应该考虑的点,自己设计也可以参考的点,对比了常用的非海量数据存储软件,这部分对数据模型,索引/存储,分布式的可靠性,可扩展,一致性给出总结性的解决方案。还介绍了海量数据(可能一般核心数据没这么多,但衍生数据量大)和流数据的存储和计算的总结性解决方案,这其中涉及众多组件,我只用过少部分,为了直观性,给出了几种常见衍生数据处理系统的应用设计。
系列文章涵盖了文中提到或未提到但常见的内容都分别单独篇章。

第一部分 数据的存储

存储应该考虑的点:
   ——性能(存储介质,数据格式,数据组织,索引,cache)
   ——功能(索引,事务)
   ——一致性
   ——可靠性
   ——扩展性(对于scale up共享内存和磁盘部分忽略)
   ——成本(物理,维护)
其中成本是需要再各个指标中关联的,比如性能里介质压缩大小,内存占用,可靠性才会冗余备份等,不单独讨论了。

性能

影响性能的:存储介质,数据模型,索引,存储格式。

存储介质 特点 数据组织
磁带
内存 map,set,list,skip-list,memory-table,stm(支持内存事务)
磁盘 顺序读写强,随机读写差,block -Tress =>B+ 层数一样,性能稳定,中间节点只有索引,容易缓存,数据只在子节点,数据可以扫描
SSD 随机性能高,并行度高,擦除影响寿命 SST(sst为何适合SSD?SSD是并发写,适合一次写入大量,擦除影响寿命,希望更新少,因为并发随机读写能力更强)
PCM

数据组织

  • 模型
    1.SQL 对象和关系不批配,ORM不能隐藏,支持XML/JSON转为行或者列
    2.文档型,多对1关系,文档对连接支弱,多对多=》文档引用(并非所有文档性都支持mongodb不支持连接)。二者对于复杂关系都抛弃了旧的网络模型(网络模型必须搜索所有路径)
    文档读取只能读取一整条,无法直接获取第二项;使用文档的好处是:文档(即对象)对应于许多编程语言中的本机数据类型。嵌入式文档和数组减少了对昂贵连接的需求,//减少join的IO。动态模式支持流畅的多态性。
    3.图模型:当多对多特别频繁,社交图谱,网络图谱等。三元组存储。SPARQL
    与网络差别:无模式,任意记录类型的嵌套,通过唯一ID直接饮用任何节点,也可以建立索引查询定点,网络中只能通过范耐高温你路径,订点和边无序,查询支持SPARQL
  • 索引及数据存储方式
    1.文件追加加入+hash索引:
    hash索引key和偏移量:散列表必须要内存中放得下,否则需要大量随机访问,不支持范围查询
    分段,开启新段,压缩合并旧段。逐个向后找。旧段的索引、旧数据的删除可以通过偏移量最小点删除或者保持冗余
    考虑点:删除标记,崩溃hash索引的恢复,数据库中途崩溃,并发控制

    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.内存中存储一切:性能+实现特殊的磁盘和索引难以实现的数据模型比如队列。=》反缓存

  • 分析=》大量扫描索引不重要了,解决IO问题。压缩
    上面的模型和结构在事务中很常见,在分析系统中,有些常用过简单的模型和索引等
    星型和雪花型
    列存储:行巨多但每次只查询少数列。=》列压缩,避免修改的LSM
  • 数据格式
    内存中:针对cpu和操作优化,树、列表
    文件/网络传输:编码,json/xml/csv/二进制(thrift,protocal buffer)
    需要考虑:数据表更改兼容(数据库中的数据,REST/RPC/Mq中的数据)
    这部分详见:https://segmentfault.com/a/11...

功能

功能各个系统自己针对不同会有很大差别(排序,聚合,搜索等),通用的功能只有事务,这里做下讨论,其他特性可以查API,功能和使用一般文档都比较好查,不仔细说了

事务

  • 单对象,日志崩溃恢复和回滚实现原子性+锁实现隔离性+CAS实现复杂的自增原子操作,但是事务更多指多对象
  • 原子性保证:中止后可以安全重试。无主复制的数据存储尽力而为遇到错误不撤销
    但重试还可能的问题:
    如果服务器已经成功但返回超时,可能成功两次
    错误由于负载过大造成,重试会造成更大的
    仅在临时性错误后才值得重试,永久性错误重试没没有意义
    事务后还副作用,比如发送邮件,几个系统一起,二阶段提交
  • 隔离
    1.脏写:行写锁
    2.脏读:锁代价大=》持有写入锁可以设置新值,读旧数据。读已提交的隔离级别,只需要保留一个版本
    3.可重复读:A事务过程中读取数据是一致的,即使该数据在期间被B修改,实现:MVCC。读已提交为每个查询使用单独的快照,可重复读对整个事务使用相同的快照。记录创建和删除的事务号,对大事务号的写入操作都忽略。索引可以指向所有版本,再过滤,可以所有版本存储在一个节点。还有仅追加,不更新,创建新的树分支,不需要再写另外存储(mongodb),mysql则在回滚段中恢复。
    4.关于幻读:网上的资料都是错的,MVCC可以保证读的正确性,会找旧版本(mysql是undo恢复)。但是对写不保证,因为写会取最新的,只能用锁保证,比如虽然事务中select * 取不到另一个事务的,但是Insert可能会冲突报错,select * for update也会读到B事务的,因为读取是最新的。若要保证更新没问题用select for update加锁(select * for update读最新加间隙锁)。这种先select检查再update的行为导致会议室预定等问题。幻读其实是写偏差。当可以间隙所时加锁,不能比如!=1
    5.丢失更新:A select value=2后update value=value+1。但是期间B事务insert value=3。此时value=4都是因为写都是最新的。这种问题mysql可重复读不保证,要么数据库加排它锁保证整个事务原子,要么显示锁定比如for update,代价较大。或者CAS,update value=value+1 where value=3;
    分布式丢失更新?允许多节点并发,不能用锁和CAS=>冲突解决
    6.串行化:单线程(扩展性不好,分布式要一个一个等,为了提高性能可以一个分区一个CPU一个事务线程,对于夸多分区的需要分区锁,尤其二级索引,吞吐量差)
    7.锁
    两阶段锁定:读共享锁写升级为排他锁,写要等所有的读释放,读必须等写释放,经常发生死锁。锁粒度控制:谓词锁(性能不好,符合搜索条件的再加锁)=》间隙锁(范围或全表)
    SSI 序列化快照隔离:悲观锁=》乐观锁(先执行到提交时再检查是否与隔离违背,违背则中止重试)。快照隔离+检测写入序列化冲突。(读之前存在未提交的写入)MVCC的读在提交时检查是否有被忽略的写入已经被提交。写入数据时通知所有最近读取的其他事务(读之后发生写入)

扩展性

  • 分片
    hash(事先分多)
    hash+键值组合
    一致性hash(不推荐,写不好)
    range(hbase 热点问题,分配不均,扫描和范围查询)
  • 二级索引:
    1.按主键ID范围分区,写入本地分区二级索引,合并读取。比如a的color放a机,按color查需要合并a和b
    2.全局按关键词索引,索引本身分布式。color自身分布式,每次写入a要写更新a和b机器的color索引,写慢读快。因为跨分区事务问题和一般异步更新二级索引,二级索引有不同步问题(不一致或部分更新失败,我认为分别是一致性问题和事务原子性问题)
  • 分区再平衡:
    1.固定分区数:如果用hash%n的形式(需要移动全部数据),需要提前分配更多,按照子集扩展,比如。虽然只有4个节点,开始就设置N为20
    2.动态分区数,按数据范围,类似B树节点的合并和拆分。mongodb的范围和hash都是动态分球
    3.固定节点数。每个节点有n个分区。新加入节点随机获取几个旧节点中几个的分区的一半组成新的分区,剩余的留在原来分区中,增加节点会增加分区数量,为了均衡随机选择分区拆分,要求分区边界基于散列的分区。(最符合一致性hash的语义)。比如卡桑德拉https://issues.apache.org/jir...,一致性hash基础上,每个节点256个虚拟节点,配置范围,新增节点将其他节点的部分范围分给新的vnodes(比如256分82%~102%)
    4.CRUSH这种(3需要记录具体配置,或者其他负载均衡都需要记录对应配置)不需要记录配置,hash到虚拟gid上个,gid与节点id一起产生随机数,所有节点id中选择随机数*权重最大的。随机数是伪随机,固定的,新增只需要把更大的移走,减少只需要移动到次大的。
  • 元数据部署
    路由决策组件:节点,单独路由,客户端
  • 分区并行查询问题后续再说

可靠性

单独说可靠性指异常补偿,涉及到复制,故障发现,故障自动转移。比较简单。有些副本提供读服务还有多写副本来提升可用性,有时存储的可靠性和可用性可以一起解决,简单说下这种部署和延时问题,详细的一致性在下面说。

  • 目的:考虑高可用的备份,高性能的负载,地理就近进行数据复制,一般三个副本可以保证11个9,两副本大规模下3个9必丢数据
  • 运行:同步,异步(绝大多数领导者形式都是这个),半同步:一个追随者同步,其他异步,至少有两个节点拥有最新的数据副本
  • 扩容:设置新从库,从主库拉取历史快照,从主库读取落后新数据直到commentid一致(需要log)
  • 故障发现,主动上报/心跳/lease
  • 故障恢复:
    1.从库,日志点
    2.主库:确认主库失效(需要考虑超时设置多少合理?)
    选择新主库 (脑裂=》单网络通道,奇数,仲裁/抢锁通道)
    更新客户端和其他从库配置
    旧主库恢复数据冲突处理
  • 复制方案:
    基于语句的。不确定性处理rand().now(),自增(mysql5.1以前),紧凑
    基于物理的。用于主存储或崩溃恢复。记录数据底层磁盘块字节更改,与存储紧密耦合,存储格式更改版本,若复制协议不支持匹配版本,需要停机处理
    逻辑日志,行复制。更新:唯一标识+所有列新值
    触发器(只复制部分)
  • 复制延迟:几种保证:读写一致性(自己更新自己立刻可看),单调读(同一用户多次读取一致),一致性前缀读(保证读取顺序按写入顺序),最终一致性。
    前三个一致性解决方法举例:更多内容见一致性。
    1)读主库,刚更新数据读主库,记录时间戳;自己请求读主库,注意不同网络和设备,分布打到多个数据中心的需要路由到主库的数据中心
    2)同一用户读固定从库,防止更旧
    3)依赖事务有因果关系的写入 同一个分片。不同分片不保证顺序一致
  • 部署:
    1.单主
    2.多主,只有多活多数据中心用到。或者需要每个本地离线写入。

    clipboard.png
    多活多数据中心与单活对比:单活每个写入都要进入主库数据中心,增加写入时间;通过网络中心的写是同步的,对网络容忍度比多中心异步复制低很多;故障一个主从切换,一个换主。
    处理写入冲突:写入检测两个主违背多主目的,写入都成功后同步时检测冲突用户无法补救。
    并发写入:哪些需要覆盖(比如依赖关系的B知道A先发生,只需要覆盖即可),哪些是并发(B不知道A的发生,并发需要按版本解决或者允许丢失用时间或序号。)
    =》
    预防冲突:同一写入只入一个,在故障等时特殊处理(需要切换,保证不了同一个请求不丢失或不重复)。
    冲突合并:每个写入或副本一个唯一ID,a.覆盖丢弃,b.维护多版本,自动处理的数据类型:集合等,带多版本等。
    b.版本向量:返回时将读取自的版本返回,再次写入时带该版本的就是依赖(本来知道有这个版本),可覆;否则是并发保留保本(本来不知道有这个版本)。

    clipboard.png

    3.无主:全部多读多写,返回数量足够成功。(读写法定人数的确认)
    单个落后如何恢复:读取时选择版本高的反更新(读修复)或 异步不断同步差异(反熵,不保证顺序)
    写入冲突和多主一样

一致性

困难:丢包\延迟,时钟不同步。会导致设计上再延时(同步,半同步,异步)和一致性(线性一致性,原子事务提交)上有一定取舍,现实中模型:同步模型,假设网络延迟,进程暂停,时钟误差都有界限;部分同步:大多数同步,偶尔变得相当大;异步模型:不能用超时,没有时钟。崩溃-停止故障:停止后永远不回来\崩溃恢复故障:未知时间后再次开始响应,节点具有稳定的存储且会保留/内存会丢失。拜占庭故障:包括试图戏弄和欺骗其他节点。可以实现不同等级的一致性:如ACID,最终一致性,sessioin一致性,单调一致性
CAP是针对一条数据的(同一个系统数据可根据要求做不同处理)。一致性的定义应该是广泛的(不只是副本之间数据相同),可以理解为对一条数据获取的一致性,多个人多同一条数据读结果一致,一个事务对同一数据读结果一致,唯一性读取一致(唯一被获取其他读应该失败)
写入顺序与实际一致 不属于一致性范畴但应尽量保证才使得读有意义,也归到这里讨论。叫因果一致性
最强的一致性是线性一致性(一旦写入成功读取的就是该值,直到再次覆盖)
因此涉及到问题大概有:因果一致性保证,线性一致性保证,事务一致性保证,唯一性约束
  • 因果一致性
    lamport时间戳,一个客户端在多个写节点中的顺序保证,多节点写入时保证同步时因果覆盖正确。解决同一客户端发出对同一操作两个有序请求,最终到两个主库上,序号(到达是有序的,但时钟不可靠)不一定哪个在后,所以同步时有错的问题。办法就是每次请求带节点最大序号,更新落后的节点。(不需去取全局递增序号)
    在纸上画了一下,与其他方案对比(不同写入按时间戳,分奇数偶数都会使得最终多副本顺序和真实一个客户端写入顺序不同)。不方便画。注意和上面多版本没关系呦,一个是单数据库控制多版本并存解决冲突,一个是对同一cli的多数据库写提供顺序sid保证安全覆盖,不同cli还是要两个version(知道因果的有序)

    只是提供同步序号问题,只有同步解决才会得到全局数据,读数据调的应用需阻塞在全局回复上

  • 全序广播
    顺序在消息传递时被固化,不允许将消息插入到顺序中较早位置。全序广播保证以固定顺序可靠的发送,不保证消息何时被送达.
    全序广播实现线性一致性存储

    向全序日志中追加一个唯一的用户名,读日志,等待消息被送回时执行。用一个全序日志做同步(C读,A读,B写,A写、每个写全部节点都执行,读要等写都执行完,是自己调的返回客户端)
    写一致性:若该日志第一条消息所有权是自己的,确认提交(给客户端返回)。若有并发写入,所有节点会对最先到达者达成一致。非自己节点也执行的
    读取一致性:当有读取时追加一条消息,消息回送时读取日志,执行实际的读取返回。用全序控制线性,串行。
  • 线性一致性:包含因果性(从概念上是读取最新的写入,但是写入有延迟是可接受的,读取不变即可)
    线性一致性是新鲜性的保证,读取一定能看见最新的写入值
    线性一致性存储实现全序广播
    每次全序广播发送首先从线性一致执行原子自增返回(比如寄存器执行自增并返回)操作,作为序号附加到消息中,将消息发送到所有节点,收件人按照序号连续处理消息。与lamport时间戳不一样,一致性存储数字没有间隙,如果节点发送了4并且收到6,在传递6之前必须等5再发送6。
    全序广播和线性一致都等价于共识
  • 分布式事务原子提交
    意义:在客户端或者proxy或者C同意提交后挂掉(不考虑回来,回来补偿)的情况下,让分布式的提交原子性,要么不commit,一个commit就全commit
    前提:2pc,3pc都是要一致(崩溃是最终一致),CL之间不通信。CL崩溃不恢复(恢复可以单独用日志,即保证在线节点一致即可)。一旦commit无法rollback
    对应mysql语句:update1 end1 prepare1;update2 end2 prepare2; commit1->commit2/rollback1->rollback2
    1.二阶段提交

    clipboard.png

    clipboard.png
    第一阶段:收集协商,若参与者返回OK,即保证undo落盘可提交可回滚了。第二阶段把redo落盘或者undo回滚
    如果协调者在准备好后失败,不得不等待他重新恢复。协调者上的常规单点提交。协调者有问题,锁无法释放棘手问题.即使协调者重新第二阶段无法判断是不是要提交,1在线但是只能知道自己yes,若2失败无法获取2的投票和状态。
    缺陷:协调者单点,引入协调者可能使得服务器不再是无状态的不能随意扩容,当夸各种数据系统时,需要时所有系统的最小集不能检测系统间死锁,无法SSI等,数据库内部的分布式事务(其实非XA)没有3问题但是系统任何部分失败都会失败 扩大失效=》改用共识

    2.三阶段分布式原子提交

    clipboard.png

    只要有一个进入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最终有收到有没挂没收到自动超时还是有问题。
    对于网络分区可以用共识解决,恢复重启需要同步日志

  • 共识
    详细见单独:https://segmentfault.com/a/11...
    全序广播相当于重复多伦共识。

常见数据存储开源方案

1.redis、codis

性能

内存,多种内存数据结构

功能

无索引,基本不支持事务

redis的分布式

1,代码中写;
2,redis Cluster。请求不在的key要两次,先返回ip再请求一次
3,代理分片,比如tuemproxy,codis

redis cluster

1.主从模式。一主多从

  • 可靠性:无法自动故障转移
  • 无扩展性
  • 复制:BGSAVE命令生成一个RDB文件+缓冲区命令
  • 同步:
    写命令传播给从服务器
    每秒一次频率向主服务器发送REPLCONF ACK <replication_offset>进行心跳检测。检测网络和命令丢失。(主服务器配置min-slaves-to-write n, min-slaves-max-lag m当从服务器数量少于3个,或者延迟大于等于10将拒绝执行写命令根据replication_offset检测是否丢失命令,补发命令)
    断点续传:replication_offset,复制积压缓冲区,服务运行ID

2.哨兵模式。哨兵系统也是一个或多个特殊的redis服务器,监视普通服务器,负责下线主服务器和故障转移

  • 可靠性:自动故障转移(哨兵通过raft协议选主,主哨兵选择主服务器)。
    1.相互发现
    2.sentinel默认1s/次的频率向所有主/从/sentinel服务器发送PING命令,有效回复为+PONG,-LOADING,-MASTERDOWN。当一个实例在down-after-milliseconds内,连续向sentinel返回无效回复,sentinel修改实例中flags加入|SRI_S_DOWN标识主观下线
    3.当接收到认为下线的sentinel数量超过quorum则flags加|SRI_O_DOWN
    4.第一个标主观的发起选主,成为主哨兵
    5.故障转移
    领头进行故障转移
    1) 选出新的主服务器
    在线的,5s内回复过INFO的,与主服务器断开连接时间足够短,优先级高,复制偏移量大,runid最小的,发送SLAVEOF no one,以1s/次(其他是10s/次)的频率向该服务器发送INFO。当role变为master时继续2
    2) 向下线的主服务的其他从服务器发送SLAVEOF命令
    3) 向旧的主服务器发送SLAVEOF命令
  • 无扩展性

3.集群模式。去中心化,增加可扩展性,每个可以读写

  • 可靠性:
    gossip协议,主从自动故障转移,gossip通信,从节点发现故障,raft重新选主
  • 扩展性:16384个槽。以槽为单位,重新分片
    指派槽与节点
    key与槽:CRC16(KEY) & 16383
    读写到任意节点, 二次转移
    重新分片:redis-trib
  • 元数据:所有节点保存

codis

扩展性

分片: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...

2.mysql、proxy

性能

磁盘,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...

3.fusion

没有开源。基于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同步保证

4.leveldb/rocksdb

详细:https://segmentfault.com/a/11...

性能

SSD+SST。在正常的读写性能写入14M/S(32核),写比读稍好

功能

ACID。
原子:WAL
隔离:版本快,常规的读只会读sequenceid之前的,内存中内容用sid控制,文件中version来组织,每次sid引用的version。

分布式

leveldb没有任何分布式。bigtable是chubby(分布式锁)+单机lebeldb。
rocksdb提供了基本的备份,增量备份,恢复,同步,事务日志,两阶段提交的接口支持。可以自己搭建proxy

5.mongodb

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与复制集合心跳

一致性

  • 路由(客户端,或mongos或单独的connfig server)
  • 数据:oplog保证

第二部分 衍生数据的处理

原数据的存储介绍了工具,介质,结构等。实际上系统中还包括对源数据处理,比如缓存,计算。计算涉及到分布式要并行处理,流数据计算等。这一章节说下分布式下数据的处理

批处理

1.MPP

并行执行SQL。缺点:仅支持sql,倾向于内存中保存尽量多数据,最多分钟级别。分布式文件系统的map-reduce或其他优化计算方式,数据多样性,不仅关系或文档;查询不限于SQL(HIVE在此之上封装了SQL);文件系统可以包含MPP风格的或OLTP如HBase

2.map/reduce

unix管道处理的思想,sort内存溢出放入磁盘,输入输出与逻辑分离/统一接口/可复用 =》map/reduce
输入输出为分布式上的文件HDFS

clipboard.png
好处:数据网络传输和计算分离。永远数据完备后才执行mapper或reducer;重试,失败回滚都中间态存储。
针对数据倾斜,每次都要map数据+reduce逻辑处理有一些优化。比如
正常都在reduce连接。某些可以在mapper连接优化,比如大数据与小数据的链接,将小数据广播打到mapper内存,mapper和reduce分区相同时,mapper只需要单独单个分区。若分区本来有序,可以直接在可以在此完成reduce的工作
举例:mapper根据需要对文档集合分区,reducer创建该分区的索引,并将索引你文件写入分布式文件系统。增量索引写入新的段,并在后台压缩与合并。就不详细说了。

基于这种处理思想,整个批处理的构建系统hadoop:

clipboard.png
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...

3.spark/flink

把整个工作流作为单个作业处理。替代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.1 直接发送:UDP组播,ZeroMQhttps://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...

应用

clipboard.png

  • 离线统计、数据量大(天级别)。kafka=>hive 自行查询出报表,表小分钟级别也出来了
  • 分钟内级别统计。kafka=>HBASE
  • 实时简单搜索

    clipboard.png

  • kafka=>ES(ES算是数据或衍生数据的主要提供索引外加一些聚合,单独篇章详见:https://segmentfault.com/a/11...
  • s级别实时报警: kafka=>spark/flink(要求更快可以加druid)=>实时监控+预测模型
  • odin监控采集:采集,tsdb,druid。
阅读 724更新于 2019-08-21

推荐阅读
目录