mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 06:03:45 +08:00
mod
This commit is contained in:
81
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(一)| 经典的Redis学习资料有哪些?.md
Normal file
81
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(一)| 经典的Redis学习资料有哪些?.md
Normal 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学习资料都有哪些呢?欢迎在留言区分享一下,我们一起进步。另外,如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。
|
||||
130
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(七) | 从微博的Redis实践中,我们可以学到哪些经验?.md
Normal file
130
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(七) | 从微博的Redis实践中,我们可以学到哪些经验?.md
Normal 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==&mid=2650782429&idx=1&sn=7f2df520a7295a002c4a59f6aea9e7f3&chksm=f3f90f48c48e865e478d936d76c5303663c98da506f221ede85f0f9250e5f897f24896147cfb&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使用内存保存数据,一方面带来了访问速度快的优势,另一方面,也让我们在运维时需要特别关注内存优化。我在前面的课程里介绍了很多和性能优化、节省内存相关的内容(比如说第18~20讲),你可以重点回顾下,并且真正地在实践中应用起来。
|
||||
|
||||
第二个经验是,在实际应用中需要基于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时,有没有一些经典的优化改进或二次开发经验?
|
||||
|
||||
欢迎你在留言区聊一聊你的经验,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你把今天的内容分享给你的朋友或同事。
|
||||
99
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(三)| 用户Kaito:我希望成为在压力中成长的人.md
Normal file
99
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(三)| 用户Kaito:我希望成为在压力中成长的人.md
Normal 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%的人,甚至是更少。所以,希望我们都能真正地行动起来,进步的路很长,我们一定要让自己在路上。**
|
||||
|
||||
最后,希望这些内容对你有所帮助,我也很期待你在留言区聊一聊你的学习方法或习惯,我们一起交流和进步。
|
||||
138
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(二)| 用户Kaito:我是如何学习Redis的?.md
Normal file
138
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(二)| 用户Kaito:我是如何学习Redis的?.md
Normal 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的呢?希望你能在留言区聊聊你的学习方法,我们一起交流。
|
||||
125
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(五) | Redis有哪些好用的运维工具?.md
Normal file
125
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(五) | Redis有哪些好用的运维工具?.md
Normal 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-shake,Redis-shake会把RDB文件发送给目的实例。接着,源实例会再把增量命令发送给Redis-shake,Redis-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运维的需求可能并不一样,直接使用现成的开源工具可能无法满足全部需求,在这种情况下,建议你基于开源工具进行二次开发或是自研,从而更好地满足业务使用需求。
|
||||
|
||||
## 每课一问
|
||||
|
||||
按照惯例,我给你提个小问题:你在实际应用中还使用过什么好的运维工具吗?
|
||||
|
||||
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。
|
||||
177
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(六)| Redis的使用规范小建议.md
Normal file
177
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(六)| Redis的使用规范小建议.md
Normal 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时,有遵循过什么好的使用规范吗?
|
||||
|
||||
欢迎在留言区分享一下你常用的使用规范,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。
|
||||
213
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(四) | Redis客户端如何与服务器端交换命令和数据?.md
Normal file
213
极客时间专栏/Redis核心技术与实战/加餐篇/加餐(四) | Redis客户端如何与服务器端交换命令和数据?.md
Normal 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使用RESP(REdis Serialization Protocol)协议定义了客户端和服务器端交互的命令、数据的编码格式。在Redis 2.0版本中,RESP协议正式成为客户端和服务器端的标准通信协议。从Redis 2.0 到Redis 5.0,RESP协议都称为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> SET testkey testvalue
|
||||
OK
|
||||
|
||||
```
|
||||
|
||||
这里的交互内容就包括了**命令**(SET命令)、**键(<strong>String类型的键testkey)和**单个值</strong>(String类型的值testvalue),而服务器端则直接返回一个**OK回复**。
|
||||
|
||||
第二个例子是执行HSET命令:
|
||||
|
||||
```
|
||||
#成功写入Hash类型数据,返回实际写入的集合元素个数
|
||||
127.0.0.1:6379>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>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>HGETALL testhash
|
||||
1) "a"
|
||||
2) "1"
|
||||
3) "b"
|
||||
4) "2"
|
||||
5) "c"
|
||||
6) "3"
|
||||
|
||||
127.0.0.1:6379>ZRANGE testzset 0 3 withscores
|
||||
1) "a"
|
||||
2) "1"
|
||||
3) "b"
|
||||
4) "2"
|
||||
5) "c"
|
||||
6) "3"
|
||||
|
||||
```
|
||||
|
||||
为了在客户端按照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为mylist,value是使用LPUSH命令写入List集合的5个元素,依次是1、2、3.3、4、hello,当执行LRANGE mylist 0 4命令时,实例返回给客户端的编码结果是怎样的?
|
||||
|
||||
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。
|
||||
Reference in New Issue
Block a user