背景
最近在学习精力管理时,又重新了解了帕累托法则(二八定律),即我们应该把精力放到 20% 的重要的事情上,这些事情给予了你 80% 的价值。
我突然联想到学习一项技术时也是可以应用二八定律的,加上我最近想要重新学习一下 Redis ,所以想以二八定律为基础,来重构我的 Redis 知识技能树,最终的目标是通过学习 Redis 那 20% 的最重要的内容,来解决掉可能会面临的 80% 的问题。
所以, Redis 那 20% 最重要的内容是什么?
我认为在工作中不同角色的人的关注点是不一样的,所以 Redis 那 20% 最重要的内容也因人而异,比如开发关注的是如何用 Redis 来实现功能,运维则更关注如何管理、维护 Redis。所以,本文仅是我以开发者的身份进行思考和整理的。
一个“为什么”
回到起点,我会问自己一个问题:你为什么要用 Redis ?
答:因为用它查数据很快
问:为什么它查数据很快?
答:因为它用内存存储数据并且通过单线程模型来管理
问:为什么基于内存和单线程就会很快?
答: 有两个方面:
- 内存存取:内存操作数据能比磁盘快上千倍,让 Redis 实现亚毫秒级的响应时间
- 单线程模型:在 Redis 场景下,单线程模型避免了多线程环境中常见的上下文切换和锁竞争带来的性能开销,让单线程本身不会成为性能瓶颈。
问:为什么单线程能避免上下文切换和锁竞争,并且这为什么能提升性能?
答:
- 避免上下文切换:上下文切换的意思是多个线程会轮流使用 CPU,并且在切换前需要做一些环境的保存和还原工作,这不仅占用了 CPU 的工作时间,还没有执行任何业务逻辑,造成了消耗
- 避免锁竞争:对拥有共享权的数据,多线程在操作时,需要进行上锁和解锁操作来保证独享,因为独享,如果线程过多,在独享时就会有其他线程处于等待状态,导致工作效率降低
- 提升性能:Redis 单线程让工作串行,不会进行数据共享,也就没有这些开销,无关开销低了,性能就高了
问:既然单线程这么好,为什么不是所有服务都用单线程?
答: 不是所有的场景都适合单线程,针对 CPU 密集型任务(需要大量计算),单线程无法发挥多核 CPU 的性能,而 Redis 主要是 I/O 密集型场景,主要涉及内存 I/O 和网络 I/O,而内存操作非常快,因此 CPU 和内存 I/O 都不是性能瓶颈,网络 I/O 才是。Redis 在网络 I/O 上存在瓶颈的前提下还能用单线程处理高并发,核心在于它采用了 I/O 多路复用。
通过上面一系列反问,我得到了第一个 Redis 的核心内容:Redis 的单线程模型设计及其相关技术。
在我将它放到核心内容篮子前,我又问自己: 我只要知道 Redis 很快就行了,理解它的单线程模型对我能有什么用?
答:
- 理解命令是串行原子执行的,不会产生数据竞争的问题,不需要加锁,即使从 0 开始无锁并发执行 +1 100 次,最后一定是 100,而不会小于一百
- 拥有预判性能瓶颈的第六感,执行一个耗时的操作时,会立刻想到命令串行执行,它的耗时会阻塞后续所有的命令
所以,理解单线程模型是必要的。
五个“基础场景”
我们在实际的业务中,可能会面临各种复杂的场景,但经过拆解后,大部分会被拆分成为几种基础的使用场景。在日常的开发中,常见的主要有以下五种场景:
场景一
我想要对某个单一、整体数据进行存、取操作。
场景二
我想要将相关联的多个信息存储在一起,并且可以直接对某一个信息进行存、取操作。
场景三
我想要将多个信息以有序的方式进行排列存储。
场景四
我想要在存储含有相同元素的数据时进行自动去重。
场景五
我想要在存储含有相同元素的数据时进行自动去重和排序。
以上五种基础场景对应 Redis 提供的五种基础数据类型:
- String
- Hash
- List
- Set
- ZSet
深入理解 Redis 这五种基础数据类型,洞悉各自针对的使用场景和适用边界,才能为每一个数据场景选择最高效、最优雅的实现方式。
一个“性能点”与两个“原子化”
使用 Redis 解决业务场景后,我们会感叹于 Redis 数据结构的精妙和指令执行的高效。然而,在重度使用之后,我们会发现,其性能的真正枷锁在于通信而非计算,Redis 通过管道(Pipeline)将通信模式从“一问一答”的对话,转变为“一次性递交清单”的高效协作,让指令执行始终高效,让我们始终专注于业务逻辑。
使用 Pipeline 虽然能一次性提交多个指令,但其无法保证指令是依次相邻执行,Redis 对多指令的原子化执行,提供了两个工具:
- 事务:指令相连执行
- Lua 脚本:在指令相连执行的基础上支持查询、条件判断、更新的一连串复杂操作
两种持久化
持久化策略为什么对 Redis 会如此重要?
数据安全是数据存储系统的基石,了解 Redis 的持久化策略,能帮助我们了解数据的安全等级,进而决定我们开发的应用能否满足业务需求。
RDB 策略:定时对当前系统的存储状态进行快照,意味着它会丢失距离上一份快照前的数据。
AOF 策略:实时保存系统执行的写命令,意味着它可能会丢失少量命令。
如果数据存在数据库中,那么缓存数据的丢失就是可容忍的,重要的是 Redis 的响应速度和快速恢复能力,因此可以不使用持久化、或者使用 RDB 来从重启状态快速恢复。
如果数据只存储在 Redis 中,那么大量数据的丢失是不可接受的,使用 AOF 来尽量避免数据的丢失。
四种缓存模式
仅仅掌握了工具的用法是不够的,为了可以正确的使用 Redis,我们还需要学习缓存的设计 “方法论”,其一共有四种模式:
- 旁路缓存(Cache-Aside)
- 读穿透(Read-Through)
- 写穿透(Write-Through)
- 写回(Write-Back)
旁路缓存:应用程序是“中间人”,负责协调缓存和 DB 之间的所有数据同步。缓存本身完全不知道 DB 的存在。
读穿透/写穿透:应用程序变成了一个“甩手掌柜”,它认为缓存就是数据源,只跟缓存打交道。当缓存没有数据时,由缓存服务自己负责去 DB 里把数据捞出来。
写回:为了让写入速度飞快,应用程序更新数据时,只更新到缓存就立刻返回成功。缓存里的数据会过一段时间后,再由另一个程序异步地、慢慢地写入 DB。
Redis 与旁路缓存是最匹配的。
总结
Redis 最重要的那 20% 就是:
单线程模型 + 五个基础数据类型 + 管道 + 事务 + Lua 脚本 + 持久化 + 缓存模式。
评论