可靠的、可扩展的、可维护的数据系统 ------《Designing Data-Intensive Applications》读书笔记1

坦白说也是机缘巧合,在硕士生阶段进入分布式系统领域学习。无论是大规模存储或计算,其核心也是运用分布式技术利用并行性来解决数据密集型应用的需求。最近开始在啃这本《Designing Data-Intensive Applications》大部头,作者Martin Kleppmann在分布式数据系统领域有着很深的功底,并在这本书中完整的梳理各类纷繁复杂设计背后的技术逻辑,不同架构之间的妥协与超越,很值得开发人员与架构设计者阅读。

很可惜的是国内目前并没有对应的中文版本,这个系列算是一个读书感悟,同时也夹带私货,阐述一些自己的理解与看法,抛砖引玉,希望大家多交流学习。这本书共有12个章节,接下来我会一个章节更新一篇读书笔记。(囧rz,感觉自己又开了一个坑)同时也希望国内的出版社可以尽快引入版权,我也想要参与翻译工作啊(,,? ? ?,,) !!

1.数据密集应用

作为一个开发者来说,目前绝大多数应用程序都是数据密集型的,而不是计算密集型的。CPU的计算能力不再成为这些应用程序的限制因素,而更加亟待解决的问题是海量的数据、数据结构之间的复杂性,应用的性能。

先看看我们经常打交道的数据系统:

  • 存储数据,以便它们或其他应用程序稍后再找到它(数据库
  • 记住昂贵操作的结果,以加快读取速度。(缓存
  • 允许用户按关键字搜索数据或通过各种方式过滤数据(搜索索引
  • 将消息发送到另一个进程,异步处理(流处理
  • 周期性地压缩大量的累积数据(批处理

而很多时候,我们所谓应用程序的绝大工作就是将这些数据系统进行组合,然后添加我们的运行逻辑,但是如何更加合理的整合这些数据系统,对我们来说仍然是一个值得学习和思考的问题。而数据系统也在慢慢变得越来越相似,不同的数据系统也在各自学习彼此的优点。如Redis这样的缓存系统可以支持数据落地,很多时候的应用场合我们可以替代传统的RDBMS。而Kafka这样的数据队列也可以支持数据落地来存储消息。更加深刻的理解这些数据系统,来更好的权衡架构设计,是一门很精深的课题。

上图是一个典型的由多种数据系统构成的应用程序,随着数据量和数据逻辑的复杂,就成为了一个数据密集型的应用。

2.设计数据密集型应用的三原则

  • 可靠性

    具有容错性(面对硬件或软件故障,甚至是人为错误),系统仍应继续正常工作(在期望的性能水平上执行正确的功能)。

  • 可扩展性

    随着系统的增长(在数据量、流量或复杂度),应该有合理的方法来处理这种增长。

  • 可维护性

    随着时间的推移,许多不同的人将致力改善数据系统(既保持当前的行为,并使系统适应新的环境),他们都应该能够卓有成效地工作。

显然,这三个原则不单单是数据密集型应用应当遵循的原则,在绝大多数软件系统中同样是很重要的问题,接下来我们一一梳理一下。

(1)可靠性

  • 硬件故障

    硬盘崩溃,内存出现故障时,电网停电,有人拔了网线,几乎硬件故障在数据中心总是不间断的出现。

    解决方案

    • 在软件与硬件层面考虑冗余,来确保硬件的故障不会演变为系统的故障。
  • 人为的错误

    人是很不可靠,从驾驶技术的演变就可以看出来,人为的疏失会带来巨大的灾难。而且,人经常犯错。

    解决方案

    • 最小化错误机会的方式设计系统。例如,精心设计的抽象,API和管理界面可以很容易地做“正确的事情”,阻止“错误的事情”。
  • 人们犯最多错误的地方和那些可能导致失败的地方解耦。
  • 全面测试,从单元测试到整个系统集成测试和手动测试。
  • 允许快速和容易地从人为错误中恢复,以尽量减少在失败的情况下的影响。例如,使其快速回滚更改配置,逐步推出新的代码(所以任何意想不到的错误只影响一小部分用户),并提供工具来重新计算数据(如果原来旧的计算是不正确的)。

(2)可扩展性

即使一个系统今天工作可靠,但这并不意味着它将来一定会可靠地工作。一个常见原因是负载增加:也许系统已经从10000个并发用户发展到100000个并发用户,或者从100万个增加到1000万个。

“如果系统以特定的方式增长,我们应对增长的选择是什么?” “我们怎样才能增加计算资源来处理额外的负载?”

  • 描述负载

    首先,我们需要简洁地描述系统当前的负载,负载可以用几个数字来描述,我们称之为负载参数。

    参数的选择取决于系统的体系结构,如:

  • 每秒对Web服务器的请求
  • 数据库中的读写比
  • 聊天室中的活跃用户数量
  • 缓存的命中率
  • 描述性能

    一旦描述了系统上的负载,就可以讨论负载增加时发生的情况。可以从两方面看:

    1.增加负载参数并保持系统资源(CPU、内存、网络带宽等)不变时,系统的性能如何受到影响?

    2.当增加负载时,如果希望保持性能不变,需要增加多少资源?

所以我们需要有描述性能的尺子:

  • 平均响应时间:给定n值的算术平均值,全部加起来,除以n。然而这不是一个很好的指标,因为它不告诉你有多少用户真正体验了延迟。
  • 百分比响应时间:把响应时间列表,从最快到最慢排序,那么中间值是中间点:例如,如果平均响应时间是200毫秒,那意味着一半请求在少于200毫秒时返回,而一半请求花费的时间比那个要长。
  • 高百分比的响应时间:可以看看高百分位数:95th,99th,和99.9th百分位数是常见的(简称P95,P99,和p999),来参考响应时间的阈值。

负载情况与性能情况是很重要的,有时系统的瓶颈是由少数极端情况引起的。作者举了一个Twitter的例子,我觉得很好,这里详细分享一下这个例子:

Twitter的故事

Twitter在2012年11月16日公布的数据。

Twitter的两个主要操作是:

  • 发出Tweet

    用户可以发布一个Tweet给他们的订阅者。(平均4.6k请求/秒,峰值超过1.2万的请求/秒)。

  • 获取Tweet

    用户可以查看他们关注者发布Tweet。(约300K的请求/秒)。

Twitter在扩展性的挑战主要不是由于Tweet的数量,而主要是在每个用户都有很多订阅者,每个用户也有很多关注者。执行这两种操作大致是两种方法:

  • 1、发布一条推特,只需将新的推文插入到全球的推文集合中即可。当用户请求他们关注者的Tweet时,可以查找他们所关注的所有人,并找到每个用户的所有Tweet,并将它们合并(按时间排序)。在关系数据库中,可以编写如下查询,例如:

    java SELECT tweets.*, users.* FROM tweets JOIN users ON tweets.sender_id = users.id JOIN follows ON follows.followee_id = users.id WHERE follows.follower_id = current_user

    如下图所示:

  • 2、为每个用户订阅的Tweet维护一个缓存,就像每个收件人的Twitter邮箱一样。当用户发布一条推文时,请查找所有关注该用户的人,并将新的Tweet推送到他们的缓存中。所以读取Tweet列表是很划算的,因为它的结果提前计算好了。

如上图所示的结构显然更合适Tweet的发布,因为发布的Tweet的写操作几乎比读的操作低两个数量级,所以在这种情况下,最好是在写时做更多的工作,而不是在读时做更多的工作。但是方法2并不适用于有大量关注者的账号,假设某人有3000W粉丝,一次发布Tweet产生的写操作可能是巨大的。所以目前在Twitter的Tweet系统中,Twitter将这两种方法混合。大多数用户的推文在发布时仍然会被扩展到Tweet缓存之中,但只有少数用户拥有大量的关注者(即名人)。用户可以跟踪的任何名人的Tweet,并单独读取并与用户的Tweet缓存中进行合并。这种混合方法能够始终如一地提供良好的性能。

这个例子很精炼的描述了架构设计的妥协与精妙,依据业务特点,最大化的优化了数据系统的性能。很佩服Twitter的工程师在架构设计上的功力。同时也很好奇如微博,微信是不是也是采用类似的架构进行设计。

  • 怎么扩展

    放大(垂直缩放,移动到更强大的机器)和缩放(横向缩放,在多台更小的机器上分配负载)之间的二选一。实际上,好的架构通常涉及到一种实用的混合方法:例如,使用几个功能强大的机器仍然比大量的小型虚拟机更简单、更便宜。无节制的分布式会给系统混入复杂度,这是软件工程中危险的地方,虽然在多台机器上分发无状态服务相当简单,但将有状态数据系统从单个节点转移到分布式安装程序会带来许多额外的复杂性。

    没有这样的东西,一个通用的,一个适合所有的应用的可伸缩的架构。(写的真好

(3)可维护性

这部分教导了一些构建可维护系统的方法。软件的大部分成本不是在最初的开发中,而是在持续的维护中修复bug、保持系统运行、使其适应新业务、添加新特性。

  • 可操作性

    让操作运维团队保持系统运行的顺利。

  • 简单

    让新工程师很容易理解系统,通过尽可能地从系统中删除尽可能多的复杂性。

  • 可进化性

    让工程师很容易在将来对系统进行更改,以适应需求变化时的意料之外的用例。也被称为可扩展性、可修改性、可塑性。

维护别人留下的烂摊子真的是很痛苦的事情,文档,注释真的是重中之重!!!

时间: 2024-08-30 14:06:50

可靠的、可扩展的、可维护的数据系统 ------《Designing Data-Intensive Applications》读书笔记1的相关文章

数据密集型应用及其可靠、可扩展与可维护的定义

一.什么算是“数据密集型应用” 对于一个应用系统,如果“数据”是其成败决定性因素,包括数据的规模.数据的复杂度或者数据产生与变化的速率等,我们就可以称为“数据密集型应用系统”:与之对应的是计算密集型,CPU主频往往是后者最大的制约瓶颈. 例如:使用了以下组件的应用系统:关系型数据库.NoSql.消息队列.缓存.搜索引擎.批处理与流处理框架 二.可靠.可扩展与可维护的应用系统 1.可靠性 意味着即使发生故障,系统也可以正常工作.故障包括: 1)硬件故障.如硬盘崩溃.内存故障.电网停电.硬盘的平均无

《大型网站技术架构》读书笔记之七:随需应变之网站的可扩展架构

此篇已收录至<大型网站技术架构>读书笔记系列目录贴,点击访问该目录可获取更多内容. 一.可伸缩与可扩展-傻傻分不清楚 上篇笔记我们学习了可伸缩架构,但在实际场合中,包括许多架构师也常常混淆可伸缩和可扩展,用可扩展表示伸缩性.那么在此,跟随作者我们来理清这两个概念,避免我们以后对其傻傻分不清楚. (1)扩展性(Extensibiltiy) 指对现有系统影响最小的情况下,系统功能可持续扩展或提升的能力.我们不禁想到了面向对象中一大原则:开闭原则,对扩展开放,对修改封闭.也就说,当系统新增一个功能时

《编写可维护的javascript》读书笔记(中)——编程实践

上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. 二.编程实践 1.UI松耦合 第一.将css从javascript中抽离(要改变dom样式数据,应该去操作dom的class名而非dom的style属性,后续要修改此样式只需到对应的css文件中修改而不用修改js文件): 第二.将javascript从HTML中抽离,比如下面的写法是不好的 <!-- 不

《Android编程权威指南》-读书笔记(四)-GeoQuiz功能扩展

<Android编程权威指南>-读书笔记 -GeoQuiz功能扩展 从现在开始,这本书开始扩展应用.在这次扩展中我们将会学习以下知识点: 创建一个新类 更新视图层 更新控制层 Git代码的修改和提交 Android Studio 在设备中运行该应用 给按钮添加图片资源 功能:下图是GeoQuiz应用对象图解.应用的对象按模型.控制器和视图的类别被分为三部分.Android应用是给予模型-控制器-视图(Model-View-Controller,简称MVC)的架构模式进行设计的. 创建一个类 T

读书笔记7随需应变之网站的可扩展架构

一.可伸缩与可扩展—傻傻分不清楚 上篇笔记我们学习了可伸缩架构,但在实际场合中,包括许多架构师也常常混淆可伸缩和可扩展,用可扩展表示伸缩性.那么在此,跟随作者我们来理清这两个概念,避免我们以后对其傻傻分不清楚. (1)扩展性(Extensibiltiy) 指对现有系统影响最小的情况下,系统功能可持续扩展或提升的能力.我们不禁想到了面向对象中一大原则:开闭原则,对扩展开放,对修改封闭.也就说,当系统新增一个功能时,不需要对现有系统的结构和代码进行修改. (2)伸缩性(Scalability) 指系

[读书笔记]C#学习笔记五: C#3.0自动属性,匿名属性及扩展方法

前言 这一章算是看这本书最大的收获了, Lambda表达式让人用着屡试不爽, C#3.0可谓颠覆了我们的代码编写风格. 因为Lambda所需篇幅挺大, 所以先总结C#3.0智能编译器给我们带来的诸多好处, 下一遍会单独介绍Lambda表达式. 这篇主要包括的内容有: 自动属性,隐式类型,对象集合初始化,匿名类型,扩展方法. 下面一起来看下C#3.0 所带来的变化吧. 1,自动实现的属性在C#3.0之前, 定义属性时一般会像下面这样去编写代码: 1 class Person 2 { 3 //定义私

《编写可维护的javascript》读书笔记(上)

最近在读<编写可维护的javascript>这本书,为了加深记忆,简单做个笔记,同时也让没有读过的同学有一个大概的了解. 一.编程风格 程序是写给人读的,所以一个团队的编程风格要保持一致. 1.缩进:一种是利用制表符缩进,一种是使用空格符缩进,各有利弊,任选一种,保持一致即可.个人比较喜欢制表符缩进. 2.语句结尾需要加上分号,避免没必要的BUG. 3.命名:首先要语义化,使用驼峰式命名法,小驼峰即首字母小写,之后每个单词首字母大写:大驼峰即首字母大写,之后同小驼峰:变量名前缀应该是名词(my

读书笔记 - js高级程序设计 - 第十一章 DOM扩展

对DOM的两个主要的扩展 Selectors API HTML5 Element Traversal 元素遍历规范 querySelector var body = document.querySelector("body"); var myDiv = document.querySelector("#myDiv"); 取得id为myDiv的元素 var selected = document.querySelector(".selected")

Oracle Coherence 3.5 读书笔记之3 - 满足性能,可扩展和可用性目标

满足性能目标 影响操作执行时间的重要因素有算法和数据结构.在分布式系统中,还有一个重要的因素就是网络延时. 处理延时 开发人员在开发时通常没有考虑延时,测试时亦是如此. 作者给了一个示例,对于一个20ms的操作(典型的web操作完成时间),在本地执行只有0.067ms延时,而跨国执行时,需要264ms. 而通常一个web请求需要多次往返web服务器,延时就更加严重.因此应减少往返次数,即使对于内存的访问也需如此. 通常听到的建议"make your remote services coarse