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.

139 lines
15 KiB
Markdown

2 years ago
# 加餐(二)| 用户Kaito我是如何学习Redis的
你好,我是蒋德钧。
在看课程留言的时候我发现Kaito同学的总结常常特别精彩所以就请编辑帮我联系了Kaito想请他来聊一聊具体是怎么学习Redis的。
接下来我就把Kaito的学习经验分享给你。
* * *
你好我是Kaito。
很荣幸受到极客时间编辑的邀请来和你分享一下我学习Redis的方法希望可以帮助你更加高效地学习Redis。
我先做个自我介绍。
从毕业到现在我已经工作7年了目前是北京的一家移动互联网公司的资深研发工程师。我之前主导设计过垂直爬虫采集平台后来开发面向用户的后端服务系统现在在从事基础架构和数据库中间件方面的研发工作具体是做跨数据中心的存储层灾备与多活领域的研发主要技术栈是Golang。
我们公司采用的Redis集群方案是Codis所以我也主要负责公司内的Codis定制化开发工作。在最近的一年多时间里我的很多工作都是围绕Redis展开的。在这期间我遇到了很多Redis相关的问题例如访问延迟变大、部署运维参数配置不合理等等也狠狠地恶补了Redis方面的知识看过书读过源码出过Bug踩过坑一路走来我逐渐梳理出了一套高效的学习路径我把它分为三大模块
1. 掌握数据结构和缓存的基本使用方法;
2. 掌握支撑Redis实现高可靠、高性能的技术
3. 精通Redis底层实现原理。
今天的这次分享我想先和你聊聊“如何高效学习Redis”后面我会再跟你分享我的一些学习心得和总结。
## 掌握数据结构和缓存的基本使用方法
要想会用一种系统我们首先要会一些基本操作。我们平时在开发业务系统时或多或少地会把Redis当作数据库或缓存使用。Redis也提供了非常丰富的数据结构这也给我们的开发提供了极大的便利。
所以要想快速地上手Redis我建议你从三个步骤入手
1. 学会基础数据类型的用法;
2. 掌握扩展数据类型的用法;
3. 积累一些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学习路径了基本上是按照从易到难逐渐递进的。在学习的过程中可以有针对性地看一些书籍以及相关的课程比如咱们的专栏这些内容可以帮助你快速地提升实战能力。
![](https://static001.geekbang.org/resource/image/0e/3c/0e7b8c42d1daf631b19c7164ac4bdf3c.jpg)
最后我也想请你聊一聊你是怎么学习Redis的呢希望你能在留言区聊聊你的学习方法我们一起交流。