learn-tech/专栏/Redis核心技术与实战/加餐02用户Kaito:我是如何学习Redis的?.md
2024-10-16 06:37:41 +08:00

152 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
加餐 02 用户Kaito我是如何学习Redis的
在看课程留言的时候我发现Kaito 同学的总结常常特别精彩,所以就请编辑帮我联系了 Kaito想请他来聊一聊具体是怎么学习 Redis 的。
接下来,我就把 Kaito 的学习经验分享给你。
你好,我是 Kaito。
很荣幸受到极客时间编辑的邀请,来和你分享一下我学习 Redis 的方法,希望可以帮助你更加高效地学习 Redis。
我先做个自我介绍。
从毕业到现在,我已经工作 7 年了,目前是北京的一家移动互联网公司的资深研发工程师。我之前主导设计过垂直爬虫采集平台,后来开发面向用户的后端服务系统,现在在从事基础架构和数据库中间件方面的研发工作,具体是做跨数据中心的存储层灾备与多活领域的研发,主要技术栈是 Golang。
我们公司采用的 Redis 集群方案是 Codis所以我也主要负责公司内的 Codis 定制化开发工作。在最近的一年多时间里,我的很多工作都是围绕 Redis 展开的。在这期间,我遇到了很多 Redis 相关的问题,例如访问延迟变大、部署运维参数配置不合理,等等,也狠狠地恶补了 Redis 方面的知识,看过书,读过源码,出过 Bug踩过坑一路走来我逐渐梳理出了一套高效的学习路径我把它分为三大模块
掌握数据结构和缓存的基本使用方法;
掌握支撑 Redis 实现高可靠、高性能的技术;
精通 Redis 底层实现原理
今天的这次分享,我想先和你聊聊“如何高效学习 Redis”后面我会再跟你分享我的一些学习心得和总结。
掌握数据结构和缓存的基本使用方法
要想会用一种系统,我们首先要会一些基本操作。我们平时在开发业务系统时,或多或少地会把 Redis 当作数据库或缓存使用。Redis 也提供了非常丰富的数据结构,这也给我们的开发提供了极大的便利。
所以,要想快速地上手 Redis我建议你从三个步骤入手
学会基础数据类型的用法;
掌握扩展数据类型的用法;
积累一些 Redis 用作缓存的方法以及典型问题的解决方案。
在刚接触 Redis 时,第一步就是要学习它的基础数据结构,也就是 String、List、Hash、Set、Sorted Set。毕竟Redis 之所以这么受欢迎,跟它丰富的数据类型是分不开的,它的数据都存储在内存中,访问速度极快,而且非常贴合我们常见的业务场景。我举几个例子:
如果你只需要存储简单的键值对,或者是对数字进行递增递减操作,就可以使用 String 存储;
如果需要一个简单的分布式队列服务List 就可以满足你的需求;
如果除了需要存储键值数据,还想单独对某个字段进行操作,使用 Hash 就非常方便;
如果想得到一个不重复的集合,就可以使用 Set而且它还可以做并集、差集和交集运算
如果想实现一个带权重的评论、排行榜列表那么Sorted Set 就能满足你。
当我们能够熟练地使用这些基础的数据类型时,就说明我们已经入门了 Redis。此时如果你的业务体量不是很大那么在使用过程中并不会遇到很大的问题。但是现在已经进入了大数据时代我们不可避免地会遇到数据请求量巨大的业务场景对于这种情况基础数据类型已经无法应对了。
举个最简单的例子,当数据量很小时,我们想要计算 App 里某一天的用户 UV 数,只需要使用一个 Set 存储这一天的访问用户,再使用 SCARD就可以计算出结果了。但是假如一天的访问用户量达到了亿级就不能这样存储了因为这会消耗非常大的内存空间。而且这么大的 key 在过期时会引发阻塞风险。这个时候,我们就需要学习 Redis 的数据结构的高阶用法了。
Redis 提供了三种扩展数据类型,就是咱们前面学到的 HyperLogLog、Bitmap 和 GEO。
HyperLogLog 就非常适合存储 UV 这样的业务数据,而且它占用的内存非常小。同样地,当需要计算大量用户的签到情况时,你会发现,使用 String、Set、Sorted Set 都会占用非常多的内存空间,而 Redis 提供的位运算就派上用场了。如果你遇到了缓存穿透问题,就可以使用位运算的布隆过滤器,这种方法能够在占用内存很少的情况下解决我们的问题。
基于这个思路,你会发现,有很多巧妙地使用 Redis 的方法。在这个阶段,基于 Redis 提供的数据类型,你可以尽可能地去挖掘它们的使用方法,去实现你的业务模型。
除了借助数据类型实现业务模型之外,我们在使用 Redis 时,还会经常把它当作缓存使用。
因为 Redis 的速度极快,非常适合把数据库中的数据缓存一份在 Redis 中,这样可以提高我们应用的访问速度。但是,由于 Redis 把数据都存储在内存中而一台机器的内存是有上限的是无法存储无限数据的。所以我们还需要思考“Redis 如何做缓存”的问题。
你可能也听说过Redis 在用作缓存时,有很多典型的问题,比如说数据库和 Redis 缓存的数据一致性问题、缓存穿透问题、缓存雪崩问题。这些问题会涉及到缓存策略、缓存如何设置过期时间、应用与缓存如何配合,等等。所以,我们在前期学习的时候,还要知道一些应对策略。
学会了这些,我们就能简单地操作 Redis 了。接下来,我们就可以学习一些高阶的用法。
掌握支撑 Redis 实现高性能、高可靠的技术点
如果你看过软件架构设计相关的文章应该就会知道一个优秀的软件必须符合三个条件高可靠、高性能、易扩展。作为一个非常优秀的数据库软件Redis 也是符合这些条件的。不过,易扩展是针对深度参与 Redis 开发来说的,我们接触得比较少,暂时可以忽略。我们需要关注另外两个:高可靠、高性能。
Redis 之所以可以实现高可靠、高性能,和它的持久化机制、主从复制机制、哨兵、故障自动恢复、切片集群等密不可分。所以,我们还要掌握这一系列机制。这样的话, 在出现问题时,我们就可以快速地定位和解决问题了。而且,我们还可以从 Redis 身上学习一个优秀软件的设计思想,这也会给我们学习其他数据库提供非常大的帮助。
我先从一个最简单的单机版 Redis 说起,和你聊一聊我的理解。
假设我们只部署一个 Redis 实例,然后把业务数据都存储在这个实例中,而 Redis 只把数据存储在内存中,那么,如果此时,这个 Redis 实例故障宕机了,就意味着,我们的业务数据就全部丢失了,这显然是不能接受的。那该如何处理呢?
这就需要 Redis 有持久化数据的能力。具体来说就是可以把内存中的数据持久化到磁盘当实例宕机时我们可以从磁盘中恢复数据。所以Redis 提供了两种持久化方式RDB 和 AOF分别对应数据快照和实时的命令持久化它们相互补充实现了 Redis 的持久化功能。
有了数据的持久化,是不是就可以高枕无忧了?
不是的。当实例宕机后,如果我们需要从磁盘恢复数据,还会面临一个问题:恢复也是需要时间的,而且实例越大,恢复的时间越长,对业务的影响就越大。
针对这个问题,解决方案就是:采用多个副本。我们需要 Redis 可以实时保持多个副本的同步,也就是我们说的主从复制。这样,当一个实例宕机时,我们还有其他完整的副本可以使用。这时,只需要把一个副本提升为主节点,继续提供服务就可以了,这就避免了数据恢复过程中的一些影响。
但是,进一步再想一下,当主节点宕机后,我们把从节点提升上来,这个过程是手动的。手动触发就意味着,当故障发生时,需要人的反应时间和操作时间,这个过程也需要消耗时间。晚操作一会儿,就会对业务产生持续的影响,这怎么办呢?我们很容易会想到,当故障发生时,是不是可以让程序自动切换主从呢?
要实现主从自动切换,就需要能够保证高可用的组件:哨兵。哨兵可以实时检测主节点的健康情况。当主节点故障时,它会立即把一个从节点提升为主节点,实现自动故障转移,整个过程无需人工干预,程序自动完成,大大地减少了故障带来的影响。
所以你看,经过刚刚的分析,我们知道,为了保证可靠性,一个数据库软件必然需要做到数据持久化、主从副本和故障自动恢复。其他的数据库软件也遵循这样的原则,你可以留意观察一下。
到这里,我们说的都是针对单个 Redis 实例的功能,如果我们业务的读写请求不大,使用单个实例没有问题,但是当业务写入量很大时,单个 Redis 实例就无法承担这么大的写入量了。
这个时候,我们就需要引入切片集群了,也就是把多个 Redis 实例组织起来,形成一个集群,对外提供服务。同时,这个集群还要具有水平扩展的能力,当业务量再增长时,可以通过增加机器部署新实例的方法,承担更大的请求量,这样一来,我们的集群性能也可以变得很高。
所以,就有了 Redis Cluster、Twemproxy、Codis 这些集群解决方案。其中Redis Cluster 是官方提供的集群方案,而 Twemproxy 和 Codis 是早期 Redis Cluster 不够完善时开发者设计的。
既然是多个节点存储数据,而且还要在节点不足时能够增加新的节点扩容集群,这也对应着切片集群的核心问题:数据路由和数据迁移。
数据路由用于解决把数据写到哪个节点的问题,而数据迁移用于解决在节点发生变更时,集群数据重新分布的问题。
当我们从单机版 Redis 进入到切片集群化的领域时,就打开了另一个世界的大门。
不知道你有没有思考过这样一个问题:当我们的系统需要承担更大体量的请求时,从应用层到数据层,容易引发性能问题的地方在哪儿?
其实,最终都会落到数据库层面。因为我们的应用层是无状态的,如果性能达到了瓶颈,就可以增加机器的横向扩展能力,部署多个实例,非常容易。但是,应用层水平扩容后,数据库还是单体的,大量请求还是只有一个机器的数据库在支撑,这必然会产生性能瓶颈。所以,最好的方案是,数据库层也可以做成分布式的,这也就是说,数据也可以分布在不同的机器上,并且拥有横向扩展的能力,这样,在业务层和数据库层,都可以根据业务的体量进行弹性伸缩,非常灵活。
切片集群虽然更可靠,性能更好,但是因为涉及到多个机器的部署,所以就会引入新的问题,比如说,多个节点如何组织?多个节点的状态如何保持一致?跨机器的故障如何检测?网络延迟时集群是否还能正常工作?这些就涉及到分布式系统领域相关的知识了。
上面这些都是跟可靠性相关的知识,下面我们再来看看高性能。
Redis 的数据都存储在内存中,再加上使用 IO 多路复用机制所以Redis 的性能非常高。如果配合切片集群的使用,性能就会再上一个台阶。但是,这也意味着,如果发生操作延迟变大的情况,就会跟我们的预期不符。所以,如何使用和运维好 Redis 也是需要我们重点关注的,只有这样,才可以让 Redis 持续稳定地发挥其高性能。
而性能问题,就贯穿了刚刚我们说到的所有方面,业务使用不当,高可靠、切片集群运维不当,都会产生性能问题。
例如,在业务使用层面,使用复杂度过高的命令、使用 O(N) 命令而且 N 很大、大量数据集中过期、实例内存达到上限等,都会导致操作延迟变大;在运维层面,持久化策略选择不当、主从复制参数配置不合理、部署和监控不到位、机器资源饱和,等等,也会产生性能问题。
Redis 性能涉及到了 CPU、内存、网络甚至磁盘的方方面面一旦某个环节出现问题都会影响到性能。所以在第二个阶段我们就需要掌握跟高可靠、高性能相关的一系列机制。
这个时候,我们的 Redis 使用能力就超过了很多人,不过还达不到精通的程度。要想成为 Redis 大神,我们还必须具备能够随时解决棘手问题的能力。这个时候,我们就要去学习 Redis 的底层原理了。
精通 Redis 底层实现原理
我们要知道各种数据类型的底层原理。这个时候可以去看下源码。例如t_string.c、t_list.c、t_hash.c、t_set.c、t_zset.c。
在阅读源码的时候,我们就会了解每种数据结构的具体实现,例如 List 在底层是一个链表,在 List 中查找元素时就会比较慢,而 Hash 和 Set 底层都是哈希表实现的,所以定位元素的速度非常快,而 Sorted Set 是把哈希表和跳表结合起来使用,查找元素和遍历元素都比较快。如果你不了解这些数据结构的实现,就无法选择最佳的方案。
如果你看得比较仔细的话,还会发现,每种数据结构对应了不同的实现,例如 List、Hash、Sorted Set 为了减少内存的使用在数据量比较少时都采用压缩列表ziplist存储这样可以节省内存。而 String 和 Set 在存储数据时,也尽量选择使用 int 编码存储,这也是为了节省内存占用。这些都是 Redis 针对数据结构做的优化。只有了解了这些底层原理,我们在使用 Redis 时才能更加游刃有余,把它的优势真正发挥出来。
另外,我们还需要掌握跟高性能、高可靠相关的一系列原理,主要就是持久化、主从同步、故障转移、切片集群是如何做的,比如说:
RDB 和 AOF 重写都使用了操作系统提供的”fork”机制进行数据持久化这涉及到了操作系统层面的知识
故障转移使用哨兵集群实现,而哨兵集群的维护就涉及到了分布式系统的选举问题和共识问题;
切片集群是操作多个机器上的节点,如何对多个节点进行管理、调度和维护,也涉及到分布式系统的很多问题,例如 CAP 原理、分布式事务、架构设计;
……
掌握了原理,就可以以不变应万变,无论遇到什么问题,我们都可以轻松地进行分析和定位了。到了这个阶段,我们的 Redis 应用能力就已经远超很多人了。
好了,这些就是我总结的 Redis 学习路径了,基本上是按照从易到难逐渐递进的。在学习的过程中,可以有针对性地看一些书籍,以及相关的课程,比如咱们的专栏,这些内容可以帮助你快速地提升实战能力。
最后,我也想请你聊一聊,你是怎么学习 Redis 的呢?希望你能在留言区聊聊你的学习方法,我们一起交流。