文章目录
对于一个应用系统,如果“数据”是其成败决定性因素,包括数据的规模、复杂或者数据产生与变化的速率等,我们就可以称为“数据密集型应用系统”,与之相反的是计算密集型,CPU主频往往是后者最大的制约瓶颈(补充:CPU主频增长日趋缓慢,网络速度则依旧快速发展,这就意味着 并行分布式系统将会成为业界主流)。
当今很多应用都属于数据密集型,通常也是基于标准模块构建而成,每个模块负责单一的常用功能,例如:数据库、高速缓存、索引(用于搜索并支持过滤)、流式处理(持续发送消息至另一个进程,处理采用异步方式)、批处理(定期处理大量的累计数据)。
硬盘没有想象中牢靠,有研究证明硬盘的平均无故障时间(MTTF)约为10~50年,因此,在一个包括10000个硬盘的存储集群中,我们应该预期平均每天有一个硬盘发生故障。
最好不要将响应时间视为一个固定的数字,而是可度量的一种数值分布。如 百分位数,将响应时间信息从最快到最慢排序,中位数就是列表中间的响应时间,例如,中位数时间为 200ms,就意味着有一半的请求响应不到200ms,而另一半则需要更长的时间;中位数也成为50百分位数,有时缩写为 p50。为了弄清楚异常值有多糟糕,需要关注更大的百分位数,如 第95、99和99.9(缩写分别为 p95、p99和p999)值,作为典型的响应时间阈值,它们分别表示有 95%、99%和 99.99% 的请求响应时间快于阈值,例如,如果95百分位响应时间为1.5s,这意味着100个请求中的95个请求快于1.5s,而有5个请求则需要1.5s或更长时间。
✨一个例子:Twitter,它有2个典型业务操作:
(1)发布 tweet 消息,用户可以快速推送新消息到所有的关注者,平均大约 4.6k request/sec,峰值约 12k request/sec。
(2)主页时间线浏览:平均300k request/sec 查看关注对象的最新消息。
仅仅处理 12k request/sec 的消息发送并不难,难点在于扇出(原先是电子工程的术语,描述了输入的逻辑门 连接到另一个输出门的数量,输出需要提供足够的电流来驱动所有连接的输入。在事务处理系统中,用来描述为了服务一个输入请求而需要做的请求总数)。即 不在于消息大小之类的,而是巨大的扇出结构:每个用户会关注很多人,也会被很多人关注,那么,有以下方案:
(1)将发送的新 tweet 插入到全局的 tweet 集合中(可以理解为有一个 tweet 总表 ),当用户查看时间线时(可以理解为刷朋友圈),首先查找所有的关注对象,列出这些人所有的 tweet ,最后再以时间为序来排列合并。推测SQL查询语句如下:
SELECT tweets.*,users.* FROM tweets
JOIN users ON tweets.sender_id=users.id
JOIN follows ON followers.followee_id=users.id
where follows.follower_id=current_user
这种写法简单粗暴,不过主页时间线的读负载压力与日俱增,系统优化破费周折,另一种方案:
(2)对每个用户的时间线维护一个缓存,当用户推送新 tweet 时,查询其关注者,将 tweet 插入到每个关注者的时间线缓存中。这样访问时间线性能非常快。然而缺点是,在发布 tweet 时增加了大量额外的工作,考虑平均 75 个关注者和每秒 4.6k 的 tweet,则需要每秒 75✖4.6=345k 的速率写入缓存,不过这个 75 只是平均数,实际上某个用户的追随者可能超过 3000万,这就意味着峰值情况一个 tweet 会导致 3000w 的写入,而且要求尽量快,这是个大挑战。Twitter 最后采取的是:结合二者,对于超多关注者采用类上方案1 ,推文被单独提取,读取时才和用户的时间线主表合并。
由于系统要处理各种不同的请求,响应时间可能变化很大,因此,最好不要将响应时间视为一个固定数字,而应该是可度量的一种数值分布。
即使所有请求都相同,也会由于其他变量因素而引入一些随机抖动,比如,上下文切换和进程调度、网络数据包丢失和 TCP 重传、垃圾回收暂停、缺页中断 和 磁盘 I/O,甚至服务机架的机械振动。
最好用百分位数 percentiles ,将搜集到的响应时间信息,从最快到最慢排序,中位数就是列表中间的响应时间,比如,如果中位数响应时间为 200ms,那意味着有一半的请求响应时间不到 200ms,而另一半则需要更长的时间。这样的中位数也被称为50百分位数,缩写为 p50,为了弄清楚异常值有多糟糕,需要关注更大的百分位数,如 第95、99和99.9(缩写为 p95、p99、p999),都是典型的时间阈值,分别表示有 95%、99%、99.9% 的请求响应时间快于阈值。