From 2a4f95308de1424dcb61deb3fda2bb6af64a2748 Mon Sep 17 00:00:00 2001 From: guide Date: Sat, 14 Aug 2021 09:16:39 +0800 Subject: [PATCH] =?UTF-8?q?[feat]=20=E8=A1=A5=E5=85=85=20redis=20=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/database/Redis/redis-all.md | 118 ++++++++++--------------------- 1 file changed, 38 insertions(+), 80 deletions(-) diff --git a/docs/database/Redis/redis-all.md b/docs/database/Redis/redis-all.md index 64ea49d5..93227374 100644 --- a/docs/database/Redis/redis-all.md +++ b/docs/database/Redis/redis-all.md @@ -1,45 +1,5 @@ -点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java 面试突击》以及 Java 工程师必备学习资源。 - - - - -- [1. 简单介绍一下 Redis 呗!](#1-简单介绍一下-redis-呗) -- [2. 分布式缓存常见的技术选型方案有哪些?](#2-分布式缓存常见的技术选型方案有哪些) -- [3. 说一下 Redis 和 Memcached 的区别和共同点](#3-说一下-redis-和-memcached-的区别和共同点) -- [4. 缓存数据的处理流程是怎样的?](#4-缓存数据的处理流程是怎样的) -- [5. 为什么要用 Redis/为什么要用缓存?](#5-为什么要用-redis为什么要用缓存) -- [6. Redis 常见数据结构以及使用场景分析](#6-redis-常见数据结构以及使用场景分析) - - [6.1. string](#61-string) - - [6.2. list](#62-list) - - [6.3. hash](#63-hash) - - [6.4. set](#64-set) - - [6.5. sorted set](#65-sorted-set) - - [6.6 bitmap](#66-bitmap) -- [7. Redis 单线程模型详解](#7-redis-单线程模型详解) -- [8. Redis 没有使用多线程?为什么不使用多线程?](#8-redis-没有使用多线程为什么不使用多线程) -- [9. Redis6.0 之后为何引入了多线程?](#9-redis60-之后为何引入了多线程) -- [10. Redis 给缓存数据设置过期时间有啥用?](#10-redis-给缓存数据设置过期时间有啥用) -- [11. Redis 是如何判断数据是否过期的呢?](#11-redis-是如何判断数据是否过期的呢) -- [12. 过期的数据的删除策略了解么?](#12-过期的数据的删除策略了解么) -- [13. Redis 内存淘汰机制了解么?](#13-redis-内存淘汰机制了解么) -- [14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)](#14-redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复) -- [15. Redis 事务](#15-redis-事务) -- [16. 缓存穿透](#16-缓存穿透) - - [16.1. 什么是缓存穿透?](#161-什么是缓存穿透) - - [16.2. 缓存穿透情况的处理流程是怎样的?](#162-缓存穿透情况的处理流程是怎样的) - - [16.3. 有哪些解决办法?](#163-有哪些解决办法) -- [17. 缓存雪崩](#17-缓存雪崩) - - [17.1. 什么是缓存雪崩?](#171-什么是缓存雪崩) - - [17.2. 有哪些解决办法?](#172-有哪些解决办法) -- [18. 如何保证缓存和数据库数据的一致性?](#18-如何保证缓存和数据库数据的一致性) -- [19. 参考](#19-参考) -- [20. 公众号](#20-公众号) - - - - -### 1. 简单介绍一下 Redis 呗! +### 简单介绍一下 Redis 呗! 简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。 @@ -47,7 +7,7 @@ **Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。** -### 2. 分布式缓存常见的技术选型方案有哪些? +### 分布式缓存常见的技术选型方案有哪些? 分布式缓存的话,使用的比较多的主要是 **Memcached** 和 **Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**。 @@ -55,7 +15,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来 分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。 -### 3. 说一下 Redis 和 Memcached 的区别和共同点 +### 说一下 Redis 和 Memcached 的区别和共同点 现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!不过,了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据! @@ -78,7 +38,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来 相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。 -### 4. 缓存数据的处理流程是怎样的? +### 缓存数据的处理流程是怎样的? 作为暖男一号,我给大家画了一个草图。 @@ -91,7 +51,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来 3. 数据库中存在的话就更新缓存中的数据。 4. 数据库中不存在的话就返回空数据。 -### 5. 为什么要用 Redis/为什么要用缓存? +### 为什么要用 Redis/为什么要用缓存? _简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。_ @@ -117,13 +77,21 @@ _简单,来说使用缓存主要是为了提升用户体验以及应对更多 由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。 -### 6. Redis 常见数据结构以及使用场景分析 +### Redis 除了做缓存,还能做什么? + +- **分布式锁** : 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。相关阅读:[《分布式锁中的王者方案 - Redisson》](https://mp.weixin.qq.com/s/CbnPRfvq4m1sqo2uKI6qQw)。 +- **限流** :一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》](https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA)。 +- **消息队列** :Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。 +- **复杂业务场景** :通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。 +- ...... + +### Redis 常见数据结构以及使用场景分析 你可以自己本机安装 redis 或者通过 redis 官网提供的[在线 redis 环境](https://try.redis.io/)。 ![try-redis](./images/redis-all/try-redis.png) -#### 6.1. string +#### string 1. **介绍** :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 **简单动态字符串**(simple dynamic string,**SDS**)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。 2. **常用命令:** `set,get,strlen,exists,decr,incr,setex` 等等。 @@ -184,7 +152,7 @@ OK (integer) 56 ``` -#### 6.2. list +#### list 1. **介绍** :**list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 2. **常用命令:** `rpush,lpop,lpush,rpop,lrange,llen` 等。 @@ -245,7 +213,7 @@ OK (integer) 3 ``` -#### 6.3. hash +#### hash 1. **介绍** :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,**特别适合用于存储对象**,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。 2. **常用命令:** `hset,hmset,hexists,hget,hgetall,hkeys,hvals` 等。 @@ -282,7 +250,7 @@ OK "GuideGeGe" ``` -#### 6.4. set +#### set 1. **介绍 :** set 类似于 Java 中的 `HashSet` 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。 2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。 @@ -310,7 +278,7 @@ OK 1) "value2" ``` -#### 6.5. sorted set +#### sorted set 1. **介绍:** 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。 2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。 @@ -337,7 +305,7 @@ OK 2) "value2" ``` -#### 6.6 bitmap +#### bitmap 1. **介绍:** bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。 2. **常用命令:** `setbit` 、`getbit` 、`bitcount`、`bitop` @@ -417,7 +385,7 @@ BITOP operation destkey key [key ...] 只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。 -### 7. Redis 单线程模型详解 +### Redis 单线程模型详解 **Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型** (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。 @@ -450,7 +418,7 @@ Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(

《Redis设计与实现:12章》

-### 8. Redis 没有使用多线程?为什么不使用多线程? +### Redis 没有使用多线程?为什么不使用多线程? 虽然说 Redis 是单线程模型,但是,实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。** @@ -468,7 +436,7 @@ Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接( 2. Redis 的性能瓶颈不在 CPU ,主要在内存和网络; 3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。 -### 9. Redis6.0 之后为何引入了多线程? +### Redis6.0 之后为何引入了多线程? **Redis6.0 引入多线程主要是为了提高网络 IO 读写性能**,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。 @@ -491,7 +459,7 @@ io-threads 4 #官网建议4核的机器建议设置为2或3个线程,8核的 1. [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw) 2. [为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/) -### 10. Redis 给缓存数据设置过期时间有啥用? +### Redis 给缓存数据设置过期时间有啥用? 一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢? @@ -516,7 +484,7 @@ OK 如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。 -### 11. Redis 是如何判断数据是否过期的呢? +### Redis 是如何判断数据是否过期的呢? Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。 @@ -534,7 +502,7 @@ typedef struct redisDb { } redisDb; ``` -### 12. 过期的数据的删除策略了解么? +### 过期的数据的删除策略了解么? 如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢? @@ -549,7 +517,7 @@ typedef struct redisDb { 怎么解决这个问题呢?答案就是:**Redis 内存淘汰机制。** -### 13. Redis 内存淘汰机制了解么? +### Redis 内存淘汰机制了解么? > 相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据? @@ -567,7 +535,7 @@ Redis 提供 6 种数据淘汰策略: 7. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 8. **allkeys-lfu(least frequently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key -### 14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复) +### Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复) 很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 @@ -629,7 +597,7 @@ AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的 在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。 -### 15. Redis 事务 +### Redis 事务 Redis 可以通过 **`MULTI`,`EXEC`,`DISCARD` 和 `WATCH`** 等命令来实现事务(transaction)功能。 @@ -704,19 +672,19 @@ Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis - [issue452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) 。 - [Issue491:关于 redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491) -### 16. 缓存穿透 +### 缓存穿透 -#### 16.1. 什么是缓存穿透? +#### 什么是缓存穿透? 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。 -#### 16.2. 缓存穿透情况的处理流程是怎样的? +#### 缓存穿透情况的处理流程是怎样的? 如下图所示,用户的请求最终都要跑到数据库中查询一遍。 ![缓存穿透情况](./images/redis-all/缓存穿透情况.png) -#### 16.3. 有哪些解决办法? +#### 有哪些解决办法? 最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。 @@ -777,9 +745,9 @@ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理 更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。 -### 17. 缓存雪崩 +### 缓存雪崩 -#### 17.1. 什么是缓存雪崩? +#### 什么是缓存雪崩? 我发现缓存雪崩这名字起的有点意思,哈哈。 @@ -791,7 +759,7 @@ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理 举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。 -#### 17.2. 有哪些解决办法? +#### 有哪些解决办法? **针对 Redis 服务不可用的情况:** @@ -803,7 +771,7 @@ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理 1. 设置不同的失效时间比如随机设置缓存的失效时间。 2. 缓存永不失效。 -### 18. 如何保证缓存和数据库数据的一致性? +### 如何保证缓存和数据库数据的一致性? 细说的话可以扯很多,但是我觉得其实没太大必要(小声 BB:很多解决方案我也没太弄明白)。我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。 @@ -816,20 +784,10 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删 1. **缓存失效时间变短(不推荐,治标不治本)** :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。 2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。 -### 19. 参考 +### 参考 - 《Redis 开发与运维》 - 《Redis 设计与实现》 - Redis 命令总结:http://Redisdoc.com/string/set.html - 通俗易懂的 Redis 数据结构基础教程:[https://juejin.im/post/5b53ee7e5188251aaa2d2e16](https://juejin.im/post/5b53ee7e5188251aaa2d2e16) - WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153) - -### 20. 公众号 - -如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 - -**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java 面试突击"** 即可免费领取! - -**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 - -![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) \ No newline at end of file