面试 --- Redis

在这里插入图片描述
在这里插入图片描述
redis的数据 是 存储在 内存中的 ,所以 读写 速度非常快。

redis 除了 做缓存外, 还可以做 分布锁 , 消息队列。

redis 支持 事务 持久化 Lua脚本 多种集群方案。

分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。

** 说一下 Redis 和 Memcached 的区别和共同点 **

共同点:
1.都是基于 内存 的 数据库 ,一般 做缓存使用。
2. 都有 过期策略。
3. 两者 性能都非常高。

区别:

  1. redis 支持 更丰富 的 数据类型(支持更复杂的 应用场景)。
    redis 不单单 支持 key-value ,还 支持 list set zset hash 等数据结构。
    而Memcached 只支持 简单的 key-value 数据类型。

  2. redis 支持 数据持久化, 可以将 内存中的 数据 保持在 磁盘中, 重启的时候可以再次 加载 进行使用 ,而 memcached 把 数据 存储在 内存 之中。

  3. redis 有 灾难恢复机制。 因为 可以 把 缓存中的 数据 持久化到 磁盘上。

  4. redis 在服务器 内存 使用完 之后 ,可以 将 不用的 数据 放到 磁盘上。
    而 memcached 在 服务器内存 使用完之后 就会爆异常。

  5. memcached 没有 原生的 集群模式, 需要 依靠 客户端 来 实现 往集群中 写入数据; 但是 redis 目前 是 支持 cluster 模式。

  6. memcached 是 多线程 , 非阻塞 IO 复用的 网络模型,
    而redis 使用 单线程的 多路 IO 复用模型。

  7. redis 支持 发布订阅模型 ,lua脚本 , 事务等功能,而 memcached不支持,
    而且 redis 支持更多的 编译语言。

  8. memcached 过期数据 的 删除策略 只用了 惰性删除, 而 redis 同时使用了惰性删除 和 定期删除。

** 缓存 数据 的 处理 流程 是怎样的 ?**
在这里插入图片描述

如果用户请求的数据在缓存中就直接返回。
缓存中不存在的话就看数据库中是否存在。
数据库中存在的话就更新缓存中的数据。
数据库中不存在的话就返回空数据。

** 为什么要用 redis 做缓存? **
简单来说 , 就是为了 提高 用户体验 以及 应对更多的 用户。

在这里插入图片描述
从高性能出发 :
对于 用户 第一次 访问 某些数据的话 ,过程比较慢 ,因为是从 硬盘读取的。
但是 对于 用户访问的 数据 属于 高频数据 并且不经常改变的 ,可以存储在 缓存中,等 用户第二次访问的 时候 就可以从 缓存中读取, 因为操作缓存 是直接操作内存,速度较快。
不过, 要保持 数据库和 缓存中的 数据 的 一致性 ,如果 数据库 中的 对应 数据 改变之后 ,同步 改变 缓存 中 相应的 数据 即可!

从高并发出发:
一般像 mysql 这类的 数据库 的 QPS 大概 是 1w 左右, 但是 使用 redis 缓存后 很容易到达 10w ,甚至 30w (redis 集群的 话 会更高)。
【 QPS: 服务器每秒 可以 执行 的 查询次数。】

由此可见, 直接操作 缓存 能够 承受的 数据库请求数量 是 远远大于 直接 访问数据库的 , 所以我们可以 考虑 把 数据库 中的 部分数据 转移到 缓存中 ,这样一来, 用户的 一部分请求 会直接 到 缓存这里 而不用 经过 数据库 ,从而 提高了 系统整体的并发。

** redis 除了做缓存 ,还能 做什么 ?**
1.分布式锁
可以基于 Redisson 实现分布锁。
在这里插入图片描述
https://mp.weixin.qq.com/s/CbnPRfvq4m1sqo2uKI6qQw
2.限流
一般 是 通过 redis + lua 脚本的方式 实现 限流。
https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA

3.消息队列
redis 自带的 list 数据结构 可以 作为 简单的 队列 使用。
Redis5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。

4.复杂业务场景

通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。

** redis 的 基本 数据类型 **
string (简单的 key-value类型)
list (双向链表)
hash ( 数组 + 链表 string 类型 的 filed 和 value 的 映射表)
set (无序集合 类似于 hashset 集合中的 元素 没有 先后顺序)
sorted set (有序集合 double类型的score作为权重, hashmap 和 treeset 的 结合体)

bitmap(新特性)

** String 类型 **
在这里插入图片描述

string 数据结构 是 简单的 key-value类型。
string类型是二进制安全的,string可以包含任何数据,比如jpg图片或序列化对象
相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
string类型是redis最基本的数据类型,一个键最大能存储512MB。

应用场景 :
一般用于 计数场景
如: 用户访问次数 热点文章的点赞转发。

普通字符串的基本操作:

set   设置 key-value 类型的值
get  根据 key 获得对应的 value
del  删除某个 key 对应的值
strlen  返回 key 所储存的字符串值的长度。
exists   判断某个 key 是否存在

127.0.0.1:6379> set key value #设置 key-value 类型的值
OK
127.0.0.1:6379> get key # 根据 key 获得对应的 value
"value"
127.0.0.1:6379> exists key  # 判断某个 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。
(integer) 5
127.0.0.1:6379> del key # 删除某个 key 对应的值
(integer) 1
127.0.0.1:6379> get key
(nil)



批量设置 :
	127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
	OK
	127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
	1) "value1"
	2) "value2"


计数器(字符串的内容为整数的时候可以使用):
	127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> incr number # 将 key 中储存的数字值增一
(integer) 2
127.0.0.1:6379> get number
"2"
127.0.0.1:6379> decr number # 将 key 中储存的数字值减一
(integer) 1
127.0.0.1:6379> get number
"1"


过期(默认为永不过期):
	127.0.0.1:6379> expire 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

** list **
在这里插入图片描述

list 即是 链表 , 特点 是 易于 数据元素的 插入 和 删除 并且 可以 灵活的 调整 链表的 长度 ,但是 链表的 随机 访问困难。
redis 的 list 是 一个 双向链表, 既可以 支持 反向查询 和 遍历 ,更 方便 操作 ,不过 带来了 部分 额外的 内存开销。

使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。

通过 rpush/lpop 实现队列:
127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素
(integer) 1
127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素
(integer) 3
127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出
"value1"
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
1) "value2"
2) "value3"
127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
1) "value2"
2) "value3"

通过 rpush/rpop 实现栈:
127.0.0.1:6379> rpush myList2 value1 value2 value3
(integer) 3
127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
"value3"

在这里插入图片描述

通过 lrange 查看对应下标范围的列表元素:
127.0.0.1:6379> rpush myList value1 value2 value3
(integer) 3
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
1) "value1"
2) "value2"
127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
1) "value1"
2) "value2"
3) "value3"

通过 lrange 命令,你可以基于 list 实现分页查询,性能非常高!


通过 llen 查看链表长度:

127.0.0.1:6379> llen myList
(integer) 3

** hash**
在这里插入图片描述

hash 类似于 jdk1.8 前的 hashmap , 内部实现 差不多是 ( 数组 + 链表)的 形式, 不过 redis 内部 做了 很多 优化。

另外 , hash 是一个 string 类型 的 filed 和 value 的 映射表, 特别 适合于 存储对象。
可以 仅仅 通过 修改 这个对象中的 某个字段的值 。
可以用 hash 结构 存储 用户信息 , 商品信息等。

博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

应用场景: 系统中对象数据的存储。
下面我们简单看看它的使用!
	127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24"
OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
(integer) 1
127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。
"guide"
127.0.0.1:6379> hget userInfoKey age
"24"
127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
1) "name"
2) "description"
3) "age"
127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
1) "guide"
2) "dev"
3) "24"
127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值
127.0.0.1:6379> hget userInfoKey name
"GuideGeGe"


** set **
在这里插入图片描述

类似于 java中的 hashset 。
redis 中的 set 类型 是一种 无序集合 , 集合中的 元素 没有 先后顺序。
当需要 存储 一个 列表数据 , 又不希望 出现 重复 数据 时, set 是一个 很好的选择, 并且 set 提供了 判断 某个成员 是否 在 一个 set集合的 重要接口, 这样是 list不能提供的。
可以基于 set 实现 交集 并集 差集 的 操作。

比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。

应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景

因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
下面我们简单看看它的使用!
127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去
(integer) 2
127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
(integer) 0
127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
1) "value1"
2) "value2"
127.0.0.1:6379> scard mySet # 查看 set 的长度
(integer) 2
127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
(integer) 1
127.0.0.1:6379> sadd mySet2 value2 value3
(integer) 2
127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
(integer) 1
127.0.0.1:6379> smembers mySet3
1) "value2"

sorted set (zset)
在这里插入图片描述

有点像 java 的 hashmap 和 treeset 的 结合体。
有序集合。元素为string类型,元素具有唯一性,不重复。
每个元素都会关联一个double类型的score,表示权重,通过权重将元素从小到大排序 没有修改的操作

和set相比 , sorted set 增加了 权重参数 sorted , 使得 元素 中 的 元素 能够 按 score 进行 有序排列 , 还可以 通过 sorce 的 范围来获取 元素的 列表。

可以做排行榜应用,取TOP N操作。

127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3"
127.0.0.1:6379> zrange  myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
1) "value3"
2) "value2"
3) "value1"
127.0.0.1:6379> zrange  myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value3"
2) "value2"
127.0.0.1:6379> zrevrange  myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value1"
2) "value2"

bitmap

存储的 是 连续的 二进制数字 (0 和 1), 

通过 bitmap , 只需要 一个 bit位 来 表示 某个 元素的 值 或者 状态, key 就是 对应 元素本身。
我们知道 8个bit 可以 组成 一个 byte , 所以 bitmap 本身 会极大 的 节省空间。

应用场景: 适合需要保存状态信息(比如是否签到、是否登录…)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。

# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
127.0.0.1:6379> setbit mykey 7 1
(integer) 0
127.0.0.1:6379> setbit mykey 7 0
(integer) 1
127.0.0.1:6379> getbit mykey 7
(integer) 0
127.0.0.1:6379> setbit mykey 6 1
(integer) 0
127.0.0.1:6379> setbit mykey 8 1
(integer) 0
# 通过 bitcount 统计被被设置为 1 的位的数量。
127.0.0.1:6379> bitcount mykey
(integer) 2

使用场景一:用户行为分析 很多网站为了分析你的喜好,需要研究你点赞过的内容。

# 记录你喜欢过 001 号小姐姐
127.0.0.1:6379> setbit beauty_girl_001 uid 1
Copy to clipboardErrorCopied
使用场景二:统计活跃用户

使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1

那么我该如何计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只要有一天在线就称为活跃),有请下一个 redis 的命令

# 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
# BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数
BITOP operation destkey key [key ...]
Copy to clipboardErrorCopied
初始化数据:

127.0.0.1:6379> setbit 20210308 1 1
(integer) 0
127.0.0.1:6379> setbit 20210308 2 1
(integer) 0
127.0.0.1:6379> setbit 20210309 1 1
(integer) 0
Copy to clipboardErrorCopied
统计 20210308~20210309 总活跃用户数: 1

127.0.0.1:6379> bitop and desk1 20210308 20210309
(integer) 1
127.0.0.1:6379> bitcount desk1
(integer) 1
Copy to clipboardErrorCopied
统计 20210308~20210309 在线活跃用户数: 2

127.0.0.1:6379> bitop or desk2 20210308 20210309
(integer) 1
127.0.0.1:6379> bitcount desk2
(integer) 2
Copy to clipboardErrorCopied
使用场景三:用户在线状态

对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间且效率又高的一种方法。

只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。

缓存 和 数据库 读写 一致性怎么保证
https://www.jianshu.com/p/dc1e5091a0d8

Reactor和Proactor模型
https://www.cnblogs.com/goya/p/11961521.html

** Redis 单线程 模型 详解 **

redis 是 基于 redctor 模式 设计 开发 自己的 一套 高效的 事件处理模型。
这套 事件 对应的 是 redis 的 文件事件处理器。

由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。

** 既然 是 单线程 , 那怎么 监听 大量的 客户端连接 呢? **
1.reids 是通过 io多路复用程序 来 监听 来自 客户端 的 大量连接 (或者说是 监听 多个 socket) , 它会 将 感兴趣的 事件 及 类型 (读写 ) 注册到 内核中 并 监听 每个 事件 是否 发生。

好处 : redis 的 io多路复用技术 的 使用 让 redis 不需要 创建 多余的 线程 来 监听 客户端的 大量连接,降低了 资源的 消耗。

  1. redis 服务器 是 一个 事件驱动程序, 服务器 需要 处理 两类事件 :
    文件事件 和 时间事件。

文件事件 — 客户端进行 读写操作 ,涉及 一系列 网络通信。

	《Redis 设计与实现》有一段话是如是介绍文件事件的,我觉得写得挺不错。
Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。

文件处理器 : (4个部分)
1.多个socket (客户端连接)
2.io多路复用程序 (支持多个客户端连接的关键)
3.文件事件分派器 (将 socket 关联到相应的事件处理器)
4.事件处理器 (连接应答处理器、命令请求处理器、命令回复处理器)

在这里插入图片描述

** redis 给 缓存数据 设置过期时间 作用? **

因为内存是有限的 ,如果 缓存中的 所有 数据 都是 一直保存的 花, 会导致
内存不足 ( Out of memory)

Redis 自带了给缓存数据设置过期时间的功能

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 命令可以移除一个键的过期时间。 **

** 过期时间 除了 缓解 内存的消耗 , 还有其他作用? **
很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。
如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。

** redis 是如何 判断 数据 是否 过期的呢 ?**

redis 通过 一个 过期字典(类似于 hash表) 来保存 数据 过期时间。

过期字典 指向 redis 数据库中 的 某个 键 ,过期字典 的 值 是一个 long 类型的整数, 这个整数 保存了 key 所指向的 数据库键 的 过期时间。(毫秒精度的 UNIX 时间戳)
在这里插入图片描述
过期字典是存储在 redisDb 这个结构里的

	typedef struct redisDb {
    ...

    dict *dict;     //数据库键空间,保存着数据库中所有键值对
    dict *expires   // 过期字典,保存着键的过期时间
    ...
} redisDb;

** 过期的数据的 删除策略 **
1.惰性删除
只会在 取出 key 的时候 才会 对 数据 进行 过期 检查。
这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。

2.定期删除
每隔一段时间 抽取 一批 key 执行 删除 过期 key 操作。
并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。
所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。

**redis 内存淘汰机制 **

相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?

Redis 提供 6 种数据淘汰策略:··
volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

4.0 版本后增加以下两种:

volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰

allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

** Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复) **

	Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。Redis 的一种持久化方式叫快照(RDB),另一种方式是只追加文件(AOF)

	1快照(RDB)
		Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。
		Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
	
	```
快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:

save 900 1           #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 300 10          #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000        #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
		```
		
	2.只追加文件(AOF)
appendonly yes  开启AOF

		开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。
		
--------
AOF文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。

在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

Redis 的 AOF 方式
https://github.com/Snailclimb/JavaGuide/issues/783

补充内容:AOF 重写

AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。

AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。

在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。

** Redis 事务 **
Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能。

> MULTI
OK
> SET USER "Guide哥"
QUEUED
> GET USER
QUEUED
> EXEC
1) OK
2) "Guide哥"
Copy to clipboardErrorCopied
使用 MULTI 命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 EXEC 命令将执行所有命令。

这个过程是这样的:

开始事务(MULTI)。
命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
执行事务(EXEC)。
你也可以通过 DISCARD 命令取消一个事务,它会清空事务队列中保存的所有命令。

> MULTI
OK
> SET USER "Guide哥"
QUEUED
> GET USER
QUEUED
> DISCARD
OK
Copy to clipboardErrorCopied
WATCH 命令用于监听指定的键,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的键被修改的话,整个事务都不会执行,直接返回失败。

> WATCH USER
OK
> MULTI
> SET USER "Guide哥"
OK
> GET USER
Guide哥
> EXEC
ERR EXEC without MULTI

Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。

** 缓存穿透 **(重点)
缓存穿透的话 就是 大量请求的key 根本 不存在 缓存中 ,导致请求 直接 到达了 数据库, 没有 经过 缓存 这一层。

( 某个黑客 故意制造 缓存中 不存在 的 key 发起 大量 请求 ,导致 大量 请求 落到 数据库)

** 缓存 穿透 情况 的 处理流程 是怎样的 ? **
用户的请求最终都要跑到数据库中查询一遍。
在这里插入图片描述

** 缓存 穿透的 解决办法 **

1.做好 参数 校验 ,一些 不合法 的 参数请求 直接 抛出 异常信息 返回给 客户端。

(查询 数据库的 id 不能 小于 0 ,传入的 邮件 格式 不对的 时候 直接返回 错误信息 给客户端 等)

  1. 缓存 无效的 key
    如果 缓存和数据库 都 查不到 某个 key 的数据 就 写入 到 redis 中 并 设置 过期时间。
    set key value ex 10086。
    这种 方式 可以 解决 请求 的 key 变化 不频繁的 情况,
    但如果 黑客 恶意攻击 , 每次 都 构建 不同的 请求key ,会导致 redis 中 缓存了 大量无效的 key。
    如果 非要 用这种 方式 解决 缓存 穿透的 话 , 尽量 把 无效的 key 的 过期时间设置得 短一些 如 一分钟。

(另外,这里多说一嘴,一般情况下我们是这样设计 key 的: 表名:列名:主键名:主键值 。)

public Object getObjectInclNullById(Integer id) {
    // 从缓存中获取数据
    Object cacheValue = cache.get(id);
    // 缓存为空
    if (cacheValue == null) {
        // 从数据库中获取
        Object storageValue = storage.get(key);
        // 缓存空对象
        cache.set(key, storageValue);
        // 如果存储数据为空,需要设置一个过期时间(300秒)
        if (storageValue == null) {
            // 必须设置过期时间,否则有被攻击的风险
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    }
    return cacheValue;
}

  1. 布隆 过滤器
    布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。
    我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。

流程 : 把 所有 可能 存在 的 请求 的 值 都 存放 到 布隆 过滤器中 ,
当 用户 请求 过来 , 先 判断 用户 发送的 请求 是否在 布隆过滤器中 ,
如果不存在 直接返回 错误信息 给客户端 ,
如果存在 就 接着走 一个流程。

加入布隆过滤器之后的缓存处理流程图如下。

在这里插入图片描述

但是, 布隆过滤器 也可能 存在 误判的 情况。
布隆过滤器 说 某个元素 存在 ,小概率误判。
布隆 过滤器 说 某个 元素 不存在 ,那么 这元素 一定不存在。

** 为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理来说! **
原理 :

  1. 当一个元素 加入 布隆过滤器中的时候 进行哪些操作。
    1. 使用 布隆过滤器 中的 hash函数 对 元素值 进行计算 ,得到哈希值 (有几个哈希函数得到几个哈希值)
    2.根据得到的 哈希值 , 在位数组 中 把 对应下标的值 置为 1。

  2. 当一个元素 判断 是否 存在于 布隆过滤器的时候 ,进行哪些操作。
    1.对 给定 元素 再次 进行 相同的 哈希计算。
    2.得到值 之后 判断 数组中 的 每个 元素 是否都为 1,
    如果都为 1 , 那么说明 这个值 存在 于 布隆过滤器 ,
    如果存在 一个 值 不为1 ,说明 该元素 不在 布隆过滤器中。

然后, 一定会出现的 情况 : 不同的 字符串 可能 哈希出来 的值 相同。
(可以适当增加位数组大小或者调整我们的哈希函数来降低概率)

更多关于布隆过滤器的内容
https://github.com/Snailclimb/JavaGuide/blob/master/docs/cs-basics/data-structure/bloom-filter.md

** 缓存雪崩 ** (重点)

1.缓存 在 同一时间 大面积 失效 , 后面的 请求 直接 落在了 数据库上 ,造成 数据库 短时间内 承受大量请求。
(数据库的压力可想而知,可能直接就被这么多请求弄宕机了。)

(举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。)

  1. 有一些 大量访问数据 (热点缓存) 在 某一时刻 大面积 失效, 导致 对应的请求 直接 落在 数据库上。

(举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。)

** 解决办法 **

  1. 针对 redis 服务器 不可用 的 情况:

    1.采用 redis 集群 ,避免单机 出现 问题 整个 缓存服务器 都没办法使用。
    2.限流。 避免同时 处理 大量的请求。

  2. 针对 热点 缓存 失效的 情况:

    1.设置 不同的 失效时间 (如 随机 设置 缓存 的 失效时间)

    1. 缓存用不失效。

···

如何保证缓存和数据库数据的一致性?
我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。

下面单独对 Cache Aside Pattern(旁路缓存模式) 来聊聊。

Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删除 cache 。

如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:

缓存失效时间变短(不推荐,治标不治本) :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
增加 cache 更新重试机制(常用): 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。

https://baijiahao.baidu.com/s?id=1710566894419278870&wfr=spider&for=pc
···

面试/工作必备!3种常用的缓存读写策略!
https://snailclimb.gitee.io/javaguide/#/docs/database/Redis/3%E7%A7%8D%E5%B8%B8%E7%94%A8%E7%9A%84%E7%BC%93%E5%AD%98%E8%AF%BB%E5%86%99%E7%AD%96%E7%95%A5?id=write-behind-pattern%ef%bc%88%e5%bc%82%e6%ad%a5%e7%bc%93%e5%ad%98%e5%86%99%e5%85%a5%ef%bc%89


Redis 的 发布 和 订阅

** 什么是 发布和订阅?

是一种 消息通信模式, 发送者 (pub) 发送信息 , 订阅者 (sub) 接收消息

redis 客户端 可以 订阅 任意数量的频道

流程图:
在这里插入图片描述

加入发送端 有 1 2 3 三个频道 , 客户端 订阅了 频道1 ,只能通过 频道1 获取信息,其他频道无法获得信息

**命令行 实现

subscribe channel1
public channel1 hello
在这里插入图片描述


Redis 6 新数据类型

BitMaps HyperLogLog Geospatial

**BitMaps
BitMaps 本身不是一种 数据类型 , 实际上 是字符串但 可以 对 字符串 进行位移操作

BitMaps 可以看做 一个 以位为单位的 数组, 数组中 每个 单元 只存储0 和 1, 下标称作 偏移量

如果 偏移量大的情况下, 会导致 redis的阻塞

key:键 , offest:偏移量 ,value:值
set

get

bitcount [start , end ] 统计字符串 被 设置为1的 bit数量
-1表示最后一位 , -2倒数第二个

在这里插入图片描述

bitop
在这里插入图片描述

**HyperLogLog

页面的统计访问量

Redis HyperLogLog 是用来 做 基数统计的 算法, 在输入元素数量 或者 体积非常大时候,
计算 基数所需要的空间 都是固定的 ,并且很小

在这里插入图片描述

实际操作:
存储
在这里插入图片描述
成功为1 不成功为 0

在这里插入图片描述

计算
在这里插入图片描述

在这里插入图片描述

合并
在这里插入图片描述

**Geospatial

在这里插入图片描述

经纬度 地理位置

添加 可以一次性添加多个
在这里插入图片描述
在这里插入图片描述

获取
在这里插入图片描述
在这里插入图片描述

取距离
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


**Redis 的 事务

在这里插入图片描述

**Redis 事务

Redis 事务 是一个 单独的隔离操作: 事务中的所有命令都会 序列化,按顺序地执行。
事务在 执行过程中,不会被 其他客户端 发送来的 命令 请求 所打断

主要作用: 串联多个命令防止别的命令插队

命令:

Multi: 开启事务

Exec:提交事务

discard: 结束命令

从输入 Multi 命令 开始 , 输入的命令 都会 依次 进入 命令队列 ,但不会执行 ,直到 输入
Exec 后, redis 会将 之前的 命令队列 依次执行

组队过程中 可以 通过 discard 来放弃组队

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


** 事务的 错误处理

1.组队中 某个命令 出现了 错误报告, 执行的 整个队列 都会被 取消

2.如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
(只有出现报错的命令 不会被执行 , 其他命令 正常执行 , 不会回滚)

第一种情况: 组队失败,执行失败,事务回滚
在这里插入图片描述
在这里插入图片描述


第二种情况: 组队成功,执行失败,事务不回滚
在这里插入图片描述
在这里插入图片描述


**锁机制 ----------- 事务冲突 (悲观锁 和 乐观锁)

乐观锁: 通过 版本号+ 时间戳
在这里插入图片描述
在这里插入图片描述

乐观锁的过程:

*通过 watch 加 乐观锁

在这里插入图片描述

**实例
在这里插入图片描述

在这里插入图片描述

  • unwatch 取消监视
    在这里插入图片描述

** Redis 事务的 三个特性

redis 不支持 ACID特性

  1. 单独的隔离操作

    *事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

  2. 没有隔离级别的概念

    *队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

  3. 不保证原子性

    *事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚


** Redis 事务秒杀 案例

  1. 解决计数器和人员记录的事务操作

在这里插入图片描述

**过程模拟

  1. 用户 id 和 商品id 进行非空判断

  2. 连接 jedis

  3. 拼接key
    3.1 商品库存 key
    3.2 秒杀成功用户 key

  4. 获取库存 , 如果 库存为 null , 秒杀还没开始

  5. 判断用户 是否 重复 秒杀 操作

  6. 判断 如果 商品 数量 小于 1 ,秒杀结束

  7. 秒杀过程
    7.1 库存 -1
    7.2 把秒杀成功用户 添加到 清单里面

public class SecKill_redis {

	public static void main(String[] args) {
		Jedis jedis =new Jedis("192.168.44.168",6379);
		System.out.println(jedis.ping());
		jedis.close();
	}

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid) throws IOException {
		//1 uid和prodid非空判断
		if(uid == null || prodid == null) {
			return false;
		}

		//2 连接redis
		//Jedis jedis = new Jedis("192.168.44.168",6379);
		//通过连接池得到jedis对象
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis = jedisPoolInstance.getResource();

		//3 拼接key
		// 3.1 库存key
		String kcKey = "sk:"+prodid+":qt";
		// 3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";

		//监视库存
		jedis.watch(kcKey);

		//4 获取库存,如果库存null,秒杀还没有开始
		String kc = jedis.get(kcKey);
		if(kc == null) {
			System.out.println("秒杀还没有开始,请等待");
			jedis.close();
			return false;
		}

		// 5 判断用户是否重复秒杀操作
		if(jedis.sismember(userKey, uid)) {
			System.out.println("已经秒杀成功了,不能重复秒杀");
			jedis.close();
			return false;
		}

		//6 判断如果商品数量,库存数量小于1,秒杀结束
		if(Integer.parseInt(kc)<=0) {
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		}

		//7 秒杀过程
		//使用事务
		Transaction multi = jedis.multi();

		//组队操作
		multi.decr(kcKey);
		multi.sadd(userKey,uid);

		//执行
		List<Object> results = multi.exec();

		if(results == null || results.size()==0) {
			System.out.println("秒杀失败了....");
			jedis.close();
			return false;
		}

		//7.1 库存-1
		//jedis.decr(kcKey);
		//7.2 把秒杀成功用户添加清单里面
		//jedis.sadd(userKey,uid);

		System.out.println("秒杀成功了..");
		jedis.close();
		return true;
	}
}

出现 连接超时问题 : 使用连接池解决

出现 超卖情况(库存为负数) : 使用 乐观锁 解决

** 库存 遗留问题 ? **

由于乐观锁 导致 库存遗留问题

假如 库存 500 , 2000个人去 抢, 等一个人抢到了 , 版本号改为 1.1 原本 1.0,导致 其他 1999个人 无法抢购 。
在这里插入图片描述

解决方案:

** Lua 脚本 **

在这里插入图片描述

**LUA 在 redis 中 的 优势 **

将复杂的 或者 多步的 redis 操作, 写为 一个脚本 , 一次提交 给 redis 执行 , 减少 反复连接 redis 的 次数,提升性能。

LUA脚本 是 类似 redis 事务, 有 一定的 原子性, 不会被其他命令插队 ,可以完成 一些 redis 事务性的 操作。

利用 Lua 脚本 淘汰 用户, 解决超卖问题。

通过 Lua 脚本 解决 争抢问题, 实际上 redis 利用 其 单线程的特性 , 用 任务队列的方式 解决 多任务并发问题。


**redis 持久化操作 — RDB AOP

redis 是 存储在内存中的 ,也可以 通过 持久化 存储在 硬盘中。

RDB 默认开启

指定时间 内 将 内存中的 数据集 快照 写入 磁盘中。

(Snpashot 快照, 它 恢复时 将 快照文件 直接 读到内存中)

** 备份 是 如何 执行的 ?**

Redis 会 单独 创建 一个 fork子进程 来进行 持久化, 会 将 数据写入到 一个 临时文件中, 待 持久化过程 都结束了, 再将 这个 临时文件 替换 上次 持久化好的 文件。

整个 过程中 ,主进程 不进行 I/O 操作, 这就 确保了 极高的性能 , 如果需要 进行 大规模数据的恢复, 且 对 数据恢复的 完整性 不是 很敏感 ,RDB 方式 比 AOF 方式 更加 高效。

RDB 的缺点 是 最后一次 持久化后的 数据 可能 会 丢失

在这里插入图片描述

**fork
fork 的作用 是 复制一个 与当前进程 一样的 进程, 新进程的 所有数据(变量,环境变量,程序计数器等)数组 都和 原进程 一致, 但是 是一个 全新的进程, 并作为
原进程的子进程

使用的技术为 写时复制技术

RDB 持久化过程图:

在这里插入图片描述

配置信息:

dump.rdb文件 :在redis.conf中配置文件名称,默认为dump.rdb

dir ./ rdb 文件保存地址

save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。
bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。
可以通过lastsave 命令获取最后一次成功执行快照的时间

stop-writes-on-bgsave-error :当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes.

rdbcompression 压缩文件:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。
如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes.

rdbchecksum :检查完整性

优势:

适合大规模的 数据恢复
对数据完整性和 一致性 要求不高 的话 适合使用
节省磁盘空间
恢复速度快

缺点

RDB 的缺点 是 最后一次 持久化后的 数据 可能 会 丢失

** RDB的 备份 **
在这里插入图片描述

在这里插入图片描述


AOP 默认不开启

以日志的形式 来 记录写操作(增量保存),将 Redis 执行过程的所有写指令记录下来(读取不记录),只 追加文件 但不可以改写文件, redis 启动之初 会 读取 该文件 重新 构建数据, 换言之,redis 重启的话 就 根据 日志文件的内容 将写指令 从前到后 执行 一次 以完成 数据的恢复工作。

(以日志的形式 只记录写的操作,不记录读的操作 , 只可以追加文件不可以改写文件)

在这里插入图片描述

AOF 和 RDB 同时开启 ,默认使用 AOF

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

** Rewrite 压缩 (重写)

文件大于 64M 的 100% 也就是 128M reids 会对 AOF 进行重写
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

AOF持久化流程:

1.客户端的请求命令 会被 append 追加 到 AOF缓冲区
2. AOF 缓冲区 根据 AOF 持久化策略【always, everysec ,no】 将 操作 sync 同步到磁盘 的 AOF 文件中
3. AOF 文件大小超过重写策略或手动策略,会对AOF文件 rewrite 重写,压缩文件容量
4. redis 重启时,会重新 load 加载 AOF 文件 中 的 写操作 达到 数据恢复的 目的
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


如果对数据不敏感,可以选单独用RDB。
不建议单独用 AOF,因为可能会出现Bug。
如果只是做纯内存缓存,可以都不用。


官方

	RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
	AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾. 
	Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
	只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
	同时开启两种持久化方式
	在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
	RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 
	建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
	性能建议
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
 
如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。
代价,一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。
默认超过原大小100%大小时重写可以改到适当的数值。

Redis 主从复制

主机 数据更新后 根据策略和配置, 自动同步到 备机 的 master/slaver 机制,

Master 以写为主Slaver 以读为主

好处:

  1. 一主多从 , 实现读写分离, 性能扩展
  2. 容灾的快速恢复

在这里插入图片描述


**配置 主从复制 过程

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

info replication 打印主从复制的相关信息

在这里插入图片描述

在这里插入图片描述

kill -9 进程号 消灭进程

ps -ef | grep (redis)任务 : 查看进程

** 配置从机 不配置 主机

在从机上: slaveof 主机id地址 端口号

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


** 主从复制 : 复制原理 || 一主二仆

原理:
在这里插入图片描述
原理过程:
 Slave启动成功连接到master后会发送一个sync命令
 Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

在这里插入图片描述

一主二仆
特点:
1. 当 一台 从服务器 宕机了 , 需要重新 使用 slaveof 设置为 从机,因为不设置 默认为 主机 , 数据 会 重新 从头开始复制
2. 当 主服务器 宕机了 , 从服务器 仍然配置不动 ,还是 默认为 从机, 当 主服务器 恢复后, 仍然 是 主机, 一切没有发生变化

薪火相传
一台主机 ,一台从机1, 从机2 又 认 从机 1 为主机

上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master,

可以有效减轻master的写压力,去中心化降低风险。

用 slaveof
中途变更转向:会清除之前的数据,重新建立拷贝最新的

风险是一旦某个slave宕机,后面的slave都没法备份
主机挂了,从机还是从机,无法写数据了

在这里插入图片描述

反客为主

slaverof no one 将从机 转变成 主机

在 薪火相传的基础上, 主机宕机了, 从机1 手动 输入 slaverof no one 转变成 主机


哨兵模式

反客为主的自动版 , 能够 后台监控 主机 是否故障, 如果 故障了 根据 投票数 自动 从服务器
转为 主服务器

提供一个哨兵 监视 主服务器 是否 宕机, 如果宕机 就让 从服务器 自动转为 主服务器。


** 配置哨兵

  1. 自定义的 /myredis 目录下 新建 sentinel.conf 文件(名字不能更改)

  2. 在 sentinel.conf 文件 下 写入 sentinel monitor mymaster 127.0.0.1 6379 1
    mymaster 为 监控对象起的 服务器名称 , 1 为 至少有 多少个 哨兵 同意迁移的数量

  3. 执行 redis-sentinel /myredis/sentinel.conf 启动

  4. 上述命令执行完后在这里插入图片描述

  5. 如果 主服务器挂了, 哨兵 会 推选出一个 新的 从机 为 主机, 如果 主机 即使重启, 也是转为了 从机

1、优先级在redis.conf中默认:
replica-priority 100 || slave-priority 100
在配置文件 中, 优先级 越高 值越低

2、偏移量是指获得原主机数据最全的

3、每个redis实例启动后都会随机生成一个40位的runid

故障恢复 (主服务器挂了, 哨兵的选举)
在这里插入图片描述


复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。


** Redis 集群 **

主机代理模式 (需要的服务器 太多了 )
在这里插入图片描述

无中心化集群配置(推荐)

任何 一台服务器 都可以 作为 集群的 入口 ,互相之间可以 互相联通
在这里插入图片描述

什么是集群
Redis 集群 实现了 对 Redis 的 水平扩容, 即 启动 N个 redis 节点, 将 整个 数据库 分布 存储 到这 N个节点中 ,每个 节点 存储 总数据的 1/N

redis 集群 通过 分区 来 提供 一定程度的 可用性 : 即使 集群 中 有 一部分 节点 失效 或者
无法 进行 通讯, 集群 也可以 继续 处理 命令请求


** 集群的搭建 **

rm -rf 文件名(加* 以什么为开头) 删除文件
在这里插入图片描述
在这里插入图片描述

%s/6479/6380 在文档中 集体替换数据 6479改为 6380

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


分配原则 尽量保证 每个 主数据 运行 在 不同的 ip地址, 每个 从库 和 主库 不在 一个 ip地址上

** 什么是 slots
在这里插入图片描述
在这里插入图片描述

在当前主机下插入数据, 可能 插槽数 不在 本机上, 在 其他主机的 插槽上

在这里插入图片描述
在 6379 插入数据, 插槽却是 12706 ,不在 6479上, 而在 6381上

如果需要插入 多个数据, 需要用组的 形式

在这里插入图片描述

计算插槽值
在这里插入图片描述


**集群 故障恢复 **

cluster node 查询集群信息

如果主节点下线?从节点能否自动升为主节点?注意:15秒超时

主节点恢复后,主从关系会如何?主节点回来变成从机。

如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage


优点:

实现扩容 分摊压力 无中心化配置相对简单

缺点:

多键操作 不被支持

多键的 redis 事务 也不支持 , lua脚本也不支持


** 应用问题 解决 **
缓存穿透 缓存雪崩 缓存击穿 分布式锁

**1. 缓存穿透

查询缓存中不存在的数据,从而导致大量请求直接访问数据库导致压力过大

内存有限, 根据redis 淘汰数据规则,当请求数据多了,一些不常用的数据将被淘汰
现象:

  1. 应用服务器(数据库)压力变大
  2. redis 命中率 降低 , 即 在 redis 中 查询不到 缓存数据
  3. 在reids中 查询不到信息, 导致 一直 访问查询 数据库

出现原因:

黑客 恶意攻击

  1. 出现很多非正常 url 访问
  2. redis查询不到到数据库信息

在这里插入图片描述
解决方案:

1.对空值进行缓存
2.设置可访问的名单(白名单 使用新特性 bitmaps)
3.采用布隆过滤器 (由于存在 哈希冲突导致 命中率会降低)
4.进行实时监控

对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),
我们仍然把这个空结果(null)进行缓存,
设置空结果的过期时间会很短,最长不超过五分钟

设置可访问的名单(白名单)
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,
每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。

采用布隆过滤器
它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务


** 2.缓存 击穿

现象:
1.数据库访问压力瞬间变大
2.redis里面没有出现大量 key过期
3.redis正常运行

原因:

  1. redis 中 某个 key 过期了 ,然而 这个 key 是 大量访问的热点数据

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
在这里插入图片描述

解决办法:

key 可能会在某个时间点 被 超高并发访问, 是一种 热点数据, 这个时候, 就需要考虑
缓存被 击穿的 现象。

  1. 预先设置 热门数据的 过期时间 加长 或者 永不过期
  2. 实时监控 哪些 热门数据, 实时调整 key 的 过期时长
  3. 使用锁 (排它锁) 效率低

(1) 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
(2) 先使用缓存工具的某些带成功操作返回值的操作
(比如Redis的SETNX)去set一个mutex key
(3) 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
(4) 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。

在这里插入图片描述


** 3. 缓存雪崩

现象:

数据库压力变大 导致服务器崩溃

出现原因:

在 极少的时间段内, 查询的 key 大量 集中 过期的情况

解决办法:

缓存失效时的 雪崩效应 对 底层系统的 冲击 非常可怕

1.构建多级缓存架构 (nginx缓存 + redis缓存 +其他缓存(ehcache等))
2.使用锁或队列
3.设置过期标志更新缓存 (提前量)
4.将缓存失效时间分散开 (随机值)

使用锁或队列
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,
从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况

设置过期标志更新缓存
记录缓存数据是否过期(设置提前量),
如果过期会触发通知另外的线程在后台去更新实际key的缓存。

将缓存失效时间分散开
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,
这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key

在这里插入图片描述


** 分布式锁**

分布式锁: 多个组件之间 都能识别 的对象 。

SETNX —> 命令 (一般 用于 设置 分布式锁)

setnx user 10 成功 出现1 , 再次 setnx user 10 失败 出现 0

del user 释放锁

expire users 10 设置锁的过期时间

ttl users 查看锁的过期时间

set users 10 nx ex 12 设置值的同时 nx上锁, ex 释放锁 和 过期时间

在这里插入图片描述

redis:命令
# set sku:1:info “OK” NX PX 10000
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。

在这里插入图片描述


** 分布式锁 --uuid 防止 误删

在这里插入图片描述
在这里插入图片描述


redis6 新功能

ACL 访问控制列表
可以对用户进行更 细粒度的权限控制
1.接入权限 : 用户名和密码
2.可以执行命令
3.可以操作的 key

其他看文档把


io 多线程

在这里插入图片描述

依然是 单线程+IO多路复用

Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值