# Redis
- 什么是 Redis
Redis 本质上是一个 Key-Value 类型的内存数据库,很像 memcached,整个数据库统统加载在内存当中 进行操作,定期通过异步操作把数据库数据 flush到硬盘上进行保存。因为是纯内存操作,Redis 的性能非 常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。 Redis 的出色之处不 仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个 value 的最大限制是 1GB,不像 memcached 只能保存1MB 的数据,因此 Redis可以用来实现很多有用的功能,比方说用他的 List 来做 FIFO 双向链表,实现一个轻量级的高性 能消息队列服务,用他的 Set 可以做高性能的 tag 系统等等。 另外 Redis 也可以对存入的 Key-Value 设置 expire 时间,因此也可以被当作一 个功能加强版的 memcached 来用。 Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读 写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
Redis是使用c语言开发的一个高性能键值数据库。Redis可以通过一些键值类型来存储数据。
- Redis 的全称
Remote Dictionary Server
- redis 数据类型
string 字符串
list 可以重复的集合
set 不可以重复的集合
hash 类似于 Map<String,String>
zset(sorted set) 带分数的 set
一个字符串类型的值能存储最大容量是512M
# Redis 事务
- 理解 Redis 事务
事务是一个单独的隔离操作:
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其 他客户端发送来的命令请求所打断。
事务是一个原子操作:
事务中的命令要么全部被执行,要么全部都不执行。
- Redis 事务相关的命令有
MULTI、EXEC、DISCARD、WATCH
MULTI 开启事务,总是返回OK,EXEC 提交事务,DISCARD放弃事务(放弃提交执行),WATCH监控
# Redis key 的过期时间和永久有效
EXPIRE 和 PERSIST 命令
# Redis 持久化数据和缓存怎么做扩容
如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。
如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
# 为什么 Redis 需要把所有数据放到内存中
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
# Redis 如何做内存优化
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。
比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面。
# 缓存穿透
- 缓存系统定义:
按照 KEY 去查询 VALUE,当 KEY 对应的 VALUE 一定不存在的时候并对 KEY 并发请求量很大的时候, 就会对后端造成很大的压力。(查询一个必然不存在的数据。比如文章表,查询一个不存在的 id,每次都会访问 DB,如果有人恶意破坏,很可能直接对 DB 造成影响。)
由于缓存不命中,每次都要查询持久层。从而失去缓存的意义。
- 解决方法:
(1)缓存层缓存空值。
缓存太多空值,占用更多空间。(优化:给个空值过期时间)
存储层更新代码了,缓存层还是空值。(优化:后台设置时主动删除空值,并缓存把值进去)
(2)将数据库中所有的查询条件,放布隆过滤器中。
当一个查询请求来临的时候,先经过布隆过滤器进行查,如果请求存在这个条件中,那么继续执行, 如果不在,直接丢弃。
- 备注 :
比如数据库中有 10000 个条件,那么布隆过滤器的容量 size 设置的要稍微比 10000 大一些,比如 12000. 对于误判率的设置,根据实际项目,以及硬件设施来具体定。
但一定不能设置为 0,并且误判率设置的越小,哈希函数跟数组长度都会更多跟更长,那么对硬件,内存中间的要求就会相应的高。
# 缓存雪崩
- 简介:
缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
- 解决办法
事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
事后:利用 redis 持久化机制保存的数据尽快恢复缓存
# 锁
- 悲观锁
执行操作前假设当前的操作肯定(或有很大几率)会被打断(悲观)。
基于这个假设,我们在做操作前就会把 相关资源锁定,不允许自己执行期间有其他操作干扰。
Redis 不支持悲观锁。Redis 作为缓存服务器使用时,以操作为主,很少写操作,相应的操作被打断的几率较少。不采用悲观锁是为了防止降低性能。
- 乐观锁
执行操作前假设当前操作不会被打断(乐观)。基于这个假设,我们在做操作前不会锁定资源,万一发生了其他操作的干扰,那么本次操作将被放弃。
# 持久化
(1)RDB 持久化:
每隔一段时间,将内存中的数据集写到磁盘
Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到个临时文件中,待持久化过程都结 束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。
保存策略:
save 900 1 900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10 300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000 60 秒内如果至 10000 个 key 的值变化,则保存
(2)AOF 持久化:
以日志形式记录每个更新(总结、改)操作
Redis 重新启动时读取这个文件,重新执行新建、修改数据的命令恢复数据
保存策略:
appendfsync always:每次产生一条新的修改数据的命令都执行保存操作;效率低,但是安全!
appendfsync everysec:每秒执行一次保存操作。如果在未保存当前秒内操作时发生了断电,仍然会导致一部分数据丢失(即 1 秒钟的数据)。
appendfsync no:从不保存,将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。
缺点:
1.比起 RDB 占用更多的磁盘空间
2.恢复备份速度要慢
3.每次读写都同步的话,有一定的性能压力
4.存在个别 Bug,造成恢复不能
(3)选择策略:
可读的日志文本,通过操作 AOF
官方推荐:
如果对数据不敏感,可以选单独用 RDB;
不建议单独用 AOF,因为可能出现 Bug;
如果只是做纯内存缓存,可以都不用
- Redis 提供了哪几种持久化方式
RDB 持久化方式能够在指定的时间间隔能对你的数据进行快照存储。
AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以 redis 协议追加保存每次写的操作到文件末尾.Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大。
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。你也可以同时开启两种持久化方式,在这种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始 的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
- 如何选择合适的持久化方式
一般来说,如果想达到足以媲美 PostgreSQL 的数据安全性,你应该同时使用两种持久化功能。
如果你非 常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 RDB 持久化。 有
很多用户都只使用 AOF 持久化,但并不推荐这种方式:因为定时生成 RDB 快照(snapshot)非常便于 进行数据库备份,并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免之前提到的 AOF 程序的 bug
# Redis相关
- Redis 的内存用完了会发生什么?
如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 Redis 当缓存来使用配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。
- redis 是单线程的,为什么那么快
(1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap, HashMap 的优势就是查找和操作的时间复杂度都是O(1)
(2)数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的
(3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
(4)使用多路 I/O 复用模型,非阻塞 IO
(5)使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构 建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
- Redis 是单线程的,如何提高多核 CPU 的利用率?
可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一 个服务器是不够的, 所以,如果你想使用多个 CPU,你可以考虑一下分片(shard)。
- 一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最多能存放多少元素?
理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。
任何 list、set、和 sorted set 都可以放 232 个元素。
换句话说,Redis 的存储极限是系统中的可用内存值。
- Redis 常见性能问题和解决方案?
(1)Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
(2)如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
(3)为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内
(4)尽量避免在压力很大的主库上增加从库
(5)主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。
- Redis 相比 memcached 有哪些优势?
(1)memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
(2)redis 的速度比 memcached 快很多
(3)redis 可以持久化其数据
- Redis 有哪几种数据淘汰策略?
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
- Redis 集群方案应该怎么做?
(1)twemproxy,大概概念是,它类似于一个代理方式,使用方法和普通 redis 无任何区别,设置好它下属的多个 redis 实例后,使用时在本需要连接 redis的地方改为连接 twemproxy,它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。使用方式简 便(相对 redis 只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy 自身单端口实例的压力, 使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
(2)codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在节点数量改变情况 下,旧节点数据可恢复到新 hash 节点。
(3)redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
(4)在业务代码层实现,起几个毫无关联的 redis 实例,在代码层,对 key 进行 hash 计算,然后去对应的 redis 实例操作数据。这种方式对 hash 层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。
- Redis 哈希槽的概念?
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
- Redis 集群最大节点个数是多少? 16384 个。