《设计数据密集型应用/DDIA》精要翻译(一) :reliability, scalability, maintainability

之前一段时间在看Kafka的源码分析,想学着做个分布式消息系统。后来听说 《设计数据密集型应用》这本书是2017年的神书,对这样的数据系统的内在精髓有很好的讲解。 看完这本书之后再看kafka之类的数据系统能事半功倍。因此,虽然每天忙于搬砖,但还是要抽出时间来拜读一下!这本书暂时还没有中文版,未来的一段时间里我会陆陆续续边看边做部分精华内容的翻译。

这是第一章第一节的读书笔记,介绍了reliability, scalability, maintainability这样的常见术语,以及我们如何能够实现这些目标。

一、Reliablity, 可靠性

先看一下对可靠性的定义:就算系统出现了例如硬件损坏或者人为错误之类的情况,它依然能够按我们所预期的那样运行。一个系统可能发生的错误分为以下3类:

1.硬件错误

包括磁盘损坏、内存损坏、机房断电、网线被挖等等。(磁盘的平均寿命是10到50年, 也就是说, 如果一个存储集群有10000块硬盘,平均每天都会坏一块。)

要应对硬件错误,我们的第一反应是对硬件做冗余,比如对磁盘用RAID阵列,机房双电源、热插拔CPU。

但是,由于很多应用的数据量和计算量相较以前都已经增长了很多,它们都用上了更多的机器,也因此增大了硬件故障的速率。此外,像AWS这样的云平台上的虚拟机常常会没有任何报警就挂掉,因为它们的设计是灵活性的优先级高于单机可靠性。所以现在的很多系统还会通过软件层面的容错方法来让自己变得更稳定

2.软件错误

软件错误不像硬件错误那样。 对硬件错误而言,一台机器的硬盘坏了,不意味着另一台的也会坏。 但是对于软件错误, 如果一个实例上的软件有问题,那每个都是有问题的。而且它会引发瀑布式的后果,一个组件的错误会引起另一个的错误。

对于软件错误,没有快速的解决方案,我们只能从一些小地方着手:

  1. 充分考虑自己的假设以及与系统中别的组件的交互
  2. 彻底的测试
  3. 过程的隔离
  4. 设计时,要允许进程crash之后重启
  5. 对生产环境要实时监控和分析

3.人的错误

人是不可靠的, 根据统计,大部分的错误都来自于运营配错了配置项。因此,也需要一系列的手段来对各种人为错误进行预防和快速恢复:

  1. 做好抽象,让自己的API变得易用、难以出错
  2. 能对一些错误做快速的rollback
  3. ……

二、Scalability 可伸缩性

对于一个系统而言, 就算它今天是能够可靠地运行的, 那也不意味着它将来也是可靠的。这种情况可能是因为: 系统的用户数从1W增长到了100W。

可拓展性是我们现在用来衡量一个系统应付增长的负载的能力的一个标准。

那么,什么是负载

拿推特举例,发推文的平均qps是4.6k, 获取自己的timeline的平均qps是300k, 要支持几十k的写请求和几百k的读请求可以说是很简单的。对于推特而言, 它的可拓展性的挑战来源于它的用户关注系统: 用户在拉自己的timeline时, 需要获取自己关注的用户发的推文。

推特的timeline系统经历过以下几个阶段:

  1. 用户发推时,插入到关系型数据库的tweets表, 在获取timeline时,从follows表中拿到自己所关注的用户ids, 再与tweets表做一个join。(我的个天哪。。) 很明显,这种方式没办法支撑日益增长的读请求。
  2. 用户发推时,写到这个用户的所有follower的“邮箱”中,邮箱用redis存储。这样的话, 对于人均75个follower的推特而言, 4.6k的写qps就变成了对redis 345k的写qps。而且这里的“人均”隐藏了用户之间的差异性,对于一个3000W粉丝的大V而言,他的一条推文就要被重复存储3000W次!这显然是太浪费了,因此推特转向了第三种实现。
  3. 对于大多数普通用户而言, 写到他的所有粉丝的邮箱中, 即所谓“推”。 对于大V而言,只保存到自己的推文集合中。他的粉丝在获取timeline时,需要把自己的邮箱和自己关注的大V的推文集合做一个merge+sort。(其实这也就是现在业界非常通用的“推拉结合”的方式
什么是“性能好”

不同的系统,评价它性能的指标是不一样的:

  1. 对于一个离线批处理系统例如hadoop而言,它的最重要的性能指标是吞吐量,即每秒处理的记录数, 或者是处理一批确定大小的数据所需要的时间
  2. 对于一个在线系统而言,最重要的指标是请求的响应时间。对于这样的系统,比“平均响应时间”更好的衡量标准是95线、99线、999线之类的百分比指标。
如何对增长的负载保持好的性能

有以下几点需要注意:

  1. 对于一种级别的负载(比如千万级日活)适用的架构,大多数情况下(ps:除非做了提前的设计,不过这种情况不太常见,现实往往是先追求快速迭代)是不能够应付10倍负载(比如亿级)的系统的。
  2. 可以考虑auto scale之类的技术(ps: 比如收集各个机器上的cpu使用率等指标,达到一定threshold就加减机器)
  3. 在大规模系统中,是没有万金油架构的。系统架构与这个应用本身是息息相关的。比如,系统的瓶颈可能是读、写、数据的存储、数据的复杂性、响应时间的要求等等。
  4. 尽管如此,一个可拓展的架构通常是由一些普适性强的小部件根据一些常见的模式所组成的。这也是本书之后要介绍的内容。

三、可维护性

众所周知,一个软件的主要成本不是开发的费用,而是后期维护的费用:修bug、适应新平台、改功能等等。

而维护一个老系统是痛苦的,大多数人都不想干这个。每个老系统都有各自令人讨厌的地方,难以给出通用的建议去处理它们。但是我们在设计系统的时候,可以参考以下3个原则:

1.可操作性, 让运维人员能够方便让这个系统良好的运行下去

可操作性意味着让监控系统健康、追踪问题根因等常规工作变的简单,让运维团队能够专注在有更高价值的事情上。对于一个数据系统而言,可以做以下事情:

  1. 提供系统运行时状态的可见性,即良好的监控
  2. 提供自动化工具
  3. 让单台机器处于维护状态,而不影响整个系统的继续运行
  4. 提供好的操作文档
  5. 提供各种默认行为,同时给管理员权限去修改这些行为
  6. 自愈能力
  7. 展示可预计的操作,不要给“surprise”
2.简单性,让新工程师能够很快理解这个系统

一个复杂的系统通常有以下这些症状:

  1. 模块紧耦合
  2. 混乱的依赖管理
  3. 命名不一致
  4. 为了一些特殊的case,做了很多复杂、不一目了然的work around

减少系统额外复杂性的一个好工具是抽象,好的抽象把复杂的实现隐藏起来,只暴露一个简单易懂的接口。本书之后会讨论如何做好抽象,创建可复用的、良好定义的组件。

3.可进化性/可拓展性,让工程师在未来能够容易的对这个系统进行变更

敏捷开发模型提供了处理快速变更的需求的一个框架,但大多数的敏捷方法论的关注点都是相对小一些的项目。本书之后会讨论对于大项目,如何提升它的“敏捷性”。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值