You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

90 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 36 | 局部性原理:数据库性能跟不上,加个缓存就好了?
平时进行服务端软件开发的时候我们通常会把数据存储在数据库里。而服务端系统遇到的第一个性能瓶颈往往就发生在访问数据库的时候。这个时候大部分工程师和架构师会拿出一种叫作“缓存”的武器通过使用Redis或者Memcache这样的开源软件在数据库前面提供一层缓存的数据来缓解数据库面临的压力提升服务端的程序性能。
![](https://static001.geekbang.org/resource/image/67/89/675341b47057e483713395b55eef7089.png)
在数据库前添加数据缓存是常见的性能优化方式
那么不知道你有没有想过这种添加缓存的策略一定是有效的吗或者说这种策略在什么情况下是有效的呢如果从理论角度去分析添加缓存一定是我们的最佳策略么进一步地如果我们对于访问性能的要求非常高希望数据在1毫秒乃至100微秒内完成处理我们还能用这个添加缓存的策略么
## 理解局部性原理
我们先来回顾一下,上一讲的这张不同存储器的性能和价目表。可以看到,不同的存储器设备之间,访问速度、价格和容量都有几十乃至上千倍的差异。
![](https://static001.geekbang.org/resource/image/d3/a6/d39b0f2b3962d646133d450541fb75a6.png)
以上一讲的Intel 8265U的CPU为例它的L1 Cache只有256KL2 Cache有个1MBL3 Cache有12MB。一共13MB的存储空间如果按照7美元/1MB的价格计算就要91美元。
我们的内存有8GB容量是CPU Cache的600多倍按照表上的价格差不多就是120美元。如果按照今天京东上的价格恐怕不到40美元。128G的SSD和1T的HDD现在的价格加起来也不会超过100美元。虽然容量是内存的16倍乃至128倍但是它们的访问速度却不到内存的1/1000。
性能和价格的巨大差异,给我们工程师带来了一个挑战:**我们能不能既享受CPU Cache的速度又享受内存、硬盘巨大的容量和低廉的价格呢**你可以停下来自己思考一下,或者点击文章右上方的“请朋友读”,邀请你的朋友一起来思考这个问题。然后,再一起听我的讲解。
好了,现在我公布答案。想要同时享受到这三点,前辈们已经探索出了答案,那就是,存储器中数据的**局部性原理**Principle of Locality。我们可以利用这个局部性原理来制定管理和访问数据的策略。这个局部性原理包括**时间局部性**temporal locality和**空间局部性**spatial locality这两种策略。
我们先来看**时间局部性**。这个策略是说,如果一个数据被访问了,那么它在短时间内还会被再次访问。这么看这个策略有点奇怪是吧?我用一个简单的例子给你解释下,你一下就能明白了。
比如说《哈利波特与魔法石》这本小说我今天读了一会儿没读完明天还会继续读。同理在一个电子商务型系统中如果一个用户打开了App看到了首屏。我们推断他应该很快还会再次访问网站的其他内容或者页面我们就将这个用户的个人信息从存储在硬盘的数据库读取到内存的缓存中来。这利用的就是时间局部性。
![](https://static001.geekbang.org/resource/image/53/d3/53cb2d05d9bc8e3131466e9802d2c6d3.png)
同一份数据在短时间内会反复多次被访问
我们再来看**空间局部性**。这个策略是说,如果一个数据被访问了,那么和它相邻的数据也很快会被访问。
我们还拿刚才读《哈利波特与魔法石》的例子来说。我读完了这本书之后感觉这书不错所以就会借阅整套“哈利波特”。这就好比我们的程序在访问了数组的首项之后多半会循环访问它的下一项。因为在存储数据的时候数组内的多项数据会存储在相邻的位置。这就好比图书馆会把“哈利波特”系列放在一个书架上摆放在一起加载的时候也会一并加载。我们去图书馆借书往往会一次性把7本都借回来。
![](https://static001.geekbang.org/resource/image/1e/67/1ecca5bc07486a4d829263c8d78df667.png)
相邻的数据会被连续访问
有了时间局部性和空间局部性我们不用再把所有数据都放在内存里也不用都放在HDD硬盘上而是把访问次数多的数据放在贵但是快一点的存储器里把访问次数少的数据放在慢但是大一点的存储器里。这样组合使用内存、SSD硬盘以及HDD硬盘使得我们可以用最低的成本提供实际所需要的数据存储、管理和访问的需求。
## 如何花最少的钱,装下亚马逊的所有商品?
了解了局部性原理,下面我用一些真实世界中的数据举个例子,带你做个小小的思维体操,来看一看通过局部性原理,利用不同层次存储器的组合,究竟会有什么样的好处。
我们现在要提供一个亚马逊这样的电商网站。我们假设里面有6亿件商品如果每件商品需要4MB的存储空间考虑到商品图片的话4MB已经是一个相对较小的估计了那么一共需要2400TB = 6亿 × 4MB的数据存储。
如果我们把数据都放在内存里面那就需要3600万美元 = 2400TB/1MB × 0.015美元 = 3600万美元。但是这6亿件商品中不是每一件商品都会被经常访问。比如说有Kindle电子书这样的热销商品也一定有基本无人问津的商品比如偏门的缅甸语词典。
如果我们只在内存里放前1%的热门商品也就是600万件热门商品而把剩下的商品放在机械式的HDD硬盘上那么我们需要的存储成本就下降到45.6万美元( = 3600 万美元 × 1% + 2400TB / 1MB × 0.00004 美元是原来成本的1.3%左右。
这里我们用的就是时间局部性。我们把有用户访问过的数据,加载到内存中,一旦内存里面放不下了,我们就把最长时间没有在内存中被访问过的数据,从内存中移走,这个其实就是我们常用的**LRU**Least Recently Used**缓存算法**。热门商品被访问得多就会始终被保留在内存里而冷门商品被访问得少就只存放在HDD硬盘上数据的读取也都是直接访问硬盘。即使加载到内存中也会很快被移除。越是热门的商品越容易在内存中找到也就更好地利用了内存的随机访问性能。
那么只放600万件商品真的可以满足我们实际的线上服务请求吗这个就要看LRU缓存策略的**缓存命中率**Hit Rate/Hit Ratio也就是访问的数据中可以在我们设置的内存缓存中找到的占有多大比例。
内存的随机访问请求需要100ns。这也就意味着在极限情况下内存可以支持1000万次随机访问。我们用了24TB内存如果8G一条的话意味着有3000条内存可以支持每秒300亿次 = 24TB/8GB × 1s/100ns访问。以亚马逊2017年3亿的用户数来看我们估算每天的活跃用户为1亿这1亿用户每人平均会访问100个商品那么平均每秒访问的商品数量就是12万次。
但是如果数据没有命中内存那么对应的数据请求就要访问到HDD磁盘了。刚才的图表中我写了一块HDD硬盘只能支撑每秒100次的随机访问2400TB的数据以4TB一块磁盘来计算有600块磁盘也就是能支撑每秒 6万次 = 2400TB/4TB × 1s/10ms )的随机访问。
这就意味着所有的商品访问请求都直接到了HDD磁盘HDD磁盘支撑不了这样的压力。我们至少要50%的缓存命中率HDD磁盘才能支撑对应的访问次数。不然的话我们要么选择添加更多数量的HDD硬盘做到每秒12万次的随机访问或者将HDD替换成SSD硬盘让单个硬盘可以支持更多的随机访问请求。
![](https://static001.geekbang.org/resource/image/fb/58/fb32dd8a5847745d07a1b17254c75158.png)
当然,这里我们只是一个简单的估算。在实际的应用程序中,查看一个商品的数据可能意味着不止一次的随机内存或者随机磁盘的访问。对应的数据存储空间也不止要考虑数据,还需要考虑维护数据结构的空间,而缓存的命中率和访问请求也要考虑均值和峰值的问题。
通过这个估算过程,你需要理解,如何进行存储器的硬件规划。你需要考虑硬件的成本、访问的数据量以及访问的数据分布,然后根据这些数据的估算,来组合不同的存储器,能用尽可能低的成本支撑所需要的服务器压力。而当你用上了数据访问的局部性原理,组合起了多种存储器,你也就理解了怎么基于存储器层次结构,来进行硬件规划了。
## 总结延伸
这一讲,我们讲解了计算机存储器层次结构中最重要的一个优化思路,就是局部性原理。
在实际的计算机日常的开发和应用中,我们对于数据的访问总是会存在一定的局部性。有时候,这个局部性是时间局部性,就是我们最近访问过的数据还会被反复访问。有时候,这个局部性是空间局部性,就是我们最近访问过数据附近的数据很快会被访问到。
而局部性的存在,使得我们可以在应用开发中使用缓存这个有利的武器。比如,通过将热点数据加载并保留在速度更快的存储设备里面,我们可以用更低的成本来支撑服务器。
通过亚马逊这个例子,我们可以看到,我们可以通过快速估算的方式,来判断这个添加缓存的策略是否能够满足我们的需求,以及在估算的服务器负载的情况下,需要规划多少硬件设备。这个“估算+规划”的能力,是每一个期望成长为架构师的工程师,必须掌握的能力。
最后回到这一讲的开头我问了你这样一个问题在遇到性能问题特别是访问存储器的性能问题的时候是否可以简单地添加一层数据缓存就能让问题迎刃而解呢今天这个亚马逊网站商品数据的例子似乎给了我们一个“Yes”的答案。那么这个答案是否放之四海皆准呢后面的几讲我们会深入各种应用场景进一步来回答这个问题。
## 推荐阅读
想要仔细了解各种存储器和局部性原理,你还是可以去读一读教科书。《计算机组成与设计:硬件/软件接口》的5.15.2小节,是一个很好的阅读材料。
## 课后思考
我们今天拿了亚马逊的商品和用户访问数据做了例子。请你想一下如果是拿商品数量更多的淘宝网来看你可以估算一下至少需要使用多少DRAM的内存或者其他存储设备呢
欢迎留言和我分享你的思考过程和最终答案。如果自己的力量无法解决,你也可以拉上你的朋友一起讨论。