This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
<audio id="audio" title="加餐(一)| 经典的Redis学习资料有哪些" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/58/a0/588ef87ce0f83d7da8e48d2da3715fa0.mp3"></audio>
你好,我是蒋德钧。
咱们课程的“基础篇”已经结束了。在这个模块我们学习了Redis的系统架构、数据结构、线程模型、持久化、主从复制和切片集群这些核心知识点相信你已经初步构建了自己的一套基础知识框架。
不过如果想要持续提升自己的技术能力还需要不断丰富自己的知识体系那么阅读就是一个很好的方式。所以这节课我就给你推荐几本优秀的书籍以及一些拓展知识面的其他资料希望能够帮助你全面掌握Redis。
## 经典书籍
在学习Redis时最常见的需求有三个方面。
- 日常使用操作:比如常见命令和配置,集群搭建等;
- 关键技术原理比如我们介绍过的IO模型、AOF和RDB机制等
- 在实际使用时的经验教训比如Redis响应变慢了怎么办Redis主从库数据不一致怎么办等等。
接下来,我就根据这些需求,把参考资料分成工具类、原理类、实战类三种。我们先来看工具类参考资料。
### 工具书《Redis使用手册》
一本好的工具书可以帮助我们快速地了解或查询Redis的日常使用命令和操作方法。我要推荐的《Redis使用手册》就是一本非常好用的工具书。
在这本书中作者把Redis的内容分成了三大部分分别是“数据结构与应用”“附加功能”和“多机功能”。其中我认为最有用的就是“数据结构与应用”的内容因为它提供了丰富的操作命令介绍不仅涵盖了Redis的5大基本数据类型的主要操作命令还介绍了4种扩展数据类型的命令操作包括位图、地址坐标、HyperLogLog和流。只要这本书在手边我们就能很轻松地了解和正确使用Redis的大部分操作命令了。
不过如果你想要了解最全、最新的Redis命令操作我建议你把Redis的命令参考网站收录到你的浏览器书签中随用随查。目前Redis官方提供的所有命令操作参考肯定是最全、最新的建议你优先使用这个[官方网站](https://redis.io/commands/)。在这个网页上查找命令操作非常方便我们既可以通过命令操作的名称直接查找也可以根据Redis的功能分类查找对应功能下的操作例如和集群相关的操作和发布订阅相关的操作。考虑到有些同学可能想看中文版我再给你提供一个[翻译版的命令参考](http://redisdoc.com/)。
除了提供Redis的命令操作介绍外《Redis使用手册》还提供了“附加功能”部分介绍了Redis数据库的管理操作和过期key的操作这对我们进行Redis数据库运维例如迁移数据、清空数据库、淘汰数据等提供了操作上的指导。
有了工具手册,我们就能很轻松地掌握不同命令操作的输入参数、返回结果和复杂度了。接下来,就是进一步了解各种机制背后的原理了,我再跟你分享一本原理书。
### 原理书《Redis设计与实现》
虽然《Redis设计与实现》和《Redis使用手册》是同一个作者写的但是它们的侧重点不一样这本书更加关注Redis关键机制的实现原理。
介绍Redis原理的资料有很多但我认为这本书讲解得非常透彻尤其是在Redis底层数据结构、RDB和AOF持久化机制以及哨兵机制和切片集群的介绍上非常容易理解我建议你重点学习下这些部分的内容。
除了文字讲解,这本书还针对一些难点问题,例如数据结构的组成、哨兵实例间的交互过程、切片集群实例的交互过程等,都使用了非常清晰的插图来表示,可以最大程度地降低学习难度。
其实这本书也是我自己读的第一本Redis参考书可以说是它把我领进了Redis原理的大门。当时在学习时正是因为有了这些插图的帮助我才能快速地搞懂核心原理。直到今天我都还记得这本书中的一些插图真是受益匪浅。
虽然这本书的出版日期比较早它针对的是Redis 3.0但是里面讲的很多原理现在依然是适用的它可以帮助你在从入门Redis到精通的道路上迈进一大步。
### 实战书《Redis开发与运维》
在实战方面《Redis开发与运维》是一本不错的参考书。
首先它介绍了Redis的Java和Python客户端以及Redis用于缓存设计的关键技术和注意事项这些内容在其他参考书中不太常见你可以重点学习下。
其次,它围绕客户端、持久化、主从复制、哨兵、切片集群等几个方面,着重介绍了在日常的开发运维过程中遇到的问题和“坑”,都是经验之谈,可以帮助你提前做规避。
另外这本书还针对Redis阻塞、优化内存使用、处理bigkey这几个经典问题提供了解决方案非常值得一读。在阅读的时候你可以把目录里的问题整理一下做成列表这样在遇到问题的时候就可以对照着这个列表快速地找出原因并且利用书中的方案去解决问题了。
当然,要想真正提升实战能力,光读书是远远不够的,毕竟,“纸上得来终觉浅”。所以,我还想再给你分享两条建议。
第一个建议是阅读源码。读源码其实也是一种实战锻炼可以帮助你从代码逻辑中彻底理解Redis系统的实际运行机制**当遇到问题时,可以直接从代码层面进行定位、分析和解决问题**。阅读Redis源码最直接的材料就是Redis在GitHub上的[源码库](https://github.com/redis/redis)。另外,有一个[网站](https://github.com/huangz1990/redis-3.0-annotated)提供了Redis 3.0源码的部分中文注释,你也可以参考一下。
另外我们还需要亲自动手实践。在课程的留言中我看到有同学说“没有服务器无法实践”其实Redis运行后本身就是一个进程我们是可以直接使用自己的电脑进行部署的。只要不是性能测试在功能测试或者场景模拟上自己电脑的环境一般都是可以胜任的。比如说要想部署主从集群或者切片集群模拟主库故障我们完全可以在自己电脑上起多个Redis实例来完成只要保证它们的端口号不同就可以了。
好了关于Redis本身的书籍的推荐就先告一段落了接下来我想再给你分享一些扩展内容。
## 扩展阅读方向
通过前面几节课的学习我相信你一定已经发现了Redis的很多关键功能其实和操作系统底层的实现机制是相关的比如说非阻塞的网络框架、RDB生成和AOF重写时涉及到的fork和写时复制机制等等。另外Redis主从集群中的哨兵机制以及切片集群的数据分布还涉及到一些分布式系统的内容。
我用一张图片展示一下Redis的关键机制和操作系统、分布式系统的对应知识点。
<img src="https://static001.geekbang.org/resource/image/a0/2c/a0f558fbf9105817744ee2c44230c62c.jpg" alt="">
AOF日志的刷盘时机和操作系统的fsync机制、高速页缓存的刷回有关而网络框架跟epoll有关RDB生成和AOF重写与fork、写时复制有关我在前面第3、4、5讲上讲过它们的关联
此外,我在[第8讲](https://time.geekbang.org/column/article/275337)介绍的哨兵选主过程其实是分布式系统中的经典的Raft协议的执行过程如果你比较了解Raft协议就能很轻松地掌握哨兵选主的运行机制了。在[第9讲](https://time.geekbang.org/column/article/276545)我们学习了实现切片集群的Redis Cluster方案其实业界还有一种实现方案就是ShardedJedis而它就用到了分布式系统中经典的一致性哈希机制。
所以,如果说你希望自己的实战能力能够更强,我建议你**读一读操作系统和分布式系统方面的经典教材,**比如《操作系统导论》。尤其是这本书里对进程、线程的定义对进程API、线程API以及对文件系统fsync操作、缓存和缓冲的介绍都是和Redis直接相关的再比如《大规模分布式存储系统原理解析与架构实战》中的分布式系统章节可以让你掌握Redis主从集群、切片集群涉及到的设计规范。了解下操作系统和分布式系统的基础知识既能帮你厘清容易混淆的概念例如Redis主线程、子进程也可以帮助你将一些通用的设计方法例如一致性哈希应用到日常的实践中做到融会贯通举一反三。
## 小结
这节课我给你推荐了三本参考书分别对应了Redis的命令操作使用、关键机制的实现原理以及实战经验还介绍了Redis操作命令快速查询的两个网站这可是我们日常使用Redis的必备工具可以提升你使用操作Redis的效率。另外对于Redis关键机制涉及到的扩展知识点我从操作系统和分布式系统两个方面进行了补充。
Redis的源码阅读是成为Redis专家的必经之路你可以阅读一下Redis在GitHub上的源码库如果觉得有难度也可以从带有中文注释的源码阅读网站入手。
最后我也想请你聊一聊你的Redis学习资料都有哪些呢欢迎在留言区分享一下我们一起进步。另外如果你觉得今天的内容对你有所帮助也欢迎你分享给你的朋友或同事。

View File

@@ -0,0 +1,130 @@
<audio id="audio" title="加餐(七) | 从微博的Redis实践中我们可以学到哪些经验" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0f/39/0f110c648cf9081257b821781cab4339.mp3"></audio>
你好,我是蒋德钧。
我们知道微博内部的业务场景中广泛使用了Redis积累了大量的应用和优化经验。微博有位专家曾有过一个[分享](https://mp.weixin.qq.com/s?__biz=MzI4NTA1MDEwNg==&amp;mid=2650782429&amp;idx=1&amp;sn=7f2df520a7295a002c4a59f6aea9e7f3&amp;chksm=f3f90f48c48e865e478d936d76c5303663c98da506f221ede85f0f9250e5f897f24896147cfb&amp;scene=27#wechat_redirect)介绍了Redis在微博的优化之路其中有很多的优秀经验。
俗话说“他山之石可以攻玉”学习掌握这些经验可以帮助我们在自己的业务场景中更好地应用Redis。今天这节课我就结合微博技术专家的分享以及我和他们内部专家的交流和你聊聊微博对Redis的优化以及我总结的经验。
首先我们来看下微博业务场景对Redis的需求。这些业务需求也就是微博优化和改进Redis的出发点。
微博的业务有很多例如让红包飞活动粉丝数、用户数、阅读数统计信息流聚合音乐榜单等同时这些业务面临的用户体量非常大业务使用Redis存取的数据量经常会达到TB级别。
作为直接面向终端用户的应用微博用户的业务体验至关重要这些都需要技术的支持。我们来总结下微博对Redis的技术需求
- 能够提供高性能、高并发的读写访问,保证读写延迟低;
- 能够支持大容量存储;
- 可以灵活扩展,对于不同业务能进行快速扩容。
为了满足这些需求微博对Redis做了大量的改进优化概括来说既有对Redis本身数据结构、工作机制的改进也基于Redis自行研发了新功能组件包括支持大容量存储的RedRock和实现服务化的RedisService。
接下来我们就来具体了解下微博对Redis自身做的一些改进。
## 微博对Redis的基本改进
根据微博技术专家的分享我们可以发现微博对Redis的基本改进可以分成两类避免阻塞和节省内存。
首先针对持久化需求他们使用了全量RDB加增量AOF复制结合的机制这就避免了数据可靠性或性能降低的问题。当然Redis在官方4.0版本之后也增加了混合使用RDB和AOF的机制。
其次在AOF日志写入刷盘时用额外的BIO线程负责实际的刷盘工作这可以避免AOF日志慢速刷盘阻塞主线程的问题。
再次增加了aofnumber配置项。这个配置项可以用来设置AOF文件的数量控制AOF写盘时的总文件量避免了写入过多的AOF日志文件导致的磁盘写满问题。
最后,在主从库复制机制上,使用独立的复制线程进行主从库同步,避免对主线程的阻塞影响。
在节省内存方面,微博有一个典型的优化,就是定制化数据结构。
在使用Redis缓存用户的关注列表时针对关注列表的存储他们定制化设计了LongSet数据类型。这个数据类型是一个存储Long类型元素的集合它的底层数据结构是一个Hash数组。在设计LongSet类型之前微博是用Hash集合类型来保存用户关注列表但是Hash集合类型在保存大量数据时内存空间消耗较大。
而且当缓存的关注列表被从Redis中淘汰时缓存实例需要从后台数据库中读取用户关注列表再用HMSET写入Hash集合在并发请求压力大的场景下这个过程会降低缓存性能。跟Hash集合相比LongSet类型底层使用Hash数组保存数据既避免了Hash表较多的指针开销节省内存空间也可以实现快速存取。
从刚才介绍的改进工作你可以看到微博对Redis进行优化的出发点和我们在前面课程中反复强调的Redis优化目标是一致的。我自己也总结了两个经验。
第一个经验是高性能和省内存始终都是应用Redis要关注的重点这和Redis在整个业务系统中的位置是密切相关的。
Redis通常是作为缓存在数据库层前端部署就需要能够快速地返回结果。另外Redis使用内存保存数据一方面带来了访问速度快的优势另一方面也让我们在运维时需要特别关注内存优化。我在前面的课程里介绍了很多和性能优化、节省内存相关的内容比如说第1820讲你可以重点回顾下并且真正地在实践中应用起来。
第二个经验是在实际应用中需要基于Redis做定制化工作或二次开发来满足一些特殊场景的需求就像微博定制化数据结构。不过如果要进行定制化或二次开发就需要了解和掌握Redis源码。所以我建议你在掌握了Redis的基本原理和关键技术后把阅读Redis源码作为下一个目标。这样一来你既可以结合原理来加强对源码的理解还可以在掌握源码后开展新增功能或数据类型的开发工作。对于如何在Redis中新增数据类型我在[第13讲](https://time.geekbang.org/column/article/281745)中向你介绍过,你可以再复习下。
除了这些改进工作为了满足大容量存储需求微博专家还在技术分享中提到他们把RocksDB和硬盘结合使用以扩大单实例的容量我们来了解下。
## 微博如何应对大容量数据存储需求?
微博业务层要保存的数据经常会达到TB级别这就需要扩大Redis实例的存储容量了。
针对这个需求微博对数据区分冷热度把热数据保留在Redis中而把冷数据通过RocksDB写入底层的硬盘。
在微博的业务场景中冷热数据是比较常见的。比如说有些微博话题刚发生时热度非常高会有海量的用户访问这些话题使用Redis服务用户请求就非常有必要。
但是等到话题热度过了之后访问人数就会急剧下降这些数据就变为冷数据了。这个时候冷数据就可以从Redis迁移到RocksDB保存在硬盘中。这样一来Redis实例的内存就可以节省下来保存热数据同时单个实例能保存的数据量就由整个硬盘的大小来决定了。
根据微博的技术分享我画了一张他们使用RocksDB辅助Redis实现扩容的架构图
<img src="https://static001.geekbang.org/resource/image/c0/b0/c0fdb8248a3362afd29d3efe8b6b21b0.jpg" alt="">
从图中可以看到Redis是用异步线程在RocksDB中读写数据。
读写RocksDB的延迟毕竟比不上Redis的内存访问延迟这样做也是为了避免读写冷数据时阻塞Redis主线程。至于冷数据在SSD上的布局和管理都交给RocksDB负责。RocksDB目前已经比较成熟和稳定了可以胜任Redis冷数据管理这个工作。
关于微博使用RocksDB和SSD进行扩容的优化工作我也总结了两条经验想和你分享一下。
首先,**实现大容量的单实例在某些业务场景下还是有需求的。**虽然我们可以使用切片集群的多实例分散保存数据,但是这种方式也会带来集群运维的开销,涉及到分布式系统的管理和维护。而且,切片集群的规模会受限,如果能增加单个实例的存储容量,那么,即使在使用较小规模的集群时,集群也能保存更多的数据。
第二个经验是如果想实现大容量的Redis实例**借助于SSD和RocksDB来实现是一个不错的方案**。我们在[第28讲](https://time.geekbang.org/column/article/298205)中学习的360开源的Pika还有微博的做法都是非常好的参考。
RocksDB可以实现快速写入数据同时使用内存缓存部分数据也可以提供万级别的数据读取性能。而且当前SSD的性能提升很快单块SSD的盘级IOPS可以达到几十万级别。这些技术结合起来Redis就能够在提供大容量数据存储的同时保持一定的读写性能。当你有相同的需求时也可以把基于SSD的RocksDB应用起来保存大容量数据。
## 面向多业务线微博如何将Redis服务化
微博的不同业务对Redis容量的需求不一样而且可能会随着业务的变化出现扩容和缩容的需求。
为了能够灵活地支持这些业务需求微博对Redis进行了服务化改造RedisService。所谓服务化就是指使用Redis集群来服务不同的业务场景需求每一个业务拥有独立的资源相互不干扰。
同时所有的Redis实例形成一个资源池资源池本身也能轻松地扩容。如果有新业务上线或是旧业务下线就可以从资源池中申请资源或者是把不用的资源归还到资源池中。
形成了Redis服务之后不同业务线在使用Redis时就非常方便了。不用业务部门再去独立部署和运维只要让业务应用客户端访问Redis服务集群就可以。即使业务应用的数据量增加了也不用担心实例容量问题服务集群本身可以自动在线扩容来支撑业务的发展。
在Redis服务化的过程中微博采用了类似Codis的方案通过集群代理层来连接客户端和服务器端。从微博的公开技术资料中可以看到他们在代理层中实现了丰富的服务化功能支持。
- 客户端连接监听和端口自动增删。
- Redis协议解析确定需要路由的请求如果是非法和不支持的请求直接返回错误。
- 请求路由:根据数据和后端实例间的映射规则,将请求路由到对应的后端实例进行处理,并将结果返回给客户端。
- 指标采集监控:采集集群运行的状态,并发送到专门的可视化组件,由这些组件进行监控处理。
此外,在服务化集群中,还有一个配置中心,它用来管理整个集群的元数据。同时,实例会按照主从模式运行,保证数据的可靠性。不同业务的数据部署到不同的实例上,相互之间保持隔离。
按照我的理解画了一张示意图显示了微博Redis服务化集群的架构你可以看下。
<img src="https://static001.geekbang.org/resource/image/58/ff/58dc7b26b8b0a1df4fd1faeee24618ff.jpg" alt="">
从Redis服务化的实践中我们可以知道当多个业务线有共同的Redis使用需求时提供平台级服务是一种通用做法也就是服务化。
当把一个通用功能做成平台服务时,我们需要重点考虑的问题,包括**平台平滑扩容、多租户支持和业务数据隔离、灵活的路由规则、丰富的监控功能**等。
如果要进行平台扩容我们可以借助Codis或是Redis Cluster的方法来实现。多租户支持和业务隔离的需求是一致我们需要通过资源隔离来实现这两个需求也就是把不同租户或不同业务的数据分开部署避免混用资源。对于路由规则和监控功能来说微博目前的方案是不错的也就是在代理层proxy中来完成这两个功能。
只有很好地实现了这些功能,一个平台服务才能高效地支撑不同业务线的需求。
## 小结
今天这节课我们学习了微博的Redis实践从中总结了许多经验。总结来说微博对Redis的技术需求可以概括为3点分别是高性能、大容量和易扩展。
为了满足这些需求除了对Redis进行优化微博也在自研扩展系统包括基于RocksDB的容量扩展机制以及服务化的RedisService集群。
最后,我还想再跟你分享一下我自己的两个感受。
第一个是关于微博做的RedisService集群这个优化方向是大厂平台部门同学的主要工作方向。
业务纵切、平台横切是当前构建大规模系统的基本思路。所谓业务纵切是指把不同的业务数据单独部署这样可以避免相互之间的干扰。而平台横切是指当不同业务线对运行平台具有相同需求时可以统一起来通过构建平台级集群服务来进行支撑。Redis就是典型的多个业务线都需要的基础性服务所以将其以集群方式服务化有助于提升业务的整体效率。
第二个是代码实践在我们成长为Redis高手过程中的重要作用。
我发现对Redis的二次改造或开发是大厂的一个必经之路这和大厂业务多、需求广有密切关系。
微博做的定制化数据结构、RedRock和RedisService都是非常典型的例子。所以如果我们想要成为Redis高手成为大厂中的一员那么先原理后代码边学习边实践就是一个不错的方法。原理用来指导代码阅读的聚焦点而动手实践至关重要需要我们同时开展部署操作实践和阅读代码实践。纸上得来终觉浅绝知此事要躬行希望你不仅重视学习原理还要真正地用原理来指导实践提升自己的实战能力。
## 每课一问
按照惯例我给你提个小问题你在实际应用Redis时有没有一些经典的优化改进或二次开发经验
欢迎你在留言区聊一聊你的经验,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你把今天的内容分享给你的朋友或同事。

View File

@@ -0,0 +1,99 @@
<audio id="audio" title="加餐(三)| 用户Kaito我希望成为在压力中成长的人" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/75/30/75349a12b6a1d218ba9e8c5301f90d30.mp3"></audio>
你好我是Kaito。
上一次我分享了我总结的Redis学习路径在留言区的交流和互动中我有了很多新的收获。今天我想再分享一下我对学习这件事儿的认识以及我的学习方法包括领先一步的心理建设、事半功倍的学习方法以及提升效率的小技巧。
## 领先一步:保持好奇+不设限
我认为,任何领域的学习,在研究具体的方法之前,我们都需要先在心理上领先别人一步。什么意思呢?其实就是要建立并保持好奇心,并且不给自己设限。
我发现,很多人是缺乏好奇心的,突出表现在只知其然,不知其所以然,不善于思考和挖掘问题。
给你举个小例子。刚开始接触Redis时你肯定听说过一句话**Redis是单线程高性能**。很多人听完也就过去了但是有好奇心的人会进一步思考“单线程如何处理多个客户端的网络请求呢采用单线程的话只能用到一个CPU核心怎么达到高性能呢
顺着这个思路去学习的话你就会发现Redis虽然采用了单线程但是它使用了多路复用技术可以处理多个客户端的网络请求。而且它的数据都存储在内存中再加上高效的数据结构所以处理每个请求的速度极快。
你看,带着好奇心去看问题,最终我们得到的远远超出想象。所以,我们要**永远保持好奇心和深入探究的精神**,它是我们不断进步的核心驱动力。
我要说的第二点,就是**不要给自己设限**。
**不要没有做任何尝试,就先去说“我做不到”**。如果你这样做,就相当于提前放弃了自己的成长机会。我特别喜欢的一个心态是:“**我现在虽然不会,但是只要给我时间,我就能学会它。**”
说到这儿,我想给你分享一个我的小故事。
之前我在业务部门做开发时大部分时间都在写业务代码对Redis也只停留在“会用”的层面并不了解它的原理更别说分析和定位性能问题了。
后来一个偶然的机会我可以去公司的基础架构部门做数据库中间件相关的工作。我当时非常犹豫一方面我知道这个工作要求熟练掌握Redis的方方面面难度非常高我觉得我可能无法胜任但另一方面我也非常想踏出舒适区突破一下自己。最终我还是选择了接受挑战。
刚开始时我确实遭遇了难以想象的困难比如说不熟悉Redis的运行原理、看Redis源码时一头雾水、在系统发生问题时不知所措等等。还好面对压力我的斗志被激发了于是就疯狂地恶补数据结构、网络协议、操作系统等知识一行行地去啃源码……
真正走出舒适区之后,我看到了自己的飞速成长和进步,不仅很快就胜任了新工作,而且,我越来越自信了。之后,每次遇到新问题的时候,我再也不会害怕了,因为我知道,只要花时间去研究,就可以搞定一切。
所以,我真的想和你说,面对任何可以让自己成长的机会,都不要轻易错过,一定不要给自己设限。你要相信,你的潜能会随着你面临的压力而被激发出来,而且它的威力巨大!
## 事半功倍:行之有效的学习方法
有了强烈的学习意愿还不够,我们还要快速地找到科学有效的学习方法,这样才能事半功倍。接下来,我就聊聊我的学习方法。
首先我们要学会快速地搜集自己需要的资料。在搜索的时候我们要尽量简化检索的内容避免无用的关键词例如如果想要搜索“Redis哨兵集群在选举时是如何达成共识的”这个问题我一般会搜索“Redis sentinel raft”这样只搜索重点词汇得到的结果会更多也更符合我们想要的结果。
如果在查资料时,遇到了细节问题,找不到答案,不要犹豫,**一定要去看源码**。源码是客观的,是最细节的表现,**不要只会从别人那里获取东西,要学着自己动手觅食**,而源码,往往能够给我们提供清晰易懂的答案。
比如说Redis的String数据类型底层是由SDS实现的当要修改的value长度发生变更时如果原来的内存空间不足以存储新内容SDS就需要重新申请内存空间进行扩容那么每次扩容时会申请多大的内存呢
如果你看到了`sds.c`中的sdsMakeRoomFor函数就会知道当需要申请的内存空间小于1MB时SDS会申请1倍的内存空间这样就可以避免后面频繁申请内存而导致的性能开销当需要申请的内存空间大于1MB时为了避免内存浪费每次扩容时就只申请1MB的内存空间。类似于这样的问题我们都能很快地从源码中找到答案。
很多人都觉得看源码很难不愿意走出这一步刚开始我也是这样的但是后来有一天我突然想到了“二八定律”。我所理解的“二八定律”就是80%的人甘于平庸遇到稍微难一点的问题就会停下脚步而另外20%的人一直不愿意停留在舒适区只要确定了目标就会一直向前走。我当然希望自己是那20%的人。所以每次我觉得有压力、有难度的时候我就会告诉自己得坚持下去这样才能超越80%的人。不得不说,这招儿还挺有用的。
另外,我还想说,掌握新知识最好的方式,就是把它讲给别人听,或者是写成文章。
尤其是在写文章的时候,我们需要确定文章的结构,梳理知识点的脉络,还要组织语言,在这个过程中,我们会把一些零碎的内容转化为体系化、结构化的知识。那些散乱的点,会形成一棵“知识树”,这不仅方便我们记忆,而且,在复习的时候,只需要找到“树干”,就能延伸到“枝叶”,举一反三。
而且,在梳理的过程中,我们往往还能发现自己的知识漏洞,或者是对某些内容有了新的认识和见解。
例如我在写《Redis如何持久化数据》这篇文章的时候就已经知道了RDB+AOF两种方式但在写的过程中我发现自己并不清楚具体的细节比如为什么生成的RDB文件这么小这是如何做到的RDB和AOF分别适合用在什么场景中呢
翻阅源码之后我才发现RDB文件之所以很小一方面是因为它存储的是二进制数据另一方面Redis针对不同的数据类型做了进一步的压缩处理例如数字采用int编码存储这就进一步节省了存储空间。所以RDB更适合做定时的快照备份和主从全量数据同步而AOF是实时记录的每个变更操作更适合用在对数据完整性和安全性要求更高的业务场景中。
这种用输出反哺输入的方式,也是强化收获的一种有效手段,我真心建议你也试一试。
## 持续精进:做好精力管理
拥有了好奇心,也找到了合适的方法,也并不是万事大吉了。我们可能还会面临一个问题:“我非常想把某个技术学好,但是我总是被一些事情打断,而且有的时候总想犯懒,这该怎么办呢?”
其实这是一个效率问题。人天生是有惰性的所以我们需要借助一些东西去督促我们前进。想一下工作时什么时候效率最高是不是接近deadline的时候呢
这就说明当我们有目标、有压力的时候才会有动力、有效率地去执行。所以我常用的一个方法是在学习某个领域的知识时我会先按照从易到难的顺序把它拆解成一个个大的模块确定大框架的学习目标接着我会继续细化每个模块细化到一看到这个任务就知道立马应该做什么的程度。同时我还会给每项任务制定一个deadline。
简单举个例子。我在学习Redis的基础数据类型时首先确定了String、List、Hash、Set、Sorted Set这五大模块。接着我又对每个模块继续进行拆分例如Hash类型底层实现可以拆分成“压缩列表+哈希表”这两种数据结构实现,接下来,我就继续细化这两个模块的内容,最终确定了一个个小目标。
怎么完成这些小目标呢?我采用的方式是用**番茄工作法**。
我会把这些细化的目标加入到番茄任务中并且排列好优先级。随后我会在工作日晚上或者周末抽出一整块的时间去完成这些小目标。在开启番茄钟时我会迅速集中精力去完成这些任务。同时我会把手机静音放在自己够不到的地方。等一个番茄钟25分钟结束后休息5分钟调整下状态然后再投入到一个番茄任务中。
在实施的过程中,我们可能会遇到一些阻碍,比如说某个任务比想象中的难。这个时候,我会尝试多用几个番茄钟去攻克它,或者是把它的优先级向后放,先完成其他的番茄任务,最后再花时间去解决比较难的问题。
长时间使用这种方法,我发现,我的效率非常高。而且,把番茄任务一个个划掉之后,也会有一些小小的成就感,这种成就感会激励我持续学习。
最后,我还想再说一点,就是要**投入足够多的时间**。不要总是抱怨想要的得不到,在抱怨之前,你要先想一想,有没有远超出他人的投入和付出。想要走在别人的前面,就要准备好投入足够多的时间。
有时候,可能你会觉得,学习某一个领域的技术感觉很枯燥,因为细节很多、很繁琐,但这都是很正常的。现在我们所看到的每一项技术,都是开发者多年的总结和提炼的成果,它本身就是严肃的,你必须花足够多的时间去分析、研究、思考,没有捷径。
千万不要指望着借助碎片化学习,搞懂某个领域的知识。我所说的碎片化有两层含义:一是指内容碎片化,二是指时间碎片化。
不知道你没有遇到这种情况,当你看完一篇技术文章时,可能以为自己已经掌握了这些知识点,但是,如果别人稍微问一下相关的知识点,你可能就答不上来了。这就是因为,你学到的东西是碎片化的,并没有形成知识体系。
但是,只有系统化学习,你才能看到这项技术的全貌,会更清晰边界,知道它适合做什么,不适合做什么,为什么会这样去设计。
另一方面,不要幻想着只在地铁上学一会儿,就能把它学会,这样就有点太高估自己了。因为在很短的时间内,我们没有办法深入地去思考,去深入了解这个知识点的前因后果。你必须在晚上或者周末抽出一整块时间,去理清每个知识点之间的关系和边界,必要时还需要动手实践。
因此,如果真正想去要握某项技术,就必须需要付出整块的时间去学习,而且,必须是系统化的学习。
## 总结
今天,我跟你分享了我的一些学习总结,包括领先别人一步的心理建设,事半功倍的学习方法,以及持续精进的精力管理方法。
**这些道理其实很简单也很容易理解但是能真正做到的也只有20%的人,甚至是更少。所以,希望我们都能真正地行动起来,进步的路很长,我们一定要让自己在路上。**
最后,希望这些内容对你有所帮助,我也很期待你在留言区聊一聊你的学习方法或习惯,我们一起交流和进步。

View File

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

View File

@@ -0,0 +1,125 @@
<audio id="audio" title="加餐(五) | Redis有哪些好用的运维工具" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e9/6a/e97c6221e55eb47fe68bd89bb9yy086a.mp3"></audio>
你好,我是蒋德钧。
今天的加餐我来给你分享一些好用的Redis运维工具。
我们在应用Redis时经常会面临的运维工作包括Redis的运行状态监控数据迁移主从集群、切片集群的部署和运维。接下来我就从这三个方面给你介绍一些工具。我们先来学习下监控Redis实时运行状态的工具这些工具都用到了Redis提供的一个监控命令INFO。
## 最基本的监控命令INFO命令
**Redis本身提供的INFO命令会返回丰富的实例运行监控信息这个命令是Redis监控工具的基础**
INFO命令在使用时可以带一个参数section这个参数的取值有好几种相应的INFO命令也会返回不同类型的监控信息。我把INFO命令的返回信息分成5大类其中有的类别当中又包含了不同的监控内容如下表所示
<img src="https://static001.geekbang.org/resource/image/8f/a8/8fb2ef487fd9b7073fd062d480b220a8.jpg" alt="">
在监控Redis运行状态时INFO命令返回的结果非常有用。如果你想了解INFO命令的所有参数返回结果的详细含义可以查看Redis[官网](https://redis.io/commands/info)的介绍。这里,我给你提几个运维时需要重点关注的参数以及它们的重要返回结果。
首先,**无论你是运行单实例或是集群我建议你重点关注一下stat、commandstat、cpu和memory这四个参数的返回结果**这里面包含了命令的执行情况比如命令的执行次数和执行时间、命令使用的CPU资源内存资源的使用情况比如内存已使用量、内存碎片率CPU资源使用情况等这可以帮助我们判断实例的运行状态和资源消耗情况。
另外当你启用RDB或AOF功能时你就需要重点关注下persistence参数的返回结果你可以通过它查看到RDB或者AOF的执行情况。
如果你在使用主从集群就要重点关注下replication参数的返回结果这里面包含了主从同步的实时状态。
不过INFO命令只是提供了文本形式的监控结果并没有可视化所以在实际应用中我们还可以使用一些第三方开源工具将INFO命令的返回结果可视化。接下来我要讲的Prometheus就可以通过插件将Redis的统计结果可视化。
## 面向Prometheus的Redis-exporter监控
[Prometheus](https://prometheus.io/)是一套开源的系统监控报警框架。它的核心功能是从被监控系统中拉取监控数据,结合[Grafana](https://grafana.com/)工具进行可视化展示。而且监控数据可以保存到时序数据库中以便运维人员进行历史查询。同时Prometheus会检测系统的监控指标是否超过了预设的阈值一旦超过阈值Prometheus就会触发报警。
对于系统的日常运维管理来说这些功能是非常重要的。而Prometheus已经实现了使用这些功能的工具框架。我们只要能从被监控系统中获取到监控数据就可以用Prometheus来实现运维监控。
Prometheus正好提供了插件功能来实现对一个系统的监控我们把插件称为exporter每一个exporter实际是一个采集监控数据的组件。exporter采集的数据格式符合Prometheus的要求Prometheus获取这些数据后就可以进行展示和保存了。
[Redis-exporter](https://github.com/oliver006/redis_exporter)就是用来监控Redis的它将INFO命令监控到的运行状态和各种统计信息提供给Prometheus从而进行可视化展示和报警设置。目前Redis-exporter可以支持Redis 2.0至6.0版本,适用范围比较广。
除了获取Redis实例的运行状态Redis-exporter还可以监控键值对的大小和集合类型数据的元素个数这个可以在运行Redis-exporter时使用check-keys的命令行选项来实现。
此外我们可以开发一个Lua脚本定制化采集所需监控的数据。然后我们使用scripts命令行选项让Redis-exporter运行这个特定的脚本从而可以满足业务层的多样化监控需求。
最后,我还想再给你分享两个小工具:[redis-stat](https://github.com/junegunn/redis-stat)和[Redis Live](https://github.com/snakeliwei/RedisLive)。跟Redis-exporter相比这两个都是轻量级的监控工具。它们分别是用Ruby和Python开发的也是将INFO命令提供的实例运行状态信息可视化展示。虽然这两个工具目前已经很少更新了不过如果你想自行开发Redis监控工具它们都是不错的参考。
除了监控Redis的运行状态还有一个常见的运维任务就是数据迁移。接下来我们再来学习下数据迁移的工具。
## 数据迁移工具Redis-shake
有时候,我们需要在不同的实例间迁移数据。目前,比较常用的一个数据迁移工具是[Redis-shake](https://github.com/aliyun/redis-shake)这是阿里云Redis和MongoDB团队开发的一个用于Redis数据同步的工具。
Redis-shake的基本运行原理是先启动Redis-shake进程这个进程模拟了一个Redis实例。然后Redis-shake进程和数据迁出的源实例进行数据的全量同步。
这个过程和Redis主从实例的全量同步是类似的。
源实例相当于主库Redis-shake相当于从库源实例先把RDB文件传输给Redis-shakeRedis-shake会把RDB文件发送给目的实例。接着源实例会再把增量命令发送给Redis-shakeRedis-shake负责把这些增量命令再同步给目的实例。
下面这张图展示了Redis-shake进行数据迁移的过程
<img src="https://static001.geekbang.org/resource/image/02/5b/027f6ae0276d483650ee4d5179f19c5b.jpg" alt="">
**Redis-shake的一大优势就是支持多种类型的迁移。**
**首先,它既支持单个实例间的数据迁移,也支持集群到集群间的数据迁移**
**其次**有的Redis切片集群例如Codis会使用proxy接收请求操作Redis-shake也同样支持和proxy进行数据迁移。
**另外**因为Redis-shake是阿里云团队开发的所以除了支持开源的Redis版本以外Redis-shake还支持云下的Redis实例和云上的Redis实例进行迁移可以帮助我们实现Redis服务上云的目标。
**在数据迁移后,我们通常需要对比源实例和目的实例中的数据是否一致**。如果有不一致的数据,我们需要把它们找出来,从目的实例中剔除,或者是再次迁移这些不一致的数据。
这里,我就要再给你介绍一个数据一致性比对的工具了,就是阿里云团队开发的[Redis-full-check](https://github.com/aliyun/redis-full-check)。
Redis-full-check的工作原理很简单就是对源实例和目的实例中的数据进行全量比对从而完成数据校验。不过为了降低数据校验的比对开销Redis-full-check采用了多轮比较的方法。
在第一轮校验时Redis-full-check会找出在源实例上的所有key然后从源实例和目的实例中把相应的值也都查找出来进行比对。第一次比对后redis-full-check会把目的实例中和源实例不一致的数据记录到sqlite数据库中。
从第二轮校验开始Redis-full-check只比较上一轮结束后记录在数据库中的不一致的数据。
为了避免对实例的正常请求处理造成影响Redis-full-check在每一轮比对结束后会暂停一段时间。随着Redis-shake增量同步的进行源实例和目的实例中的不一致数据也会逐步减少所以我们校验比对的轮数不用很多。
我们可以自己设置比对的轮数。具体的方法是在运行redis-full-check命令时把参数comparetimes的值设置为我们想要比对的轮数。
等到所有轮数都比对完成后,数据库中记录的数据就是源实例和目的实例最终的差异结果了。
这里有个地方需要注意下Redis-full-check提供了三种比对模式我们可以通过comparemode参数进行设置。comparemode参数有三种取值含义如下
- KeyOutline只对比key值是否相等
- ValueOutline只对比value值的长度是否相等
- FullValue对比key值、value长度、value值是否相等。
我们在应用Redis-full-check时可以根据业务对数据一致性程度的要求选择相应的比对模式。如果一致性要求高就把comparemode参数设置为FullValue。
好了最后我再向你介绍一个用于Redis集群运维管理的工具CacheCloud。
## 集群管理工具CacheCloud
[CacheCloud](https://github.com/sohutv/cachecloud)是搜狐开发的一个面向Redis运维管理的云平台它**实现了主从集群、哨兵集群和Redis Cluster的自动部署和管理**,用户可以直接在平台的管理界面上进行操作。
针对常见的集群运维需求CacheCloud提供了5个运维操作。
- 下线实例:关闭实例以及实例相关的监控任务。
- 上线实例:重新启动已下线的实例,并进行监控。
- 添加从节点:在主从集群中给主节点添加一个从节点。
- 故障切换手动完成Redis Cluster主从节点的故障转移。
- 配置管理:用户提交配置修改的工单后,管理员进行审核,并完成配置修改。
当然作为运维管理平台CacheCloud除了提供运维操作以外还提供了丰富的监控信息。
CacheCloud不仅会收集INFO命令提供的实例实时运行状态信息进行可视化展示而且还会把实例运行状态信息保存下来例如内存使用情况、客户端连接数、键值对数据量。这样一来当Redis运行发生问题时运维人员可以查询保存的历史记录并结合当时的运行状态信息进行分析。
如果你希望有一个统一平台把Redis实例管理相关的任务集中托管起来CacheCloud是一个不错的工具。
## 小结
这节课我给你介绍了几种Redis的运维工具。
我们先了解了Redis的INFO命令这个命令是监控工具的基础监控工具都会基于INFO命令提供的信息进行二次加工。我们还学习了3种用来监控Redis实时运行状态的运维工具分别是Redis-exporter、redis-stat和Redis Live。
关于数据迁移我们既可以使用Redis-shake工具也可以通过RDB文件或是AOF文件进行迁移。
在运维Redis时刚刚讲到的多款开源工具已经可以满足我们的不少需求了。但是有时候不同业务线对Redis运维的需求可能并不一样直接使用现成的开源工具可能无法满足全部需求在这种情况下建议你基于开源工具进行二次开发或是自研从而更好地满足业务使用需求。
## 每课一问
按照惯例,我给你提个小问题:你在实际应用中还使用过什么好的运维工具吗?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。

View File

@@ -0,0 +1,177 @@
<audio id="audio" title="加餐(六)| Redis的使用规范小建议" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bf/a4/bff585ayy164fd5c3a454f1bfbe32ba4.mp3"></audio>
你好,我是蒋德钧。
今天的加餐我们来聊一个轻松点儿的话题我来给你介绍一下Redis的使用规范包括键值对使用、业务数据保存和命令使用规范。
毕竟高性能和节省内存是我们的两个目标只有规范地使用Redis才能真正实现这两个目标。如果说之前的内容教会了你怎么用那么今天的内容就是帮助你用好Redis尽量不出错。
好了,话不多说,我们来看下键值对的使用规范。
## 键值对使用规范
关于键值对的使用规范,我主要想和你说两个方面:
1. key的命名规范只有命名规范才能提供可读性强、可维护性好的key方便日常管理
1. value的设计规范包括避免bigkey、选择高效序列化方法和压缩方法、使用整数对象共享池、数据类型选择。
### 规范一key的命名规范
一个Redis实例默认可以支持16个数据库我们可以把不同的业务数据分散保存到不同的数据库中。
但是在使用不同数据库时客户端需要使用SELECT命令进行数据库切换相当于增加了一个额外的操作。
其实我们可以通过合理命名key减少这个操作。具体的做法是把业务名作为前缀然后用冒号分隔再加上具体的业务数据名。这样一来我们可以通过key的前缀区分不同的业务数据就不用在多个数据库间来回切换了。
我给你举个简单的小例子看看具体怎么命名key。
比如说如果我们要统计网页的独立访客量就可以用下面的代码设置key这就表示这个数据对应的业务是统计unique visitor独立访客量而且对应的页面编号是1024。
```
uv:page:1024
```
这里有一个地方需要注意一下。key本身是字符串底层的数据结构是SDS。SDS结构中会包含字符串长度、分配空间大小等元数据信息。从Redis 3.2版本开始,**当key字符串的长度增加时SDS中的元数据也会占用更多内存空间**。
所以,**我们在设置key的名称时要注意控制key的长度**。否则如果key很长的话就会消耗较多内存空间而且SDS元数据也会额外消耗一定的内存空间。
SDS结构中的字符串长度和元数据大小的对应关系如下表所示
<img src="https://static001.geekbang.org/resource/image/5a/0c/5afd9d57af8edd1ae69e62b1c998050c.jpg" alt="">
为了减少key占用的内存空间我给你一个小建议对于业务名或业务数据名可以使用相应的英文单词的首字母表示比如user用u表示message用m或者是用缩写表示例如unique visitor使用uv
### 规范二避免使用bigkey
Redis是使用单线程读写数据bigkey的读写操作会阻塞线程降低Redis的处理效率。所以在应用Redis时关于value的设计规范非常重要的一点就是避免bigkey。
bigkey通常有两种情况。
- 情况一键值对的值大小本身就很大例如value为1MB的String类型数据。为了避免String类型的bigkey在业务层我们要尽量把String类型的数据大小控制在10KB以下。
- 情况二键值对的值是集合类型集合元素个数非常多例如包含100万个元素的Hash集合类型数据。为了避免集合类型的bigkey我给你的设计规范建议是**尽量把集合类型的元素个数控制在1万以下**。
当然这些建议只是为了尽量避免bigkey如果业务层的String类型数据确实很大我们还可以通过数据压缩来减小数据大小如果集合类型的元素的确很多我们可以将一个大集合拆分成多个小集合来保存。
这里还有个地方需要注意下Redis的4种集合类型List、Hash、Set和Sorted Set在集合元素个数小于一定的阈值时会使用内存紧凑型的底层数据结构进行保存从而节省内存。例如假设Hash集合的hash-max-ziplist-entries配置项是1000如果Hash集合元素个数不超过1000就会使用ziplist保存数据。
紧凑型数据结构虽然可以节省内存但是会在一定程度上导致数据的读写性能下降。所以如果业务应用更加需要保持高性能访问而不是节省内存的话在不会导致bigkey的前提下你就不用刻意控制集合元素个数了。
### 规范三:使用高效序列化方法和压缩方法
为了节省内存除了采用紧凑型数据结构以外我们还可以遵循两个使用规范分别是使用高效的序列化方法和压缩方法这样可以减少value的大小。
Redis中的字符串都是使用二进制安全的字节数组来保存的所以我们可以把业务数据序列化成二进制数据写入到Redis中。
但是,**不同的序列化方法,在序列化速度和数据序列化后的占用内存空间这两个方面,效果是不一样的**。比如说protostuff和kryo这两种序列化方法就要比Java内置的序列化方法java-build-in-serializer效率更高。
此外业务应用有时会使用字符串形式的XML和JSON格式保存数据。
这样做的好处是,这两种格式的可读性好,便于调试,不同的开发语言都支持这两种格式的解析。
缺点在于XML和JSON格式的数据占用的内存空间比较大。为了避免数据占用过大的内存空间我建议使用压缩工具例如snappy或gzip把数据压缩后再写入Redis这样就可以节省内存空间了。
### 规范四:使用整数对象共享池
整数是常用的数据类型Redis内部维护了0到9999这1万个整数对象并把这些整数作为一个共享池使用。
换句话说如果一个键值对中有0到9999范围的整数Redis就不会为这个键值对专门创建整数对象了而是会复用共享池中的整数对象。
这样一来即使大量键值对保存了0到9999范围内的整数在Redis实例中其实只保存了一份整数对象可以节省内存空间。
基于这个特点,我建议你,在满足业务数据需求的前提下,能用整数时就尽量用整数,这样可以节省实例内存。
那什么时候不能用整数对象共享池呢?主要有两种情况。
第一种情况是,**如果Redis中设置了maxmemory而且启用了LRU策略allkeys-lru或volatile-lru策略那么整数对象共享池就无法使用了**。这是因为LRU策略需要统计每个键值对的使用时间如果不同的键值对都共享使用一个整数对象LRU策略就无法进行统计了。
第二种情况是如果集合类型数据采用ziplist编码而集合元素是整数这个时候也不能使用共享池。因为ziplist使用了紧凑型内存结构判断整数对象的共享情况效率低。
好了,到这里,我们了解了和键值对使用相关的四种规范,遵循这四种规范,最直接的好处就是可以节省内存空间。接下来,我们再来了解下,在实际保存数据时,该遵循哪些规范。
## 数据保存规范
### 规范一使用Redis保存热数据
为了提供高性能访问Redis是把所有数据保存到内存中的。
虽然Redis支持使用RDB快照和AOF日志持久化保存数据但是这两个机制都是用来提供数据可靠性保证的并不是用来扩充数据容量的。而且内存成本本身就比较高如果把业务数据都保存在Redis中会带来较大的内存成本压力。
所以一般来说在实际应用Redis时我们会更多地把它作为缓存保存热数据这样既可以充分利用Redis的高性能特性还可以把宝贵的内存资源用在服务热数据上就是俗话说的“好钢用在刀刃上”。
### 规范二:不同的业务数据分实例存储
虽然我们可以使用key的前缀把不同业务的数据区分开但是如果所有业务的数据量都很大而且访问特征也不一样我们把这些数据保存在同一个实例上时这些数据的操作就会相互干扰。
你可以想象这样一个场景假如数据采集业务使用Redis保存数据时以写操作为主而用户统计业务使用Redis时是以读查询为主如果这两个业务数据混在一起保存读写操作相互干扰肯定会导致业务响应变慢。
那么,**我建议你把不同的业务数据放到不同的 Redis 实例中**。这样一来,既可以避免单实例的内存使用量过大,也可以避免不同业务的操作相互干扰。
### 规范三:在数据保存时,要设置过期时间
对于Redis来说内存是非常宝贵的资源而且Redis通常用于保存热数据。热数据一般都有使用的时效性。
所以在数据保存时我建议你根据业务使用数据的时长设置数据的过期时间。不然的话写入Redis的数据会一直占用内存如果数据持续增多就可能达到机器的内存上限造成内存溢出导致服务崩溃。
### 规范四控制Redis实例的容量
Redis单实例的内存大小都不要太大根据我自己的经验值建议你设置在 2~6GB 。这样一来无论是RDB快照还是主从集群进行数据同步都能很快完成不会阻塞正常请求的处理。
## 命令使用规范
最后我们再来看下在使用Redis命令时要遵守什么规范。
### 规范一:线上禁用部分命令
Redis 是单线程处理请求操作如果我们执行一些涉及大量操作、耗时长的命令就会严重阻塞主线程导致其它请求无法得到正常处理这类命令主要有3种。
- **KEYS**按照键值对的key内容进行匹配返回符合匹配条件的键值对该命令需要对Redis的全局哈希表进行全表扫描严重阻塞Redis主线程
- **FLUSHALL**删除Redis实例上的所有数据如果数据量很大会严重阻塞Redis主线程
- **FLUSHDB**删除当前数据库中的数据如果数据量很大同样会阻塞Redis主线程。
所以我们在线上应用Redis时就需要禁用这些命令。**具体的做法是管理员用rename-command命令在配置文件中对这些命令进行重命名让客户端无法使用这些命令**。
当然你还可以使用其它命令替代这3个命令。
- 对于KEYS命令来说你可以用SCAN命令代替KEYS命令分批返回符合条件的键值对避免造成主线程阻塞
- 对于FLUSHALL、FLUSHDB命令来说你可以加上ASYNC选项让这两个命令使用后台线程异步删除数据可以避免阻塞主线程。
### 规范二慎用MONITOR命令
Redis的MONITOR命令在执行后会持续输出监测到的各个命令操作所以我们通常会用MONITOR命令返回的结果检查命令的执行情况。
但是MONITOR命令会把监控到的内容持续写入输出缓冲区。如果线上命令的操作很多输出缓冲区很快就会溢出了这就会对Redis性能造成影响甚至引起服务崩溃。
所以除非十分需要监测某些命令的执行例如Redis性能突然变慢我们想查看下客户端执行了哪些命令你可以偶尔在短时间内使用下MONITOR命令否则我建议你不要使用MONITOR命令。
### 规范三:慎用全量操作命令
对于集合类型的数据来说如果想要获得集合中的所有元素一般不建议使用全量操作的命令例如Hash类型的HGETALL、Set类型的SMEMBERS。这些操作会对Hash和Set类型的底层数据结构进行全量扫描如果集合类型数据较多的话就会阻塞Redis主线程。
如果想要获得集合类型的全量数据,我给你三个小建议。
- 第一个建议是你可以使用SSCAN、HSCAN命令分批返回集合中的数据减少对主线程的阻塞。
- 第二个建议是你可以化整为零把一个大的Hash集合拆分成多个小的Hash集合。这个操作对应到业务层就是对业务数据进行拆分按照时间、地域、用户ID等属性把一个大集合的业务数据拆分成多个小集合数据。例如当你统计用户的访问情况时就可以按照天的粒度把每天的数据作为一个Hash集合。
- 最后一个建议是如果集合类型保存的是业务数据的多个属性而每次查询时也需要返回这些属性那么你可以使用String类型将这些属性序列化后保存每次直接返回String数据就行不用再对集合类型做全量扫描了。
## 小结
这节课我围绕Redis应用时的高性能访问和节省内存空间这两个目标分别在键值对使用、命令使用和数据保存三方面向你介绍了11个规范。
我按照强制、推荐、建议这三个类别,把这些规范分了下类,如下表所示:
<img src="https://static001.geekbang.org/resource/image/f2/fd/f2513c69d7757830e7f3e3c831fcdcfd.jpg" alt="">
我来解释一下这3个类别的规范。
- 强制类别的规范这表示如果不按照规范内容来执行就会给Redis的应用带来极大的负面影响例如性能受损。
- 推荐类别的规范:这个规范的内容能有效提升性能、节省内存空间,或者是增加开发和运维的便捷性,你可以直接应用到实践中。
- 建议类别的规范:这类规范内容和实际业务应用相关,我只是从我的经历或经验给你一个建议,你需要结合自己的业务场景参考使用。
我再多说一句你一定要熟练掌握这些使用规范并且真正地把它们应用到你的Redis使用场景中提高Redis的使用效率。
## 每课一问
按照惯例我给你提个小问题你在日常应用Redis时有遵循过什么好的使用规范吗
欢迎在留言区分享一下你常用的使用规范,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。

View File

@@ -0,0 +1,213 @@
<audio id="audio" title="加餐(四) | Redis客户端如何与服务器端交换命令和数据" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f3/34/f35b889aaba2673aedec5379yyf6d434.mp3"></audio>
你好,我是蒋德钧。
在前面的课程中我们主要学习了Redis服务器端的机制和关键技术很少涉及到客户端的问题。但是Redis采用的是典型的client-server服务器端-客户端)架构,客户端会发送请求给服务器端,服务器端会返回响应给客户端。
如果要对Redis客户端进行二次开发比如增加新的命令我们就需要了解请求和响应涉及的命令、数据在客户端和服务器之间传输时是如何编码的。否则我们在客户端新增的命令就无法被服务器端识别和处理。
Redis使用RESPREdis Serialization Protocol协议定义了客户端和服务器端交互的命令、数据的编码格式。在Redis 2.0版本中RESP协议正式成为客户端和服务器端的标准通信协议。从Redis 2.0 到Redis 5.0RESP协议都称为RESP 2协议从Redis 6.0开始Redis就采用RESP 3协议了。不过6.0版本是在今年5月刚推出的所以目前我们广泛使用的还是RESP 2协议。
这节课我就向你重点介绍下RESP 2协议的规范要求以及RESP 3相对RESP 2的改进之处。
首先,我们先来看下客户端和服务器端交互的内容包括哪些,毕竟,交互内容不同,编码形式也不一样。
## 客户端和服务器端交互的内容有哪些?
为了方便你更加清晰地理解RESP 2协议是如何对命令和数据进行格式编码的我们可以把交互内容分成客户端请求和服务器端响应两类
- 在客户端请求中客户端会给Redis发送命令以及要写入的键和值
- 而在服务器端响应中Redis实例会返回读取的值、OK标识、成功写入的元素个数、错误信息以及命令例如Redis Cluster中的MOVE命令
其实,这些交互内容还可以再进一步细分成七类,我们再来了解下它们。
1. **命令**这就是针对不同数据类型的操作命令。例如对String类型的SET、GET操作对Hash类型的HSET、HGET等这些命令就是代表操作语义的字符串。
1. **键**:键值对中的键,可以直接用字符串表示。
1. **单个值**对应String类型的数据数据本身可以是字符串、数值整数或浮点数布尔值True或是False等。
1. **集合值**对应List、Hash、Set、Sorted Set类型的数据不仅包含多个值而且每个值也可以是字符串、数值或布尔值等。
1. **OK回复**对应命令操作成功的结果就是一个字符串的“OK”。
1. **整数回复**这里有两种情况。一种是命令操作返回的结果是整数例如LLEN命令返回列表的长度另一种是集合命令成功操作时实际操作的元素个数例如SADD命令返回成功添加的元素个数。
1. **错误信息**命令操作出错时的返回结果包括“error”标识以及具体的错误信息。
了解了这7类内容都是什么下面我再结合三个具体的例子帮助你进一步地掌握这些交互内容。
先看第一个例子,来看看下面的命令:
```
#成功写入String类型数据返回OK
127.0.0.1:6379&gt; SET testkey testvalue
OK
```
这里的交互内容就包括了**命令**SET命令、**键(<strong>String类型的键testkey和**单个值</strong>String类型的值testvalue而服务器端则直接返回一个**OK回复**。
第二个例子是执行HSET命令
```
#成功写入Hash类型数据返回实际写入的集合元素个数
127.0.0.1:6379&gt;HSET testhash a 1 b 2 c 3
(integer) 3
```
这里的交互内容包括三个key-value的Hash**集合值**a 1 b 2 c 3而服务器端返回**整数回复**3表示操作成功写入的元素个数。
最后一个例子是执行PUT命令如下所示
```
#发送的命令不对,报错,并返回错误信息
127.0.0.1:6379&gt;PUT testkey2 testvalue
(error) ERR unknown command 'PUT', with args beginning with: 'testkey', 'testvalue'
```
可以看到,这里的交互内容包括**错误信息,**这是因为Redis实例本身不支持PUT命令所以服务器端报错“error”并返回具体的错误信息也就是未知的命令“put”。
好了到这里你了解了Redis客户端和服务器端交互的内容。接下来我们就来看下RESP 2是按照什么样的格式规范来对这些内容进行编码的。
## RESP 2的编码格式规范
RESP 2协议的设计目标是希望Redis开发人员实现客户端时简单方便这样就可以减少客户端开发时出现的Bug。而且当客户端和服务器端交互出现问题时希望开发人员可以通过查看协议交互过程能快速定位问题方便调试。为了实现这一目标RESP 2协议采用了可读性很好的文本形式进行编码也就是通过一系列的字符串来表示各种命令和数据。
不过交互内容有多种而且实际传输的命令或数据也会有很多个。针对这两种情况RESP 2协议在编码时设计了两个基本规范。
1. 为了对不同类型的交互内容进行编码RESP 2协议实现了5种编码格式类型。同时为了区分这5种编码类型RESP 2使用一个专门的字符作为每种编码类型的开头字符。这样一来客户端或服务器端在对编码后的数据进行解析时就可以直接通过开头字符知道当前解析的编码类型。
1. RESP 2进行编码时会按照单个命令或单个数据的粒度进行编码并在每个编码结果后面增加一个换行符“\r\n”有时也表示成CRLF表示一次编码结束。
接下来我就来分别介绍下这5种编码类型。
**1.简单字符串类型RESP Simple Strings**
这种类型就是用一个字符串来进行编码比如请求操作在服务器端成功执行后的OK标识回复就是用这种类型进行编码的。
当服务器端成功执行一个操作后返回的OK标识就可以编码如下
```
+OK\r\n
```
**2.长字符串类型RESP Bulk String**
这种类型是用一个二进制安全的字符串来进行编码。这里的二进制安全其实是相对于C语言中对字符串的处理方式来说的。我来具体解释一下。
Redis在解析字符串时不会像C语言那样使用“`\0`”判定一个字符串的结尾Redis会把 “`\0`”解析成正常的0字符并使用额外的属性值表示字符串的长度。
举个例子对于“Redis\0Cluster\0”这个字符串来说C语言会解析为“Redis”而Redis会解析为“Redis Cluster”并用len属性表示字符串的真实长度是14字节如下图所示
<img src="https://static001.geekbang.org/resource/image/b4/7a/b4e98e2ecf00b42098a790cec363fc7a.jpg" alt="">
这样一来,字符串中即使存储了“`\0`”字符也不会导致Redis解析到“`\0`”时,就认为字符串结束了从而停止解析,这就保证了数据的安全性。和长字符串类型相比,简单字符串就是非二进制安全的。
长字符串类型最大可以达到512MB所以可以对很大的数据量进行编码正好可以满足键值对本身的数据量需求所以RESP 2就用这种类型对交互内容中的键或值进行编码并且使用“`$`”字符作为开头字符,`$`字符后面会紧跟着一个数字,这个数字表示字符串的实际长度。
例如我们使用GET命令读取一个键假设键为testkey的值假设值为testvalue服务端返回的String值编码结果如下其中`$`字符后的9表示数据长度为9个字符。
```
$9 testvalue\r\n
```
**3.整数类型RESP Integer**
这种类型也还是一个字符串但是表示的是一个有符号64位整数。为了和包含数字的简单字符串类型区分开整数类型使用“`:`”字符作为开头字符,可以用于对服务器端返回的整数回复进行编码。
例如在刚才介绍的例子中我们使用HSET命令设置了testhash的三个元素服务器端实际返回的编码结果如下
```
:3\r\n
```
**4.错误类型RESP Errors**
它是一个字符串包括了错误类型和具体的错误信息。Redis服务器端报错响应就是用这种类型进行编码的。RESP 2使用“`-`”字符作为它的开头字符。
例如在刚才的例子中我们在redis-cli执行PUT testkey2 testvalue命令报错服务器端实际返回给客户端的报错编码结果如下
```
-ERR unknown command `PUT`, with args beginning with: `testkey`, `testvalue`
```
其中ERR就是报错类型表示是一个通用错误ERR后面的文字内容就是具体的报错信息。
**5.数组编码类型RESP Arrays**
这是一个包含多个元素的数组其中元素的类型可以是刚才介绍的这4种编码类型。
在客户端发送请求和服务器端返回结果时数组编码类型都能用得上。客户端在发送请求操作时一般会同时包括命令和要操作的数据。而数组类型包含了多个元素所以就适合用来对发送的命令和数据进行编码。为了和其他类型区分RESP 2使用“`*`”字符作为开头字符。
例如我们执行命令GET testkey此时客户端发送给服务器端的命令的编码结果就是使用数组类型编码的如下所示
```
*2\r\n$3\r\nGET\r\n$7\r\ntestkey\r\n
```
其中,**第一个`*`字符标识当前是数组类型的编码结果**2表示该数组有2个元素分别对应命令GET和键testkey。命令GET和键testkey都是使用长字符串类型编码的所以用`$`字符加字符串长度来表示。
类似地,当服务器端返回包含多个元素的集合类型数据时,也会用`*`字符和元素个数作为标识,并用长字符串类型对返回的集合元素进行编码。
好了到这里你了解了RESP 2协议的5种编码类型和相应的开头字符我在下面的表格里做了小结你可以看下。
<img src="https://static001.geekbang.org/resource/image/46/ce/4658d36cdb64a846fe1732a29c45b3ce.jpg" alt="">
Redis 6.0中使用了RESP 3协议对RESP 2.0做了改进,我们来学习下具体都有哪些改进。
## RESP 2的不足和RESP 3的改进
虽然我们刚刚说RESP 2协议提供了5种编码类型看起来很丰富其实是不够的。毕竟基本数据类型还包括很多种例如浮点数、布尔值等。编码类型偏少会带来两个问题。
一方面在值的基本数据类型方面RESP 2只能区分字符串和整数对于其他的数据类型客户端使用RESP 2协议时就需要进行额外的转换操作。例如当一个浮点数用字符串表示时客户端需要将字符串中的值和实际数字值比较判断是否为数字值然后再将字符串转换成实际的浮点数。
另一方面RESP 2用数组类别编码表示所有的集合类型但是Redis的集合类型包括了List、Hash、Set和Sorted Set。当客户端接收到数组类型编码的结果时还需要根据调用的命令操作接口来判断返回的数组究竟是哪一种集合类型。
我来举个例子。假设有一个Hash类型的键是testhash集合元素分别为a:1、b:2、c:3。同时有一个Sorted Set类型的键是testzset集合元素分别是a、b、c它们的分数分别是1、2、3。我们在redis-cli客户端中读取它们的结果时返回的形式都是一个数组如下所示
```
127.0.0.1:6379&gt;HGETALL testhash
1) &quot;a&quot;
2) &quot;1&quot;
3) &quot;b&quot;
4) &quot;2&quot;
5) &quot;c&quot;
6) &quot;3&quot;
127.0.0.1:6379&gt;ZRANGE testzset 0 3 withscores
1) &quot;a&quot;
2) &quot;1&quot;
3) &quot;b&quot;
4) &quot;2&quot;
5) &quot;c&quot;
6) &quot;3&quot;
```
为了在客户端按照Hash和Sorted Set两种类型处理代码中返回的数据客户端还需要根据发送的命令操作HGETALL和ZRANGE来把这两个编码的数组结果转换成相应的Hash集合和有序集合增加了客户端额外的开销。
从Redis 6.0版本开始RESP 3协议增加了对多种数据类型的支持包括空值、浮点数、布尔值、有序的字典集合、无序的集合等。RESP 3也是通过不同的开头字符来区分不同的数据类型例如当开头第一个字符是“`,`”,就表示接下来的编码结果是浮点数。这样一来,客户端就不用再通过额外的字符串比对,来实现数据转换操作了,提升了客户端的效率。
## 小结
这节课我们学习了RESP 2协议。这个协议定义了Redis客户端和服务器端进行命令和数据交互时的编码格式。RESP 2提供了5种类型的编码格式包括简单字符串类型、长字符串类型、整数类型、错误类型和数组类型。为了区分这5种类型RESP 2协议使用了5种不同的字符作为这5种类型编码结果的第一个字符分别是`+``$`、:、-和*。
RESP 2协议是文本形式的协议实现简单可以减少客户端开发出现的Bug而且可读性强便于开发调试。当你需要开发定制化的Redis客户端时就需要了解和掌握RESP 2协议。
RESP 2协议的一个不足就是支持的类型偏少所以Redis 6.0版本使用了RESP 3协议。和RESP 2协议相比RESP 3协议增加了对浮点数、布尔类型、有序字典集合、无序集合等多种类型数据的支持。不过这里有个地方需要你注意Redis 6.0只支持RESP 3对RESP 2协议不兼容所以如果你使用Redis 6.0版本需要确认客户端已经支持了RESP 3协议否则将无法使用Redis 6.0。
最后我也给你提供一个小工具。如果你想查看服务器端返回数据的RESP 2编码结果就可以使用telnet命令和redis实例连接执行如下命令就行
```
telnet 实例IP 实例端口
```
接着你可以给实例发送命令这样就能看到用RESP 2协议编码后的返回结果了。当然你也可以在telnet中向Redis实例发送用RESP 2协议编写的命令操作实例同样能处理你可以课后试试看。
## 每课一问
按照惯例我给你提个小问题假设Redis实例中有一个List类型的数据key为mylistvalue是使用LPUSH命令写入List集合的5个元素依次是1、2、3.3、4、hello当执行LRANGE mylist 0 4命令时实例返回给客户端的编码结果是怎样的
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。