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-28 15:09:03 +08:00
parent 5052833b92
commit 449dda4857
5 changed files with 167 additions and 138 deletions

View File

@ -131,12 +131,12 @@
1. **[Java 并发基础常见面试题总结](docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md)** 1. **[Java 并发基础常见面试题总结](docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md)**
2. **[Java 并发进阶常见面试题总结](docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md)** 2. **[Java 并发进阶常见面试题总结](docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md)**
**必备知识点:** **面试常问知识点:**
1. [并发容器总结](docs/java/Multithread/并发容器总结.md) 1. [并发容器总结](docs/java/Multithread/并发容器总结.md)
2. **线程池**[Java线程池学习总结](./docs/java/Multithread/java线程池学习总结.md)、[拿来即用的线程池最佳实践](./docs/java/Multithread/best-practice-of-threadpool.md) 2. **线程池**[Java线程池学习总结](./docs/java/Multithread/java线程池学习总结.md)、[拿来即用的线程池最佳实践](./docs/java/Multithread/best-practice-of-threadpool.md)
3. [乐观锁与悲观锁](docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md) 3. [乐观锁与悲观锁](docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md)
4. [万字图文深度解析ThreadLocal](docs/java/Multithread/ThreadLocal.md) 4. [万字图文深度解析 ThreadLocal](docs/java/Multithread/ThreadLocal.md)
5. [JUC 中的 Atomic 原子类总结](docs/java/Multithread/Atomic.md) 5. [JUC 中的 Atomic 原子类总结](docs/java/Multithread/Atomic.md)
6. [AQS 原理以及 AQS 同步组件总结](docs/java/Multithread/AQS.md) 6. [AQS 原理以及 AQS 同步组件总结](docs/java/Multithread/AQS.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

View File

@ -7,7 +7,7 @@
- [1. 简单说说有哪些本地缓存解决方案?](#1-简单说说有哪些本地缓存解决方案) - [1. 简单说说有哪些本地缓存解决方案?](#1-简单说说有哪些本地缓存解决方案)
- [2. 为什么要有分布式缓存?/为什么不直接用本地缓存?](#2-为什么要有分布式缓存为什么不直接用本地缓存) - [2. 为什么要有分布式缓存?/为什么不直接用本地缓存?](#2-为什么要有分布式缓存为什么不直接用本地缓存)
- [3. 分布式缓存有哪些常见的技术选型方案呢?](#3-分布式缓存有哪些常见的技术选型方案呢) - [3. 分布式缓存有哪些常见的技术选型方案呢?](#3-分布式缓存有哪些常见的技术选型方案呢)
- [4. 简单介绍一下 Redis 呗](#4-简单介绍一下-redis-呗) - [4. 简单介绍一下 Redis 呗!](#4-简单介绍一下-redis-呗)
- [5. 说一下 Redis 和 Memcached 的区别和共同点](#5-说一下-redis-和-memcached-的区别和共同点) - [5. 说一下 Redis 和 Memcached 的区别和共同点](#5-说一下-redis-和-memcached-的区别和共同点)
- [6. 缓存数据的处理流程是怎样的?](#6-缓存数据的处理流程是怎样的) - [6. 缓存数据的处理流程是怎样的?](#6-缓存数据的处理流程是怎样的)
- [7. 为什么要用 Redis/为什么要用缓存?](#7-为什么要用-redis为什么要用缓存) - [7. 为什么要用 Redis/为什么要用缓存?](#7-为什么要用-redis为什么要用缓存)
@ -20,25 +20,26 @@
- [9. Redis 单线程模型详解](#9-redis-单线程模型详解) - [9. Redis 单线程模型详解](#9-redis-单线程模型详解)
- [10. Redis 没有使用多线程?为什么不使用多线程?](#10-redis-没有使用多线程为什么不使用多线程) - [10. Redis 没有使用多线程?为什么不使用多线程?](#10-redis-没有使用多线程为什么不使用多线程)
- [11. Redis6.0 之后为何引入了多线程?](#11-redis60-之后为何引入了多线程) - [11. Redis6.0 之后为何引入了多线程?](#11-redis60-之后为何引入了多线程)
- [12. Redis 设置过期时间](#12-redis-设置过期时间) - [12. Redis 给缓存数据设置过期时间有啥用?](#12-redis-给缓存数据设置过期时间有啥用)
- [13. Redis 内存淘汰机制(MySQL 里有 2000w 数据Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?)](#13-redis-内存淘汰机制mysql-里有-2000w-数据redis-中只存-20w-的数据如何保证-redis-中的数据都是热点数据) - [13. Redis是如何判断数据是否过期的呢](#13-redis是如何判断数据是否过期的呢)
- [14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)](#14-redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复) - [14. 过期的数据的删除策略了解么?](#14-过期的数据的删除策略了解么)
- [15. Redis 事务](#15-redis-事务) - [15. Redis 内存淘汰机制了解么?](#15-redis-内存淘汰机制了解么)
- [16. 缓存穿透](#16-缓存穿透) - [16. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)](#16-redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复)
- [16.1. 什么是缓存穿透?](#161-什么是缓存穿透) - [17. Redis 事务](#17-redis-事务)
- [16.2. 缓存穿透情况的处理流程是怎样的?](#162-缓存穿透情况的处理流程是怎样的) - [18. 缓存穿透](#18-缓存穿透)
- [16.3. 有哪些解决办法?](#163-有哪些解决办法) - [18.1. 什么是缓存穿透?](#181-什么是缓存穿透)
- [17. 缓存雪崩](#17-缓存雪崩) - [18.2. 缓存穿透情况的处理流程是怎样的?](#182-缓存穿透情况的处理流程是怎样的)
- [17.1. 什么是缓存雪崩?](#171-什么是缓存雪崩) - [18.3. 有哪些解决办法?](#183-有哪些解决办法)
- [17.2. 有哪些解决办法?](#172-有哪些解决办法) - [19. 缓存雪崩](#19-缓存雪崩)
- [18. 如何解决 Redis 的并发竞争 Key 问题](#18-如何解决-redis-的并发竞争-key-问题) - [19.1. 什么是缓存雪崩?](#191-什么是缓存雪崩)
- [19. 如何保证缓存与数据库双写时的数据一致性?](#19-如何保证缓存与数据库双写时的数据一致性) - [19.2. 有哪些解决办法?](#192-有哪些解决办法)
- [20. 参考](#20-参考) - [20. 如何解决 Redis 的并发竞争 Key 问题](#20-如何解决-redis-的并发竞争-key-问题)
- [公众号](#公众号) - [21. 如何保证缓存与数据库双写时的数据一致性?](#21-如何保证缓存与数据库双写时的数据一致性)
- [22. 参考](#22-参考)
- [23. 公众号](#23-公众号)
<!-- /code_chunk_output --> <!-- /code_chunk_output -->
*正式开始 Redis 之前,我们先来回顾一下在 Java 后端领域,有哪些可以用来当做缓存。* *正式开始 Redis 之前,我们先来回顾一下在 Java 后端领域,有哪些可以用来当做缓存。*
### 1. 简单说说有哪些本地缓存解决方案? ### 1. 简单说说有哪些本地缓存解决方案?
@ -51,23 +52,23 @@ _先来聊聊本地缓存这个实际在很多项目中用的蛮多特别
_那本地缓存的方案有哪些呢且听 Guide 给你来说一说。_ _那本地缓存的方案有哪些呢且听 Guide 给你来说一说。_
**一JDK 自带的 `HashMap``ConcurrentHashMap`了。** **一JDK 自带的 `HashMap``ConcurrentHashMap` 了。**
`ConcurrentHashMap` 可以看作是线程安全版本的 `HashMap` ,两者都是存放 key/value 形式的键值对。但是,大部分场景来说不会使用这两者当做缓存,因为只提供了缓存的功能,并没有提供其他诸如过期时间之类的功能。一个稍微完善一点的缓存框架至少要提供:过期时间、淘汰机制、命中率统计这三点。 `ConcurrentHashMap` 可以看作是线程安全版本的 `HashMap` ,两者都是存放 key/value 形式的键值对。但是,大部分场景来说不会使用这两者当做缓存,因为只提供了缓存的功能,并没有提供其他诸如过期时间之类的功能。一个稍微完善一点的缓存框架至少要提供:过期时间、淘汰机制、命中率统计这三点。
**二:`Ehcache``Guava Cache``Spring Cache`这三者是使用的比较多的本地缓存框架。** **二: `Ehcache``Guava Cache` `Spring Cache` 这三者是使用的比较多的本地缓存框架。**
`Ehcache` 的话相比于其他两者更加重量。不过,相比于 `Guava Cache``Spring Cache`来说, `Ehcache` 支持可以嵌入到 hibernate 和 mybatis 作为多级缓存,并且可以将缓存的数据持久化到本地磁盘中、同时也提供了集群方案(比较鸡肋,可忽略)。 `Ehcache` 的话相比于其他两者更加重量。不过,相比于 `Guava Cache` `Spring Cache` 来说, `Ehcache` 支持可以嵌入到 hibernate 和 mybatis 作为多级缓存,并且可以将缓存的数据持久化到本地磁盘中、同时也提供了集群方案(比较鸡肋,可忽略)。
`Guava Cache``Spring Cache`两者的话比较像。 `Guava Cache` `Spring Cache` 两者的话比较像。
`Guava` 相比于 `Spring Cache` 的话使用的更多一点,它提供了 API 非常方便我们使用,同时也提供了设置缓存有效时间等功能。它的内部实现也比较干净,很多地方都和`ConcurrentHashMap`的思想有异曲同工之妙。 `Guava` 相比于 `Spring Cache` 的话使用的更多一点,它提供了 API 非常方便我们使用,同时也提供了设置缓存有效时间等功能。它的内部实现也比较干净,很多地方都和 `ConcurrentHashMap` 的思想有异曲同工之妙。
使用 `Spring Cache` 的注解实现缓存的话,代码会看着很干净和优雅,但是很容易出现问题比如缓存穿透、内存溢出。 使用 `Spring Cache` 的注解实现缓存的话,代码会看着很干净和优雅,但是很容易出现问题比如缓存穿透、内存溢出。
**三 `Caffeine`** **三 后起之秀Caffeine。**
相比于 `Guava`来说 `Caffeine` 在各个方面比如性能要更加优秀,一般建议使用其来替代 `Guava`。并且, `Guava``Caffeine` 的使用方式很像! 相比于 `Guava` 来说 `Caffeine` 在各个方面比如性能要更加优秀,一般建议使用其来替代 `Guava` 。并且, `Guava``Caffeine` 的使用方式很像!
本地缓存固然好,但是缺陷也很明显,比如多个相同服务之间的本地缓存的数据无法共享。 本地缓存固然好,但是缺陷也很明显,比如多个相同服务之间的本地缓存的数据无法共享。
@ -100,7 +101,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。 分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
### 4. 简单介绍一下 Redis 呗 ### 4. 简单介绍一下 Redis 呗!
简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。 简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
@ -127,6 +128,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.** 5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.**
6. **Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。** Redis 6.0 引入了多线程 IO 6. **Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。** Redis 6.0 引入了多线程 IO
7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。** 7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。**
8. **Memcached过期数据的删除策略只用了惰性删除而 Redis 同时使用了惰性删除与定期删除。**
相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。 相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。
@ -185,7 +187,7 @@ _简单来说使用缓存主要是为了提升用户体验以及应对更多
**普通字符串的基本操作:** **普通字符串的基本操作:**
```bash ``` bash
127.0.0.1:6379> set key value #设置 key-value 类型的值 127.0.0.1:6379> set key value #设置 key-value 类型的值
OK OK
127.0.0.1:6379> get key # 根据 key 获得对应的 value 127.0.0.1:6379> get key # 根据 key 获得对应的 value
@ -202,7 +204,7 @@ OK
**批量设置** : **批量设置** :
```bash ``` bash
127.0.0.1:6379> mest key1 value1 key2 value2 # 批量设置 key-value 类型的值 127.0.0.1:6379> mest key1 value1 key2 value2 # 批量设置 key-value 类型的值
OK OK
127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value 127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
@ -212,7 +214,7 @@ OK
**计数器(字符串的内容为整数的时候可以使用):** **计数器(字符串的内容为整数的时候可以使用):**
```bash ``` bash
127.0.0.1:6379> set number 1 127.0.0.1:6379> set number 1
OK OK
@ -228,8 +230,8 @@ OK
**过期** **过期**
```bash ``` bash
127.0.0.1:6379> exp key 60 # 数据在 60s 后过期 127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
(integer) 1 (integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire) 127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK OK
@ -247,7 +249,7 @@ OK
**通过 `rpush/lpop` 实现队列:** **通过 `rpush/lpop` 实现队列:**
```bash ``` bash
127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素 127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素
(integer) 1 (integer) 1
127.0.0.1:6379> rpush myList value2 value3 # 向list的头部最右边添加多个元素 127.0.0.1:6379> rpush myList value2 value3 # 向list的头部最右边添加多个元素
@ -264,7 +266,7 @@ OK
**通过 `rpush/rpop` 实现栈:** **通过 `rpush/rpop` 实现栈:**
```bash ``` bash
127.0.0.1:6379> rpush myList2 value1 value2 value3 127.0.0.1:6379> rpush myList2 value1 value2 value3
(integer) 3 (integer) 3
127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出 127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
@ -275,9 +277,9 @@ OK
![redis list](./images/redis/redis-list.png) ![redis list](./images/redis/redis-list.png)
**通过`lrange`查看对应下标范围的列表元素:** **通过 `lrange` 查看对应下标范围的列表元素:**
```bash ``` bash
127.0.0.1:6379> rpush myList value1 value2 value3 127.0.0.1:6379> rpush myList value1 value2 value3
(integer) 3 (integer) 3
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表 0 为 start,1为 end 127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表 0 为 start,1为 end
@ -293,7 +295,7 @@ OK
**通过 `llen` 查看链表长度:** **通过 `llen` 查看链表长度:**
```bash ``` bash
127.0.0.1:6379> llen myList 127.0.0.1:6379> llen myList
(integer) 3 (integer) 3
``` ```
@ -306,7 +308,7 @@ OK
下面我们简单看看它的使用! 下面我们简单看看它的使用!
```bash ``` bash
127.0.0.1:6379> hset userInfoKey name "guide" description "dev" age "24" 127.0.0.1:6379> hset userInfoKey name "guide" description "dev" age "24"
OK OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。 127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
@ -337,13 +339,13 @@ OK
#### 8.4. set #### 8.4. set
1. **介绍 ** set 类似于 Java 中的 `HashSet`。Redis 中的 set 类型是一种无序集合集合中的元素没有先后顺序。当你需要存储一个列表数据又不希望出现重复数据时set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如你可以将一个用户所有的关注人存在一个集合中将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。 1. **介绍 ** set 类似于 Java 中的 `HashSet` 。Redis 中的 set 类型是一种无序集合集合中的元素没有先后顺序。当你需要存储一个列表数据又不希望出现重复数据时set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如你可以将一个用户所有的关注人存在一个集合中将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。 2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。
3. **应用场景:** 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景 3. **应用场景:** 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
下面我们简单看看它的使用! 下面我们简单看看它的使用!
```bash ``` bash
127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去 127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去
(integer) 2 (integer) 2
127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素 127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
@ -369,7 +371,7 @@ OK
2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。 2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。
3. **应用场景:** 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。 3. **应用场景:** 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。
```bash ``` bash
127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重 127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
(integer) 1 (integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素 127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
@ -400,7 +402,7 @@ Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(
这样的好处非常明显: **I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。 这样的好处非常明显: **I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。
另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: 1.文件事件;2.时间事件。 另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: 1. 文件事件; 2. 时间事件。
时间事件不需要多花时间了解,我们接触最多的还是 **文件事件**(客户端进行读取写入等操作,涉及一系列网络通信)。 时间事件不需要多花时间了解,我们接触最多的还是 **文件事件**(客户端进行读取写入等操作,涉及一系列网络通信)。
@ -414,14 +416,14 @@ Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(
可以看出文件事件处理器file event handler主要是包含 4 个部分: 可以看出文件事件处理器file event handler主要是包含 4 个部分:
- 多个 socket客户端连接 * 多个 socket客户端连接
- IO 多路复用程序(支持多个客户端连接的关键) * IO 多路复用程序(支持多个客户端连接的关键)
- 文件事件分派器(将 socket 关联到相应的事件处理器) * 文件事件分派器(将 socket 关联到相应的事件处理器)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) * 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
![](images/redis/redis事件处理器.png) ![](images/redis/redis事件处理器.png)
<p style="text-align:right;font-size:14px;color:gray">《Redis设计与实现12章》</p> <p style="text-align:right; font-size:14px; color:gray">《Redis设计与实现12章》</p>
### 10. Redis 没有使用多线程?为什么不使用多线程? ### 10. Redis 没有使用多线程?为什么不使用多线程?
@ -449,13 +451,13 @@ Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(
Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件 `redis.conf` Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件 `redis.conf`
```bash ``` bash
io-threads-do-reads yes io-threads-do-reads yes
``` ```
开启多线程后,还需要设置线程数,否则是不生效的。同样需要修改 redis 配置文件 `redis.conf`: 开启多线程后,还需要设置线程数,否则是不生效的。同样需要修改 redis 配置文件 `redis.conf` :
```bash ``` bash
io-threads 4 #官网建议4核的机器建议设置为2或3个线程8核的建议设置为6个线程 io-threads 4 #官网建议4核的机器建议设置为2或3个线程8核的建议设置为6个线程
``` ```
@ -464,30 +466,69 @@ 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/) 2. [为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/)
### 12. Redis 设置过期时间 ### 12. Redis 给缓存数据设置过期时间有啥用?
_一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。_ 一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢?
Redis 中有个设置时间过期的功能,即对存储在 Redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能 因为内存是有限的如果缓存中的所有数据都是一直保存的话分分钟直接Out of memory
我们 set key 的时候,都可以给一个 expire time就是过期时间通过过期时间我们可以指定这个 key 可以存活的时间。 Redis 自带了给缓存数据设置过期时间的功能,比如:
如果假设你设置了一批 key 只能存活 1 个小时,那么接下来 1 小时后Redis 是怎么对这批 key 进行删除的? ``` bash
127.0.0.1:6379> exp key 60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56
```
**定期删除+惰性删除。** 注意:**Redis中除了字符串类型有自己独有设置过期时间的命令 `setex` 外,其他方法都需要依靠 `expire` 命令来设置过期时间 。另外, `persist` 命令可以移除一个键的过期时间: **
通过名字大概就能猜出这两个删除方式的意思了。 **过期时间除了有助于缓解内存的消耗,还有什么其他用么?**
- **定期删除**Redis 默认是每隔 100ms 就**随机抽取**一些设置了过期时间的 key检查其是否过期如果过期就删除。注意这里是随机抽取的。为什么要随机呢你想一想假如 Redis 存了几十万个 key ,每隔 100ms 就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! 很多时候我们的业务场景就是需要某个数据只在某一时间段内存在比如我们的短信验证码可能只在1分钟内有效用户登录的 token 可能只在 1 天内有效。
- **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key靠定期删除没有被删除掉还停留在内存里除非你的系统去查一下那个 key才会被 Redis 给删除掉。这就是所谓的惰性删除,也是够懒的哈!
但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key然后你也没及时去查也就没走惰性删除此时会怎么样如果大量过期 key 堆积在内存里,导致 Redis 内存块耗尽了。怎么解决这个问题呢? **Redis 内存淘汰机制。** 如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。
### 13. Redis 内存淘汰机制(MySQL 里有 2000w 数据Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?) ### 13. Redis是如何判断数据是否过期的呢?
Redis 配置文件 Redis.conf 中有相关注释,我这里就不贴了,大家可以自行查阅或者通过这个网址查看: [http://download.Redis.io/Redis-stable/Redis.conf](http://download.Redis.io/Redis-stable/Redis.conf) Redis 通过一个叫做过期字典可以看作是hash表来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键)过期字典的值是一个long long类型的整数这个整数保存了key所指向的数据库键的过期时间毫秒精度的UNIX时间戳
**Redis 提供 6 种数据淘汰策略:** ![redis过期字典](images/redis/redis过期时间.png)
过期字典是存储在redisDb这个结构里的
``` c
typedef struct redisDb {
...
dict *dict; //数据库键空间,保存着数据库中所有键值对
dict *expires // 过期字典,保存着键的过期时间
...
} redisDb;
```
### 14. 过期的数据的删除策略了解么?
如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后Redis 是怎么对这批 key 进行删除的呢?
常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):
1. **惰性删除** 只会在取出key的时候才对数据进行过期检查。这样对CPU最友好但是可能会造成太多过期 key 没有被删除。
2. **定期删除** 每隔一段时间抽取一批 key 执行删除过期key操作。并且Redis 底层会并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
定期删除对内存更加友好惰性删除对CPU更加友好。两者各有千秋所以Redis 采用的是 **定期删除+惰性/懒汉式删除**
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里然后就Out of memory了。
怎么解决这个问题呢?答案就是: **Redis 内存淘汰机制。**
### 15. Redis 内存淘汰机制了解么?
相关问题MySQL 里有 2000w 数据Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
Redis 提供 6 种数据淘汰策略:
1. **volatile-lruleast frequently used**从已设置过期时间的数据集server.db[i].expires中挑选最近最少使用的数据淘汰 1. **volatile-lruleast frequently used**从已设置过期时间的数据集server.db[i].expires中挑选最近最少使用的数据淘汰
2. **volatile-ttl**从已设置过期时间的数据集server.db[i].expires中挑选将要过期的数据淘汰 2. **volatile-ttl**从已设置过期时间的数据集server.db[i].expires中挑选将要过期的数据淘汰
@ -501,13 +542,11 @@ Redis 配置文件 Redis.conf 中有相关注释,我这里就不贴了,大
7. **volatile-lfu**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 7. **volatile-lfu**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
8. **allkeys-lfu**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key 8. **allkeys-lfu**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
**备注: 关于 Redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!** ### 16. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
### 14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis 不同于 Memcached 的很重一点就是Redis 支持持久化,而且支持两种不同的持久化操作。**Redis 的一种持久化方式叫快照snapshottingRDB另一种方式是只追加文件append-only file,AOF**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 Redis 不同于 Memcached 的很重一点就是Redis 支持持久化,而且支持两种不同的持久化操作。**Redis 的一种持久化方式叫快照snapshottingRDB另一种方式是只追加文件append-only file, AOF**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
**快照snapshotting持久化RDB** **快照snapshotting持久化RDB**
@ -515,8 +554,7 @@ Redis 可以通过创建快照来获得存储在内存里面的数据在某个
快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置: 快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:
```conf ``` conf
save 900 1 #在900秒(15分钟)之后如果至少有1个key发生变化Redis就会自动触发BGSAVE命令创建快照。 save 900 1 #在900秒(15分钟)之后如果至少有1个key发生变化Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后如果至少有10个key发生变化Redis就会自动触发BGSAVE命令创建快照。 save 300 10 #在300秒(5分钟)之后如果至少有10个key发生变化Redis就会自动触发BGSAVE命令创建快照。
@ -528,7 +566,7 @@ save 60 10000 #在60秒(1分钟)之后如果至少有10000个key发生
与快照持久化相比AOF 持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOFappend only file方式的持久化可以通过 appendonly 参数开启: 与快照持久化相比AOF 持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOFappend only file方式的持久化可以通过 appendonly 参数开启:
```conf ``` conf
appendonly yes appendonly yes
``` ```
@ -536,7 +574,7 @@ appendonly yes
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: 在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
```conf ``` conf
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步 appendfsync no #让操作系统决定何时进行同步
@ -544,9 +582,9 @@ appendfsync no #让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
[相关 Issue783Redis 的 AOF 方式](https://github.com/Snailclimb/JavaGuide/issues/783) **相关 issue** [783Redis 的 AOF 方式](https://github.com/Snailclimb/JavaGuide/issues/783)
**Redis 4.0 对于持久化机制的优化** **拓展:Redis 4.0 对于持久化机制的优化**
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
@ -562,43 +600,72 @@ AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的
**更多内容可以查看我的这篇文章:** **更多内容可以查看我的这篇文章:**
- [Redis 持久化](Redis持久化.md) * [Redis 持久化](Redis持久化.md)
### 15. Redis 事务 ### 17. Redis 事务
Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求 Redis 可以通过 **MULTIEXECDISCARD 和 WATCH** 等命令来实现事务(transaction)功能
在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中事务总是具有原子性Atomicity、一致性Consistency和隔离性Isolation并且当 Redis 运行在某种特定的持久化模式下时事务也具有持久性Durability ``` bash
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
```
补充内容: 使用 [MULTI](https://redis.io/commands/multi)命令后可以输入多个命令。Redis不会立即执行这些命令而是将它们放到队列当调用了[EXEC](https://redis.io/commands/exec)命令将执行所有命令。
> 1. Redis 同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。(来自[issue:关于 Redis 事务不是原子性问题](https://github.com/Snailclimb/JavaGuide/issues/452) Redis官网相关介绍 [https://redis.io/topics/transactions](https://redis.io/topics/transactions) 如下:
### 16. 缓存穿透 ![redis事务](images/redis/redis事务.png)
#### 16.1. 什么是缓存穿透? 但是Redis 的事务和我们平时理解的关系型数据库的事务不同。我们知道事务具有四大特性: **1. 原子性****2. 隔离性****3. 持久性****4. 一致性**。
1. **原子性Atomicity** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2. **隔离性Isolation** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
3. **持久性Durability** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
4. **一致性Consistency** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
**Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。**
Redis官网也解释了自己为啥不支持回滚。简单来说就是Redis开发者们觉得没必要支持回滚这样更简单便捷并且性能更好。Redis开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。
![redis roll back](images/redis/redis-rollBack.png)
你可以将Redis中的事务就理解为 **Redis事务提供了一种将多个命令请求打包的功能。然后再按顺序执行打包的所有命令并且不会被中途打断。**
**相关issue** :[issue452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) ,推荐阅读:[https://zhuanlan.zhihu.com/p/43897838](https://zhuanlan.zhihu.com/p/43897838) 。
### 18. 缓存穿透
#### 18.1. 什么是缓存穿透?
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
#### 16.2. 缓存穿透情况的处理流程是怎样的? #### 18.2. 缓存穿透情况的处理流程是怎样的?
如下图所示,用户的请求最终都要跑到数据库中查询一遍。 如下图所示,用户的请求最终都要跑到数据库中查询一遍。
![缓存穿透情况](./images/redis/缓存穿透情况.png) ![缓存穿透情况](./images/redis/缓存穿透情况.png)
#### 16.3. 有哪些解决办法? #### 18.3. 有哪些解决办法?
最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。 最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
**1缓存无效 key** **1缓存无效 key**
如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086`。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。 如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下: `SET key value EX 10086` 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值` 另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值`
如果用 Java 代码展示的话,差不多是下面这样的: 如果用 Java 代码展示的话,差不多是下面这样的:
```java ``` java
public Object getObjectInclNullById(Integer id) { public Object getObjectInclNullById(Integer id) {
// 从缓存中获取数据 // 从缓存中获取数据
Object cacheValue = cache.get(id); Object cacheValue = cache.get(id);
@ -631,7 +698,7 @@ public Object getObjectInclNullById(Integer id) {
但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: **布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。** 但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: **布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
_为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来说_ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理来说_
我们先来看一下,**当一个元素加入布隆过滤器中的时候,会进行哪些操作:** 我们先来看一下,**当一个元素加入布隆过滤器中的时候,会进行哪些操作:**
@ -647,9 +714,9 @@ _为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来
更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。 更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
### 17. 缓存雪崩 ### 19. 缓存雪崩
#### 17.1. 什么是缓存雪崩? #### 19.1. 什么是缓存雪崩?
我发现缓存雪崩这名字起的有点意思,哈哈。 我发现缓存雪崩这名字起的有点意思,哈哈。
@ -661,7 +728,7 @@ _为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来
举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。 举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。
#### 17.2. 有哪些解决办法? #### 19.2. 有哪些解决办法?
**针对 Redis 服务不可用的情况:** **针对 Redis 服务不可用的情况:**
@ -673,7 +740,7 @@ _为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来
1. 设置不同的失效时间比如随机设置缓存的失效时间。 1. 设置不同的失效时间比如随机设置缓存的失效时间。
2. 缓存永不失效。 2. 缓存永不失效。
### 18. 如何解决 Redis 的并发竞争 Key 问题 ### 20. 如何解决 Redis 的并发竞争 Key 问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同! 所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
@ -685,9 +752,9 @@ _为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来
参考: 参考:
- https://www.jianshu.com/p/8bddd381de06 * https://www.jianshu.com/p/8bddd381de06
### 19. 如何保证缓存与数据库双写时的数据一致性? ### 21. 如何保证缓存与数据库双写时的数据一致性?
> 一般情况下我们都是这样使用缓存的:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。这种方式很明显会存在缓存和数据库的数据不一致的情况。 > 一般情况下我们都是这样使用缓存的:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。这种方式很明显会存在缓存和数据库的数据不一致的情况。
@ -701,53 +768,15 @@ _为什么会出现误判的情况呢?我们还要从布隆过滤器的原理来
**参考:** Java 工程师面试突击第 1 季(可能是史上最好的 Java 面试突击课程)-中华石杉老师公众号后台回复关键字“1”即可获取该视频内容。 **参考:** Java 工程师面试突击第 1 季(可能是史上最好的 Java 面试突击课程)-中华石杉老师公众号后台回复关键字“1”即可获取该视频内容。
### 20. 参考 ### 22. 参考
- 《Redis 开发与运维》 * 《Redis 开发与运维》
- 《Redis 设计与实现》 * 《Redis 设计与实现》
- Redis 命令总结http://Redisdoc.com/string/set.html * 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) * 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)
### 公众号 ### 23. 公众号
<!-- @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 -->
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。