第一部分
第一章
1.讲了一个现代应用系统的基本架构和对软件来讲至关重要的三个问题:可靠性,可扩展性,可维护性。中位数的性能判定法。总之哦,好像和我这种搬砖的离的太远了。还有扩展在水平(加机器)和垂直(升级机器)之间做取舍。
第二章
1.nosql的诞生
讲了一下nosql的诞生和其优点:更好的可伸缩和更大的吞吐量,以及一些关系所不支持的特殊的查询操作.
2.对象关系不匹配
即数据库存储的结构(table)和应用代码中的对象不匹配需要一层转换层(orm框架),存储json(或者xml这种结构化的格式)虽然可以在一定程度上减少这种不匹配,但是大大提高了局部修改的代价,且降低了条件查询的能力(es的倒排索引是解决方案之一),因为一对多的数据是一个树状结构用json可以获得比关系型存储更好的表达性.但是这样在一行中存储冗余信息的方式又不符合关系性存储的规范,所以还是要根据实际情况去取舍.虽然json可以很好的表达一对多的关系,但却没法表达多对多的关系,这是文档结构无法逃避的缺点
大部分情况下当我们去要引用其他table的信息的时候我们都会存储其id这样相当于存储了对应数据的引用,其内容发生改变的时候其他的引用了该表的表无需进行修改,但是有些时候也需要存储非引用的冗余字段,因为这些数据可能会被delete,这时我们存储的冗余字段就相当于当时快照了.但是就需要考虑这些的数据的一致性问题,即发生更新时需要,将所有的副本都更新.所以这类问题可以归属为副本问题.
网络模型
暂时给我感觉就是图数据库,查询数据需要有查询的路径,要不然就只能从多条路径中自己去取舍.
文档模型中的模式灵活性
这里说文档数据库是不会强制存储的数据的但是可以通过模式的选择决定要不要规定文档的基本格式即schema(es需要建立索引已经是显式啦!),当然现在关系性数据库也支持文档类型了比如pg.
第三章
讲了lsm tree和b+,行存列存,列存优化,OLTP主要是对数据的增删改,OLAP是对数据的查询。
第四章
讲了一些编码格式,明文格式(如json),以及降低可读性减小数据大小的二进制格式,还有rpc如何做向前向后兼容的(这类东西是第一次系统的看,过一段时间估计就忘了).
第二部分
第五章
这部讲的是分布式下数据复制问题,如何保障一致性,和6.824搭配使用更佳.
第六章
讲分区,分库分表,负载均衡,冷热分离等等这些都是分区的手段.
第七章
事务的定义以及实现原理.
读已提交.需要保存数据的至少两个版本,之前已经提交的版本(旧),和当前正在提交中的版本(新).
update被翻译为delete and insert
观察一致性快照的可见性规则
1.当前事务开始的时候,尚未提交或者中止的事务的写入会被忽略
2.被中止事务所执行的写入会被忽略
3.具有较晚事务id的,即事务id大于当前事务的id,这些事务的写入会被忽略
4.所有写入对应用都是可见的
5.读事务开始时,创建该对象的事务已经提交。对象未被标记为删除,或如果被标记为删除,请求删除的事务在读事务开始时尚未提交。
第八章
分布式系统的麻烦
1.部分节点失效
2.异步,拥塞控制使得网络不可靠
3.需要同步和物理石英石时钟的变化使得时钟不可靠
第九章
1.线性一直,保证全序
2.兰伯特时间戳提供了因果全序,但无法保证操作全序
全序广播的两个关键点
可靠交付(reliable delivery)
没有消息丢失:如果消息被传递到一个节点,它将被传递到所有节点。
全序交付(totally ordered delivery)
消息以相同的顺序传递给每个节点。
比如raft中的状态机复制
全序广播是异步的,所以不同节点可能进度不一致.
而线性一致是及时的,即读取一定能看到最新写入的值.
可以通过将全序广播当作仅追加日志的方式来实现线性的cas操作.
此时写入虽然是线性一致的,但读取并不是,因为写入是异步的,所以读取还是可能读到旧的版本,可以通过在日志中添加一条读取消息来实现线性一致的读取,这样相当于标记了读取的时间,当所有写入完成后到达读取时间点再读取,这样就不会读取到旧的版本了.更简单的方法是,直接读取同步更新的副本.
使用线性一致实现全序广播:
首先使用线性一致的到一个自增的值,然后将值作为序列号附加到消息中,接收者按照序列号传输消息(就像raft需要term作为类似序列号的做法一样).
分布式事务与共识
2pc两阶段提交:
coordinator(协调者,事务管理器).
prepare(准备)
commit(提交)
1.每次启动一个事务的时候,协调者生成全局唯一的id,标识这次事务.
2.每个参与该次事务的节点的单点事务都需要带上这个全局事务id
3.prepare阶段,任意一个节点超时或者失败,则协调向所有参与本次事务的节点发送终止该事务id的请求.
4.参与者受到prepare请求时,需要确保在任意情况下都确保可以提交事务.这包括将所有事务数据写入磁盘,以及检查是否存在任何冲突等等,总之只要答应了,就不能反悔,事务一定可以不出错的提交.
5.协调者收集所有答复,就会做出决定,提交还是终止,并将这个决定写在磁盘的事务日志上,如果稍后协调者崩溃也可以同日志恢复,这个被称为commit point.
6.一旦决定落盘,该决定就会发送给所有的参与者,如果这个请求失败或者超时,协调者必须无限重试,直到成功.如果参与者崩溃,则事务在参与者恢复后提交.
7.参与在prepare阶段回复是,且协调者崩溃,参与者只能等待协调者恢复,事务处于(存疑)in doubt状态.
8.存疑事务可能导致,事务获取的锁长时间无法释放,在极端情况下只能依靠运维去手动去释放锁.
XA事务:跨异构两阶段提交,一般情况下为一个库,被加载到应用程序的进程中.heuristic decisions(启发式决策)用于参与者,单方面处理一个存疑事务.
容错共识
共识的性质
一致同意(Uniform agreement)
没有两个节点的决定不同。
完整性(Integrity)
没有节点决定两次。
有效性(Validity)
如果一个节点决定了值 v ,则 v 由某个节点所提议。
终止(Termination)
由所有未崩溃的节点来最终决定值。
第十章
mapreduce因为已经写过6.824的lab了,所以这里就不过多记录了.但是这部分讲的mapreduce对于容错所做的设计,站在我的角度去看就是虚拟容器对资源的调度导致的,是属于谷歌基于自己的实际情况设计的,大部分用户的离线计算还是放在专有服务器上运行的,其次map,reduce的编写方式也确实不如现在批流计算引擎方便,被淘汰也属于意料之内(写过一点点flink,有一点点感悟).
可以将消费某些数据的任务和生产这些数据的任务放在同样的服务器上,这样就可以通过共享内存来传输数据,而不是通过网络.
算子的中间状态写入本地内存或者磁盘,比写入hdfs需要更少的io,在flink中这个点被称为checkpoint.
第十一章
流处理与批处理最大的不同就是数据的有界和无界,批处理你总能获取一批大小明确的数据,但流处理你并不知道数据什么时候会来,来多少.流处理对比批处理的好处就是计算更及时,用户体验更好.
流处理的编写过程类似于消息队列的消费,当生产速度大于消费速度的时候,我们就需要和外的存储系统来保存未消费的数据,而不是去压榨消费者使其崩溃.
我们可以使用数据库存储吗?当然可以,但是我们每次获取数据都会依据上一次的消费时间作为起点向后获取数据,我们并不知道数据库中是否已经存储了最新的数据,每次请求也许会一无所获,在网络io上是一种很大的浪费,所以消息系统是一种更好的选择,不仅从主动拉取变成了被动接受,而且消息系统通常也会有持久化,一次消费的语义保证,让流处理的编写变的更专注于数据的处理,当然也有不得不使用数据库的场景,面对一些业务的场景,这种不爽有时候也不得不接受.
基于binlog的同步系统是美好的,因为binlog是全序的,在异构之下也可以良好的工作.(之前就写过一点基于阿里云的dts同数据到es的事情,不过他们的sdk是真不好用哈哈,client就是基于kafka二次开发的).遗憾就是复制延迟问题.
要校正不正确的设备时钟,一种方法是记录三个时间戳
1.事件发生的时间,取决于设备时钟
2.事件发送往服务器的时间,取决于设备时钟
3.事件被服务器接收的时间,取决于服务器时钟
使用3-2去估算1
流处理的三种连接
流流,流表,表表
第十二章
主要围绕着数据变更流构建系统的好处(这对于用过阿里云dts的我来说可太熟悉了).
消息错误会被丢弃重试,这就导致正好执行一次的语义失效了,比如说邮件被发送两次这类错误,处理的方法就是幂等,给消息事件添加唯一的表示
像是金融类操作重试意味这双倍金额,我们如何来去分两次操作是独立的还是重试的,就是为一次支付操作生成唯一标识符,一般支付的做法就是,生成支付单来标识这次支付的唯一性.就算支付请求因为客户端弱网络的原因被提交的两次,相同的支付单号也会使得他们只被执行一次.这其实也是一种幂等,只不过,被唯一标识的不是一个实体,而是一个动作.
这是一种端到端的解决方案即:冲客户端一路传递到数据库的事务标识.
最后就是作者对于数据的道德底线,以及作为工程师价值的个人看法,还挺酷的,至此本书的第一遍我看完了,以后找时间二刷.