【36】局部性原理:数据库性能跟不上,加个缓存就好了?
【36】局部性原理:数据库性能跟不上,加个缓存就好了?
引言
平时进行服务端软件开发的时候,我们通常会把数据存储在数据库里。而服务端系统遇到的第一个性能瓶颈,往往就发生在访问数据库的时候。
这个时候,大部分工程师和架构师会拿出一种叫作**“缓存”的武器**,通过使用 Redis 或者 Memcache 这样的开源软件,在数据库前面提供一层缓存的数据,来缓解数据库面临的压力,提升服务端的程序性能。
那么,不知道你有没有想过,这种添加缓存的策略一定是有效的吗?或者说,这种策略在什么情况下是有效的呢?如果从理论角度去分析,添加缓存一定是我们的最佳策略么?
进一步地,如果我们对于访问性能的要求非常高,希望数据在 1 毫秒,乃至 100 微秒内完成处理,我们还能用这个添加缓存的策略么?
一、理解局部性原理
1、一个实例【存储器访问速度越差,价格也越便宜】:
我们先来回顾一下,上一讲的这张不同存储器的性能和价目表。可以看到,不同的存储器设备之间,访问速度、价格和容量都有几十乃至上千倍的差异。
以上一讲的 Intel 8265U 的 CPU 为例,它的 L1 Cache 只有 256K,L2 Cache 有个 1MB,L3 Cache 有 12MB。一共 13MB 的存储空间,如果按照 7 美元 /1MB 的价格计算,就要 91 美元。
我们的内存有 8GB,容量是 CPU Cache 的 600 多倍,按照表上的价格差不多就是 120 美元【0.015x8x1024 = 122.88美元】
。如果按照今天京东上的价格,恐怕不到 40 美元。128G 的 SSD 和 1T 的 HDD,现在的价格加起来也不会超过 100 美元。虽然容量是内存的 16 倍乃至 128 倍,但是它们的访问速度却不到内存的 1/1000。
2、新的挑战:如何既享受Cache 的速度,又享受内存、硬盘的大容量和低价?
性能和价格的巨大差异,给我们工程师带来了一个挑战:我们能不能既享受 CPU Cache 的速度,又享受内存、硬盘巨大的容量和低廉的价格呢?
想要同时享受到这三点,前辈们已经探索出了答案,那就是,存储器中数据的局部性原理(Principle of Locality)。我们可以利用这个局部性原理,来制定管理和访问数据的策略。
这个局部性原理包括时间局部性(temporal locality)和空间局部性(spatial locality)这两种策略。
(1)时间局部性【数据被访问,短时间会被再次访问】
这个策略是说,如果一个数据被访问了,那么它在短时间内还会被再次访问。
比如说,《哈利波特与魔法石》这本小说,我今天读了一会儿,没读完,明天还会继续读。
同理,在一个电子商务型系统中,如果一个用户打开了 App,看到了首屏。我们推断他应该很快还会再次访问网站的其他内容或者页面,我们就将这个用户的个人信息,从存储在硬盘的数据库读取到内存的缓存中来。这利用的就是时间局部性。
(2)空间局部性【数据被访问,其相邻数据被访问】
这个策略是说,如果一个数据被访问了,那么和它相邻的数据也很快会被访问。
我们还拿刚才读《哈利波特与魔法石》的例子来说。我读完了这本书之后,感觉这书不错,所以就会借阅整套“哈利波特”。这就好比
我们的程序,在访问了数组的首项之后,多半会循环访问它的下一项。因为,在存储数据的时候,数组内的多项数据会存储在相邻的位置
。这就好比图书馆会把“哈利波特”系列放在一个书架上,摆放在一起,加载的时候,也会一并加载。我们去图书馆借书,往往会一次性把7 本都借回来。
(3)小结
- 有了时间局部性和空间局部性,我们不用再把所有数据都放在内存里,也不用都放在 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)缓存算法】
这里我们用的就是时间局部性。我们把有用户访问过的数据,加载到内存中,一旦内存里面放不下了,我们就把最长时间没有在内存中被访问过的数据,从内存中移走,这个其实就是我们常用的 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 万次(100000000x100/24/3600=115740.74≈12万)
。
但是如果数据没有命中内存,那么对应的数据请求就要访问到 HDD 磁盘了。刚才的图表中,我写了,一块 HDD 硬盘只能支撑每秒 100 次
的随机访问,2400TB
的数据,以 4TB 一块
磁盘来计算,有 600 块磁盘
,也就是能支撑每秒 6 万次( 2400TB/4TB × 1s/10ms )的随机访问
。
这就意味着,所有的商品访问请求,都直接到了 HDD 磁盘,HDD 磁盘支撑不了这样的压力。
我们至少要 50% 的缓存命中率,HDD 磁盘才能支撑对应的访问次数。不然的话,我们要么选择添加更多数量的 HDD 硬盘,做到每秒 12 万次的随机访问,或者将 HDD 替换成 SSD 硬盘,让单个硬盘可以支持更多的随机访问请求。
当然,这里我们只是一个简单的估算。在实际的应用程序中,查看一个商品的数据可能意味着不止一次的随机内存或者随机磁盘的访问。对应的数据存储空间也不止要考虑数据,还需要考虑维护数据结构的空间,而缓存的命中率和访问请求也要考虑均值和峰值的问题。
通过这个估算过程,你需要理解,如何进行存储器的硬件规划。你需要考虑硬件的成本、访问的数据量以及访问的数据分布,然后根据这些数据的估算,来组合不同的存储器,能用尽可能低的成本支撑所需要的服务器压力。而当你用上了数据访问的局部性原理,组合起了多种存储器,你也就理解了怎么基于存储器层次结构,来进行硬件规划了。
【小结】
最后,回到这一讲的开头,我问了你这样一个问题,在遇到性能问题,特别是访问存储器的性能问题的时候,是否可以简单地添加一层数据缓存就能让问题迎刃而解呢?
今天这个亚马逊网站商品数据的例子,似乎给了我们一个“Yes”的答案。
那么,这个答案是否放之四海皆准呢?后面的几讲,我们会深入各种应用场景,进一步来回答这个问题。
三、总结【个人总结的重点】
- 应用程序访问数据库遇性能瓶颈:在数据库前面提供一层缓存数据(Redis 或者 Memcache )
- 计算机存储器层次结构中最重要的一个优化思路:局部性原理
- 新的挑战:如何既享受Cache 的速度,又享受内存、硬盘的大容量和低价?
- 时间局部性【数据被访问,短时间会被再次访问】
- 空间局部性【数据被访问,其相邻数据被访问】
- 亚马逊数据访问实例——整个理解!
- “估算 + 规划”的能力,是每一个期望成长为架构师的工程师,必须掌握的能力:
- 通过快速估算的方式,来判断这个添加缓存的策略是否能够满足我们的需求
- 在估算的服务器负载的情况下,需要规划多少硬件设备
相关文章
- 开涛spring3(9.1) - Spring的事务 之 9.1 数据库事务概述
- 12.PHP_PDO数据库抽象层
- DB2数据库性能调整和优化(第2版)
- 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制
- 在程序开发中怎样写SQL语句可以提高数据库的性能
- Oracle数据库:oracle组函数,聚合函数,多行函数,avg,sum,min,max,count,group by,having
- 性能测试:自建数据库与RDS性能对比SQL Server案例排查分析
- 15年老司机的DPM数据库性能分析产品研发之路
- 自定义JDBC数据库连接池小例子
- Spring学习-5-JdbcTemplate数据库操作
- 《SQL入门经典(第5版)》一一6.3 事务控制与数据库性能
- Spring Boot 2 实战:使用 Flyway 管理你数据库的版本变更
- 关于数据库主键和外键(终于弄懂啦)
- 1 开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你。 本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则,这其中的原则已经被许多现有的JDBC应用程序编译运行并验证过。 这些指导原则包括: 正确的使用数据库MetaData方法 只获取需要的数据 选用最佳性能的功能 管理连
- SQL Server数据库性能优化技巧
- SQL索引一步到位(此文章为“数据库性能优化二:数据库表优化”附属文章之一)
- 浅析导致数据库性能问题的常见原因
- Oracle中 HWM与数据库性能的探讨
- DB2数据库应用系统性能优化深入探究
- 数据库与服务器安全选项拾遗
- MS SQL Server分析数据库的I/O性能
- 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)
- 使用ab.exe监测100个并发/100次请求情况下同步/异步访问数据库的性能差异
- 数据库新秀 postgresql vs mongo 性能PK
- HBase 数据库检索性能优化策略--转
- 数据库表字段命名规范
- MPP分布式数据库性能评估方法 - 阿里云HybridDB for PostgreSQL最佳实践