1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-20 22:17:09 +08:00

[refractor]redis部分格式

This commit is contained in:
guide 2020-07-27 21:00:46 +08:00
parent 3d14dd85f7
commit 5052833b92
2 changed files with 152 additions and 79 deletions

View File

@ -1,5 +1,4 @@
非常感谢《redis实战》真本书本文大多内容也参考了书中的内容。非常推荐大家看一下《redis实战》这本书感觉书中的很多理论性东西还是很不错的。
为什么本文的名字要加上春夏秋冬又一春,哈哈 ,这是一部韩国的电影,我感觉电影不错,所以就用在文章名字上了,没有什么特别的含义,然后下面的有些配图也是电影相关镜头。
@ -10,12 +9,10 @@
Redis不同于Memcached的很重一点就是**Redis支持持久化**而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照snapshottingRDB**,另一种方式是**只追加文件append-only file,AOF**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
## 快照snapshotting持久化
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后可以对快照进行备份可以将快照复制到其他服务器从而创建具有相同数据的服务器副本Redis主从结构主要用来提高Redis性能还可以将快照留在原地以便重启服务器的时候使用。
![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f97568281782a?w=600&h=329&f=jpeg&s=88616)
**快照持久化是Redis默认采用的持久化方式**在redis.conf配置文件中默认有此下配置
@ -46,6 +43,7 @@ save 60 10000 #在60秒(1分钟)之后如果至少有10000个key发生
## **AOFappend-only file持久化**
与快照持久化相比AOF持久化 的实时性更好因此已成为主流的持久化方案。默认情况下Redis没有开启AOFappend only file方式的持久化可以通过appendonly参数开启
```
appendonly yes
```

View File

@ -1,22 +1,59 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java 面试突击》以及 Java 工程师必备学习资源。
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
<!-- code_chunk_output -->
- [1. 简单说说有哪些本地缓存解决方案?](#1-简单说说有哪些本地缓存解决方案)
- [2. 为什么要有分布式缓存?/为什么不直接用本地缓存?](#2-为什么要有分布式缓存为什么不直接用本地缓存)
- [3. 分布式缓存有哪些常见的技术选型方案呢?](#3-分布式缓存有哪些常见的技术选型方案呢)
- [4. 简单介绍一下 Redis 呗!](#4-简单介绍一下-redis-呗)
- [5. 说一下 Redis 和 Memcached 的区别和共同点](#5-说一下-redis-和-memcached-的区别和共同点)
- [6. 缓存数据的处理流程是怎样的?](#6-缓存数据的处理流程是怎样的)
- [7. 为什么要用 Redis/为什么要用缓存?](#7-为什么要用-redis为什么要用缓存)
- [8. Redis 常见数据结构以及使用场景分析](#8-redis-常见数据结构以及使用场景分析)
- [8.1. string](#81-string)
- [8.2. list](#82-list)
- [8.3. hash](#83-hash)
- [8.4. set](#84-set)
- [8.5. sorted set](#85-sorted-set)
- [9. Redis 单线程模型详解](#9-redis-单线程模型详解)
- [10. Redis 没有使用多线程?为什么不使用多线程?](#10-redis-没有使用多线程为什么不使用多线程)
- [11. Redis6.0 之后为何引入了多线程?](#11-redis60-之后为何引入了多线程)
- [12. Redis 设置过期时间](#12-redis-设置过期时间)
- [13. Redis 内存淘汰机制(MySQL 里有 2000w 数据Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?)](#13-redis-内存淘汰机制mysql-里有-2000w-数据redis-中只存-20w-的数据如何保证-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. 如何解决 Redis 的并发竞争 Key 问题](#18-如何解决-redis-的并发竞争-key-问题)
- [19. 如何保证缓存与数据库双写时的数据一致性?](#19-如何保证缓存与数据库双写时的数据一致性)
- [20. 参考](#20-参考)
- [公众号](#公众号)
<!-- /code_chunk_output -->
*正式开始 Redis 之前,我们先来回顾一下在 Java 后端领域,有哪些可以用来当做缓存。*
### 简单说说有哪些本地缓存解决方案?
### 1. 简单说说有哪些本地缓存解决方案?
*先来聊聊本地缓存,这个实际在很多项目中用的蛮多,特别是单体架构的时候。数据量不大,并且没有分布式要求的话,使用本地缓存还是可以的。*
_先来聊聊本地缓存,这个实际在很多项目中用的蛮多,特别是单体架构的时候。数据量不大,并且没有分布式要求的话,使用本地缓存还是可以的。_
常见的单体架构图如下,我们使用 **Nginx** 来做**负载均衡**,部署两个相同的服务到服务器,两个服务使用同一个数据库,并且使用的是本地缓存。
![单体架构](./images/redis/单体架构.png)
*那本地缓存的方案有哪些呢?且听 Guide 给你来说一说。*
_那本地缓存的方案有哪些呢且听 Guide 给你来说一说。_
**一JDK 自带的 `HashMap``ConcurrentHashMap`了。**
`ConcurrentHashMap` 可以看作是线程安全版本的 `HashMap` ,两者都是存放 key/value 形式的键值对。但是,大部分场景来说不会使用这两者当做缓存,因为只提供了缓存的功能,并没有提供其他诸如过期时间之类的功能。一个稍微完善一点的缓存框架至少要提供:过期时间、淘汰机制、命中率统计这三点。
`ConcurrentHashMap` 可以看作是线程安全版本的 `HashMap` ,两者都是存放 key/value 形式的键值对。但是,大部分场景来说不会使用这两者当做缓存,因为只提供了缓存的功能,并没有提供其他诸如过期时间之类的功能。一个稍微完善一点的缓存框架至少要提供:过期时间、淘汰机制、命中率统计这三点。
**二:`Ehcache``Guava Cache``Spring Cache`这三者是使用的比较多的本地缓存框架。**
@ -30,15 +67,15 @@
**三 `Caffeine`**
相比于 `Guava`来说 `Caffeine` 在各个方面比如性能要更加优秀,一般建议使用其来替代 `Guava`。并且, `Guava` `Caffeine` 的使用方式很像!
相比于 `Guava`来说 `Caffeine` 在各个方面比如性能要更加优秀,一般建议使用其来替代 `Guava`。并且, `Guava``Caffeine` 的使用方式很像!
本地缓存固然好,但是缺陷也很明显,比如多个相同服务之间的本地缓存的数据无法共享。
*下面我们从为什么要有分布式缓存为接入点来正式进入 Redis 的相关问题总结。*
_下面我们从为什么要有分布式缓存为接入点来正式进入 Redis 的相关问题总结。_
### 为什么要有分布式缓存?/为什么不直接用本地缓存?
### 2. 为什么要有分布式缓存?/为什么不直接用本地缓存?
*我们可以把分布式缓存Distributed Cache 看作是一种内存数据库的服务,它的最终作用就是提供缓存数据的服务。*
_我们可以把分布式缓存Distributed Cache 看作是一种内存数据库的服务,它的最终作用就是提供缓存数据的服务。_
如下图所示,就是一个简单的使用分布式缓存的架构图。我们使用 Nginx 来做负载均衡,部署两个相同的服务到服务器,两个服务使用同一个数据库和缓存。
@ -55,7 +92,7 @@
使用分布式缓存的缺点呢,也很显而易见,那就是你需要为分布式缓存引入额外的服务比如 Redis 或 Memcached你需要单独保证 Redis 或 Memcached 服务的高可用。
### 分布式缓存有哪些常见的技术选型方案呢?
### 3. 分布式缓存有哪些常见的技术选型方案呢?
分布式缓存的话,使用的比较多的主要是 **Memcached****Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**
@ -63,7 +100,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
### 简单介绍一下 Redis 呗!
### 4. 简单介绍一下 Redis 呗!
简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
@ -71,7 +108,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
**Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。**
### 说一下 Redis 和 Memcached 的区别和共同点
### 5. 说一下 Redis 和 Memcached 的区别和共同点
现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!不过,了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据!
@ -89,11 +126,11 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
4. **Redis 在服务器内存使用完之后可以将不用的数据放到磁盘上。但是Memcached 在服务器内存使用完之后,就会直接报异常。**
5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.**
6. **Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。** Redis 6.0 引入了多线程 IO
7. **Redis 支持发布订阅模型、Lua脚本、事务等功能而Memcached不支持。并且Redis支持更多的编程语言。**
7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。**
相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached来作为自己项目的分布式缓存了。
相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。
### 缓存数据的处理流程是怎样的?
### 6. 缓存数据的处理流程是怎样的?
作为暖男一号,我给大家画了一个草图。
@ -106,9 +143,9 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
3. 数据库中存在的话就更新缓存中的数据。
4. 数据库中不存在的话就返回空数据。
### 为什么要用 Redis/为什么要用缓存?
### 7. 为什么要用 Redis/为什么要用缓存?
*简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。*
_简单来说使用缓存主要是为了提升用户体验以及应对更多的用户。_
下面我们主要从“高性能”和“高并发”这两点来看待这个问题。
@ -116,7 +153,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
**高性能**
对照上面👆我画的图。我们设想这样的场景:
对照上面 👆 我画的图。我们设想这样的场景:
假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
@ -126,22 +163,22 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
**高并发:**
一般像 MySQL这类的数据库的 QPS 大概都在 1w 左右4核8g ,但是使用 Redis 缓存之后很容易达到 10w+甚至最高能达到30w+就单机redis的情况redis 集群的话会更高)。
一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右4 8g ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况redis 集群的话会更高)。
> QPSQuery Per Second服务器每秒可以执行的查询次数
所以,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高的系统整体的并发。
### Redis 常见数据结构以及使用场景分析
### 8. Redis 常见数据结构以及使用场景分析
你可以自己本机安装 redis 或者通过 redis 官网提供的[在线 redis 环境](https://try.redis.io/)。
![try-redis](./images/redis/try-redis.png)
#### string
#### 8.1. 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,dect,incr,setex` 等等。
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,dect,incr,setex` 等等。
3. **应用场景** :一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
下面我们简单看看它的使用!
@ -200,11 +237,11 @@ OK
(integer) 56
```
#### list
#### 8.2. list
1. **介绍** **list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**但是C语言并没有实现链表所以Redis实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
1. **介绍** **list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
2. **常用命令:** `rpush,lpop,lpush,rpop,lrange、llen` 等。
3. **应用场景:** 发布与订阅或者说消息队列、慢查询。
3. **应用场景:** 发布与订阅或者说消息队列、慢查询。
下面我们简单看看它的使用!
@ -261,7 +298,7 @@ OK
(integer) 3
```
#### hash
#### 8.3. hash
1. **介绍** hash 类似于 JDK1.8 前的 HashMap内部实现也差不多(数组 + 链表)。不过Redis 的 hash 做了更多优化。另外hash 是一个 string 类型的 field 和 value 的映射表,**特别适合用于存储对象**,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。
2. **常用命令:** `hset,hmset,hexists,hget,hgetall,hkeys,hvals` 等。
@ -298,11 +335,11 @@ OK
"GuideGeGe"
```
#### set
#### 8.4. set
1. **介绍 ** set 类似于 Java 中的 `HashSet`。Redis 中的 set 类型是一种无序集合集合中的元素没有先后顺序。当你需要存储一个列表数据又不希望出现重复数据时set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如你可以将一个用户所有的关注人存在一个集合中将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。
3. **应用场景:** 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
3. **应用场景:** 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
下面我们简单看看它的使用!
@ -314,7 +351,7 @@ OK
127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
1) "value1"
2) "value2"
127.0.0.1:6379> scard mySet # 查看 set 的长度
127.0.0.1:6379> scard mySet # 查看 set 的长度
(integer) 2
127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
(integer) 1
@ -326,11 +363,11 @@ OK
1) "value2"
```
#### sorted set
#### 8.5. sorted set
1. **介绍:** 和 set 相比sorted set 增加了一个权重参数 score使得集合中的元素能够按 score 进行有序排列还可以通过score的范围来获取元素的列表。有点像是Java中 HashMap和 TreeSet 的结合体。
1. **介绍:** 和 set 相比sorted set 增加了一个权重参数 score使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。
3. **应用场景:** 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。
3. **应用场景:** 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。
```bash
127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
@ -339,7 +376,7 @@ OK
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3"
127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素0 -1 表示输出所有元素
1) "value3"
@ -353,40 +390,40 @@ OK
2) "value2"
```
### Redis单线程模型详解
### 9. Redis 单线程模型详解
**Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型** Netty 的线程模型也基于Reactor 模式Reactor 模式不愧是高性能IO的基石这套事件处理模型对应的是Redis中的文件事件处理器file event handler。由于文件事件处理器file event handler是单线程方式运行的所以我们一般都说 Redis 是单线程模型。
**Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型** Netty 的线程模型也基于 Reactor 模式Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器file event handler。由于文件事件处理器file event handler是单线程方式运行的所以我们一般都说 Redis 是单线程模型。
**既然是单线程,那怎么监听大量的客户端连接呢?**
Redis 通过**IO多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
这样的好处非常明显: **I/O 多路复用技术的使用让Redis不需要额外创建多余的线程来监听客户端的大量连接降低了资源的消耗**和NIO中的 `Selector` 组件很像)。
这样的好处非常明显: **I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。
另外, **Redis服务器是一个事件驱动程序服务器需要处理两类事件 1.文件事件;2.时间事件。**
另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: 1.文件事件;2.时间事件。
时间事件不需要多花时间了解,我们接触最多的还是文件事件(客户端进行读取写入等操作,涉及一系列网络通信,涉及一系列网络通信)。
时间事件不需要多花时间了解,我们接触最多的还是 **文件事件**(客户端进行读取写入等操作,涉及一系列网络通信)。
《Redis设计与实现》有一段话是如是介绍文件事件的我觉得写得挺不错。
《Redis 设计与实现》有一段话是如是介绍文件事件的,我觉得写得挺不错。
> Redis基于Reactor模式开发了自己的网络事件处理器这个处理器被称为文件事件处理 file event handler。文件事件处理器使用I/O多路复用multiplexing程序来同时监听多个套接字并根据 套接字目前执行的任务来为套接字关联不同的事件处理器。
> Redis 基于 Reactor 模式开发了自己的网络事件处理器这个处理器被称为文件事件处理器file event handler。文件事件处理器使用 I/O 多路复用multiplexing程序来同时监听多个套接字并根据 套接字目前执行的任务来为套接字关联不同的事件处理器。
>
> 当被监听的套接字准备好执行连接应答accept、读取read、写入write、关 闭close等操作时与操作相对应的文件事件就会产生这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
> 当被监听的套接字准备好执行连接应答accept、读取read、写入write、关 闭close等操作时与操作相对应的文件事件就会产生这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
>
> **虽然文件事件处理器以单线程方式运行,但通过使用I/O多路复用程序来监听多个套接字**文件事件处理器既实现了高性能的网络通信模型又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接这保持了Redis内部单线程设计的简单性。
> **虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字**,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。
可以看出文件事件处理器file event handler主要是包含 4 个部分:
- 多个 socket客户端连接
- IO 多路复用程序(支持多个客户端连接的关键)
- 文件事件分派器将socket关联到相应的事件处理器
- 文件事件分派器(将 socket 关联到相应的事件处理器)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
![](images/redis/redis事件处理器.png)
<p style="text-align:right;font-size:14px;color:gray">《Redis设计与实现12章》</p>
### Redis 没有使用多线程?为什么不使用多线程?
### 10. Redis 没有使用多线程?为什么不使用多线程?
虽然说 Redis 是单线程模型,但是, 实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。**
@ -396,27 +433,27 @@ Redis 通过**IO多路复用程序** 来监听来自客户端的大量连接(
大体上来说,**Redis 6.0 之前主要还是单线程处理。**
**那Redis6.0之前 为什么不使用多线程?**
**那Redis6.0 之前 为什么不使用多线程?**
我觉得主要原因有下面 3 个:
1. 单线程编程容易并且更容易维护;
2. Redis 的性能瓶颈不再CPU ,主要在内存和网络;
2. Redis 的性能瓶颈不再 CPU ,主要在内存和网络;
3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
### Redis6.0之后为何引入了多线程?
### 11. Redis6.0 之后为何引入了多线程?
**Redis6.0 引入多线程主要是为了提高网络 IO 读写性能**,因为这个算是 Redis 中的一个性能瓶颈Redis 的瓶颈主要受限于内存和网络)。
虽然Redis6.0 引入了多线程模型,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了, 执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
虽然Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了, 执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
Redis6.0的多线程默认是禁用的,只使用主线程。如需开启需要修改redis.conf配置文件
Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件 `redis.conf`
```bash
io-threads-do-reads yes
```
开启多线程后,还需要设置线程数,否则是不生效的。同样修改redis.conf配置文件:
开启多线程后,还需要设置线程数,否则是不生效的。同样需要修改 redis 配置文件 `redis.conf`:
```bash
io-threads 4 #官网建议4核的机器建议设置为2或3个线程8核的建议设置为6个线程
@ -424,12 +461,12 @@ io-threads 4 #官网建议4核的机器建议设置为2或3个线程8核的
推荐阅读:
1. [Redis 6.0 新特性-多线程连环13问](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
1. [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
2. [为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/)
### Redis 设置过期时间
### 12. Redis 设置过期时间
*一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。*
_一般情况下我们设置保存的缓存数据的时候都会设置一个过期时间。_
Redis 中有个设置时间过期的功能,即对存储在 Redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。
@ -446,7 +483,7 @@ Redis 中有个设置时间过期的功能,即对存储在 Redis 数据库中
但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key然后你也没及时去查也就没走惰性删除此时会怎么样如果大量过期 key 堆积在内存里,导致 Redis 内存块耗尽了。怎么解决这个问题呢? **Redis 内存淘汰机制。**
### Redis 内存淘汰机制(MySQL 里有 2000w 数据Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?)
### 13. Redis 内存淘汰机制(MySQL 里有 2000w 数据Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?)
Redis 配置文件 Redis.conf 中有相关注释,我这里就不贴了,大家可以自行查阅或者通过这个网址查看: [http://download.Redis.io/Redis-stable/Redis.conf](http://download.Redis.io/Redis-stable/Redis.conf)
@ -466,7 +503,7 @@ Redis 配置文件 Redis.conf 中有相关注释,我这里就不贴了,大
**备注: 关于 Redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!**
### Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
### 14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
@ -527,7 +564,7 @@ AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的
- [Redis 持久化](Redis持久化.md)
### Redis 事务
### 15. Redis 事务
Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
@ -537,23 +574,23 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。
> 1. Redis 同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。(来自[issue:关于 Redis 事务不是原子性问题](https://github.com/Snailclimb/JavaGuide/issues/452)
### 缓存穿透
### 16. 缓存穿透
#### 什么是缓存穿透?
#### 16.1. 什么是缓存穿透?
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
#### 缓存穿透情况的处理流程是怎样的?
#### 16.2. 缓存穿透情况的处理流程是怎样的?
如下图所示,用户的请求最终都要跑到数据库中查询一遍。
![缓存穿透情况](./images/redis/缓存穿透情况.png)
#### 有哪些解决办法?
#### 16.3. 有哪些解决办法?
最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
**1缓存无效 key**
**1缓存无效 key**
如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086`。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
@ -592,9 +629,9 @@ public Object getObjectInclNullById(Integer id) {
![image](https://static01.imgkr.com/temp/e384cec584314b019de6e3a39ee56425.png)
但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: **布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: **布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
*为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来说!*
_为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来说_
我们先来看一下,**当一个元素加入布隆过滤器中的时候,会进行哪些操作:**
@ -610,9 +647,9 @@ public Object getObjectInclNullById(Integer id) {
更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
### 缓存雪崩
### 17. 缓存雪崩
#### 什么是缓存雪崩?
#### 17.1. 什么是缓存雪崩?
我发现缓存雪崩这名字起的有点意思,哈哈。
@ -622,13 +659,13 @@ public Object getObjectInclNullById(Integer id) {
还有一种缓存雪崩的场景是:**有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。** 这样的情况,有下面几种解决办法:
举个例子 秒杀开始12个小时之前我们统一存放了一批商品到 Redis 中设置的缓存过期时间也是12个小时那么秒杀开始的时候这些秒杀的商品的访问直接就失效了。导致的情况就是相应的请求直接就落到了数据库上就像雪崩一样可怕。
举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。
#### 有哪些解决办法?
#### 17.2. 有哪些解决办法?
**针对Redis服务不可用的情况**
**针对 Redis 服务不可用的情况:**
1. 采用Redis集群避免单机出现问题整个缓存服务都没办法使用。
1. 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
2. 限流,避免同时处理大量的请求。
**针对热点缓存失效的情况:**
@ -636,7 +673,7 @@ public Object getObjectInclNullById(Integer id) {
1. 设置不同的失效时间比如随机设置缓存的失效时间。
2. 缓存永不失效。
### 如何解决 Redis 的并发竞争 Key 问题
### 18. 如何解决 Redis 的并发竞争 Key 问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
@ -650,7 +687,7 @@ public Object getObjectInclNullById(Integer id) {
- https://www.jianshu.com/p/8bddd381de06
### 如何保证缓存与数据库双写时的数据一致性?
### 19. 如何保证缓存与数据库双写时的数据一致性?
> 一般情况下我们都是这样使用缓存的:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。这种方式很明显会存在缓存和数据库的数据不一致的情况。
@ -664,15 +701,53 @@ public Object getObjectInclNullById(Integer id) {
**参考:** Java 工程师面试突击第 1 季(可能是史上最好的 Java 面试突击课程)-中华石杉老师公众号后台回复关键字“1”即可获取该视频内容。
### 参考
### 20. 参考
- 《Redis 开发与运维》
- 《Redis设计与实现》
- 《Redis 设计与实现》
- Redis 命令总结http://Redisdoc.com/string/set.html
- 通俗易懂的Redis数据结构基础教程[https://juejin.im/post/5b53ee7e5188251aaa2d2e16](https://juejin.im/post/5b53ee7e5188251aaa2d2e16)
- 通俗易懂的 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)
## 公众号
### 公众号
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
<!-- code_chunk_output -->
- [1. 简单说说有哪些本地缓存解决方案?](#1-简单说说有哪些本地缓存解决方案)
- [2. 为什么要有分布式缓存?/为什么不直接用本地缓存?](#2-为什么要有分布式缓存为什么不直接用本地缓存)
- [3. 分布式缓存有哪些常见的技术选型方案呢?](#3-分布式缓存有哪些常见的技术选型方案呢)
- [4. 简单介绍一下 Redis 呗!](#4-简单介绍一下-redis-呗)
- [5. 说一下 Redis 和 Memcached 的区别和共同点](#5-说一下-redis-和-memcached-的区别和共同点)
- [6. 缓存数据的处理流程是怎样的?](#6-缓存数据的处理流程是怎样的)
- [7. 为什么要用 Redis/为什么要用缓存?](#7-为什么要用-redis为什么要用缓存)
- [8. Redis 常见数据结构以及使用场景分析](#8-redis-常见数据结构以及使用场景分析)
- [8.1. string](#81-string)
- [8.2. list](#82-list)
- [8.3. hash](#83-hash)
- [8.4. set](#84-set)
- [8.5. sorted set](#85-sorted-set)
- [9. Redis 单线程模型详解](#9-redis-单线程模型详解)
- [10. Redis 没有使用多线程?为什么不使用多线程?](#10-redis-没有使用多线程为什么不使用多线程)
- [11. Redis6.0 之后为何引入了多线程?](#11-redis60-之后为何引入了多线程)
- [12. Redis 设置过期时间](#12-redis-设置过期时间)
- [13. Redis 内存淘汰机制(MySQL 里有 2000w 数据Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?)](#13-redis-内存淘汰机制mysql-里有-2000w-数据redis-中只存-20w-的数据如何保证-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. 如何解决 Redis 的并发竞争 Key 问题](#18-如何解决-redis-的并发竞争-key-问题)
- [19. 如何保证缓存与数据库双写时的数据一致性?](#19-如何保证缓存与数据库双写时的数据一致性)
- [20. 参考](#20-参考)
- [公众号](#公众号)
<!-- /code_chunk_output -->
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。