那些年你不能错过的之【Redis】

3,802 阅读20分钟

大家好,我是小菜,一个渴望在互联网行业做到蔡不菜的小菜。可柔可刚,点赞则柔,白嫖则刚!
死鬼~看完记得给我来个三连哦!

本文主要介绍 Redis相关内容
如有需要,可以参考
如有帮助,不忘 点赞 ❥
创作不易,白嫖无义!

1) 什么是 Redis

  • Redis(Remote Dictionary Server)是一个使用C语言 编写的,开源的(BSD许可)高性能非关系型(NoSQL) 的键值对数据库
  • Redis可以存储键和五种不同类型的值之间的映射。
  • Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。所以读写速度非常快,因此Redis被广泛应用于缓存方向,每秒可以处理超过10万次读写操作,是已知性能最快的key-value 数据库。另外 Redis 也经常用来做分布式锁,而且 Redis 支持事务持久化LUA脚本LRU驱动事件多种集群方案等。

2)Redis为什么那么快

  • 完全基于内存,绝大部分请求是纯粹的内存操作
  • 数据结构简单,对数据操作也简单,Redis中的数据结构是专门为各种场景设计的
  • 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
  • 使用多路 I/O 复用模型,非阻塞 IO

3)Redis 的优缺点

优点:

  • 读写速度快,因为数据存在内存中,类似于HashMap
  • 支持丰富的数据类型,支持 String,List,Hash,Set,SortSet(Zset)
  • 支持事务(一致性和隔离性),利用好RDB和AOF也能实现持久性
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离

缺点:

  • 缓存和数据库双写一致性问题
  • 缓存雪崩、穿透、击穿问题
  • Redis 较难支持在线扩容,在集群容量达到上限时扩容会变得很复杂
  • 主机宕机前会有部分数据未能及时同步到从机,切换IP后还会引入数据不一致问题,降低了系统的可用性
  • 缓存的并发竞争问题

4)Redis与Memcache的区别

  • 数据类型
    • Memcached仅支持字符串类型
    • Redis 支持五种不同的数据类型,可以更灵活地解决问题
  • 数据持久化
    • Memcached 不支持持久化
    • Redis 支持两种持久化策略:RDB快照 和 AOF 日志
  • 分布式
    • Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要现在客户端计算一次数据所在的节点
    • Redis Cluster 支持分布式
  • 内存管理机制
    • Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘
    • Memcached 的数据一直会在内存中,它将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费了

5)Redis有哪些数据类型

  • String(字符串)
    • 简介: 二进制安全
    • 可以存储的值: 字符串,整数或者浮点数,还有jpg图片或者序列化对象
    • 操作: 对整个字符串或者字符串的其中一部分执行操作,对整数和浮点数执行自增或者自减操作
    • 应用场景: 做简单的键值对缓存
    • 实际使用:
  > set test cbuc
ok
    ----------------------------
  > get test
"cbuc"
    ----------------------------
  > del test
(integer) 1
    ----------------------------
  >get test
(nil)
  • List(列表)
    • 简介: 链表(双向链表)
    • 可以存储的值: 列表
    • 操作: 从两端压入或者弹出元素,对单个或者多个元素进行修改,只保留一个范围内的元素
    • 应用场景: 最新消息排行;消息队列
    • 实际使用:
  > rpush test c1
(integer) 1
    ----------------------------
  > rpush test c2
(integer) 2
    ----------------------------
  > rpush test c2
(integer) 3
    ----------------------------
  > lrange test 0 -1
1) "c1"
2) "c2"
3) "c3"
    ----------------------------
  > lindex test 1
"c2"
    ----------------------------
  > lpop test
"c1"
    ----------------------------
  > lrange test 0 -1
"c2"
"c2"    
  • Hash(字典)
    • 简介: 键值对集合,即编程语言中的map类型
    • 可以存储的值: 适合存储对象,并且可以像数据库中的update一样,只修改某一项的属性值
    • 操作: 添加、获取、移除单个键值对,获取所有键值对,检查某个键是否存在
    • 应用场景: 存储、读取、修改用户属性
    • 实际使用:
  > hset test name cbuc
(integer) 1
    ----------------------------
  > hset test age 23
(integer) 1
    ----------------------------
  > hgetall test
1)"name"
2)"cbuc"
3)"age"
4)"23"
    ----------------------------
  > hdel test age
(integer) 1
    ----------------------------
  > hget test name 
"cbuc"
    ----------------------------
  > hgetall test
1)"name"
2)"cbuc"            
  • Set(集合)
    • 简介: hash表实现,元素不重复
    • 可以存储的值: 无序集合
    • 操作: 添加、获取、移除单个元素,检查一个元素是否已经存在于集合中,计算交集、并集、差集从集合里面随机获取元素
    • 应用场景: 共同好友;利用唯一性,统计访问网站的所有IP
    • 实际使用:
  > sadd test c1
(integer) 1
  > sadd test c2
(integer) 1
  > sadd test c1
(integer) 0
    ----------------------------
  >smembers test
1)"c1"
2)"c2"
    ----------------------------
  > sismember test c3
(integer) 0
  > sismember test c1
(integer) 1
    ----------------------------
  > srem test c1
(integer) 1
    ----------------------------
  > smembers test
1)"c2"            
  • ZSet(有序集合)
    • 简介: 将 set 中的元素增加一个权重参数score,元素按score有序排列
    • 可以存储的值: 有序集合
    • 操作: 添加、获取、删除元素,根据分值范围或者成员来获取元素,计算一个键的排名
    • 应用场景: 排行榜;带权重的消息队列
    • 实际使用:
  > zadd test 92 math
(integer) 1
  > zadd test 88 english
(integer) 1
  > zadd test 92 score
(integer) 1
    ----------------------------
  > zrange test 0 -1 withscores
1)"english"
2)"88"
3)"math"
4)"92"
    ----------------------------
  > zrangebyscore test 80 90 withscores
1)"english"
2)"88"
    ----------------------------
  > zrem test english
(integer) 1
    ----------------------------
  > zrange test 0 -1 withscores
3)"math"
4)"92"    

高级用法:

  • BitMap
    位图是支持按 bit 位来存储信息,可以用来实现 布隆过滤器(BloomFilter)
  • HyperLogLog
    供不精确的去重计数功能,比较适合用来做大规模数据的去重统计,例如统计 UV
  • Geospatial
    可以用来保存地理位置,并作位置距离计算或者根据半径计算位置等。可以用来实现附近的人,或者计算最优地图路径

6)什么是Redis持久化

持久化就是把内存中的数据写到磁盘中去,防止服务宕机了内存数据丢失。

7)Redis的持久化机制

Redis提供两种持久化机制:RDB快照(默认)和AOF(机制)

RDB

RDB(Redis DataBase)是Redis中默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到磁盘中,会产生dump.rdb数据文件,可以通过配置文件中的save参数来定义快照的周期。


原理: forkcow。fork 是指 redis间隔一段时间会 fork 一个子进程,子线程将数据写到磁盘上一个临时RDB文件中,当子进程写完临时文件后,将原来的RDB替换掉,这样的好处是可以 cow(copy-on-wirte)
优点:

  • 方便持久化,只有一个文件 dump.rdb
  • 容灾性好,一个文件可以保存到安全的磁盘中
  • 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证 redis 的高性能

缺点:

  • 数据安全性低,RDB是间隔一段时间来进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失,所以这种方式更适合数据要求不严谨的时候用
  • 保存时间长,如果数据量很大,保存快照的时间会很长

AOF

AOF(Append-Only-File),是将 Redis 执行的每次写命令记录到单独的日志文件中,当重启 Redis 会重新将持久化的日志中文件恢复数据


原理: 将写命令添加到 AOF 文件(Append Only File)的末尾。使用AOF持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。
同步选项:

选项 同步频率
no 让操作系统决定何时同步
always 每个写命令都同步
everysec 每秒同步一次
  • no: 并不能给服务器性能带来多大的提升,而且也会增加系统奔溃时数据丢失的数量
  • always: 严重减低服务器性能
  • everysec: 这个选项比较合适,可以保证系统奔溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响

随着服务器写请求的增多,AOF文件会越来越大。Redis提供了一种将AOF重写的特性auto-aof-rewrite,能够去除AOF文件中的冗余写命令

优点:

  • 数据安全,AOF 持久化可以配置 appendfsync 属性中的always,没进行一次写命令操作就记录到 AOF 文件中一次、
  • 一致性,通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题

缺点:

  • AOF 文件比RDB文件大,而且恢复速度慢
  • 数据集大的时候比 RDB 启动效率低

两者比较

  • AOF 文件比 RDB 更新频率高,优先使用 AOF 还原数据
  • AOF 比 RDB 更安全也更强大
  • RDB 性能比 AOF 好
  • 如果两个都配了优先加载 AOF

8)如何选择合适的持久化方式

一般来说两者都会配置。如果单独用 RDB 的话你会丢失很多数据,单独用 AOF,你数据恢复没有 RDB 来的快,如果系统出现问题的时候我们可以先用 RDB 恢复,然后用 AOF 补全数据。冷热备份一起用,才能保证高健壮性的系统。

9)Redis的过期键删除策略

  • 定时删除
    每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源来处理过期的数据,从而影响缓存的响应时间和吞吐量
  • 惰性删除
    只有当访问一个 key 时,才会判断该 key 是否已过期,是则删除。该策略可以最大化节省CPU资源,却对内存非常不友好。极端情况下可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存
  • 定期删除
    每隔一定时间,会扫描一定数量的 expires 字典中的 key,并清除其中已过期的 key。该策略是前两者的一个折中方法。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis 一般同时使用 惰性过期 和 定期过期 两种过期策略

10)Redis 内存淘汰策略

设置过期时间的键空间选择性移除

  • volatile-lru:
    尝试回收最少使用的键使得新添加的数据有空间存放。
  • volatile-random:
    回收随机的键使得新添加的数据有空间存放
  • volatile-ttl:
    优先回收存活时间较短的键使得新添加的数据有空间存放

全局的键空间选择性移除

  • allkeys-lru:
    尝试回收最少使用的键使得新添加的数据有空间存放。
  • allkeys-random:
    回收随机的键使得新添加的数据有空间存放
  • noeviction:
    当内存达到限制并且客户端尝试执行,会返回错误

11)Redis 事务

Redis 事务的本质是通过 MULTIEXECWATCHDISCARD四个原语实现的。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结:
Redis 事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

  • Redis 不支持回滚,Redis 在事务失败时不进行回滚,而是继续执行余下的命令,所以 Redis 的内部可以保持简单且快速。
  • 如果在一个事务中的命令出现错误,那么所有的命令都不会执行
  • 如果在一个事务中出现运行错误,那么正确的命令会被执行
    四个原语
  • WATCH: 是一个乐观锁,可以为 Redis 事务提供 check-and-set(CAS)行为,可以监控一个或多个键。一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令
  • MULTI: 用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当 EXEC命令被调用时,所有队列中的命令才会被执行
  • EXEC: 执行所有事务块内的命令,返回事务块内所有命令的返回值,按命令执行的先后顺序排列,当操作被打断时,返回控制 nil
  • DISCARD: 调用 DISCARD ,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出

12)Redis设置过期时间和永久有效

EXPIRE 和 PERSIST 命令

13)缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决方法:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
  • 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队
  • 搭建集群,如果一台 Redis 挂掉之后,还有其他的可以继续工作

14)缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大
解决方法:

  • 设置热点数据永远不过期
  • 可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力
  • 使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新

15)缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决方法:

  • 对于不存在的数据,在缓存中保存一个数据进行标记,并设置上过期时间,防止相同的数据请求再次访问 DB。
  • 使用 BloomFilter 过滤器,布隆过滤器的特点是存在性检测,如果布隆过滤器中不存在,那么数据一定不存在;如果布隆过滤器中存在,实际数据也有可能会不存在。

16)Redis主从架构

单机的 Redis,能够承载的 QPS 大概在上万到几万不等,对于缓存来说,一般都是用来支撑读高并发的。如果一台机器读写合一的那会很容易发生问题。因此会采用主从架构,让 master 去处理写操作,然后把数据同步到 slave 上,slave 负责读操作。这样就会分发掉大量的请求,而且在扩容的时候还可以轻松实现水平扩容。


当启动一台 slave 的时候,它会发送一个 psync 命令到 master ,如果是第一次同步,主节点会做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接收完成后将RDB镜像加载到内存然后写入本地磁盘。处理完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,类似于数据库的binlog

17)Redis实现分布式锁

简单来说就是先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放
SETNX 是【SET if Not eXists】(如果不存在,则 SET)的简写。当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作。返回值:设置成功,返回 1 。设置失败,返回 0 。

18)Redis的同步机制

Redis可以使用主从同步,从从同步。第一次同步时,主节点会做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接收完成后将RDB镜像加载到内存然后写入本地磁盘。处理完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,类似于数据库的binlog

19)Redis集群原理

Redis Sentinel(哨兵)着眼于高可用,在master 宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster(集群)着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
选主策略:

  • slave 的 priority 设置的越低,优先级越高
  • 同等情况下,slave 复制的数据越多优先级越高
  • 相同的条件下,runid 越小越容易被选中

20)Redis 哨兵模式


哨兵必须用三个实例去保证自己的健壮性,哨兵 + 主从 并不能保证数据不丢失 ,但是可以保证集群的高可用。
工作原理:

  • 每个 Sentinel 节点以每秒一次的频率向它所知的主服务器、从服务器和其他 Sentinel 节点发送一个 PING 命令
  • 如果 一个实例举例最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 所指定的值,那么这个实例就会被标记为主观下线
  • 如果一个主服务器被标记为主观下线,那么正在监视这个服务器的所有 Sentinel 节点会以每秒一次的频率确认主服务器的确进入了主观下线状态
  • 如果有足够数量的 Sentinel (至少要达到配置文件中指定的数量)在指定的时间范围内同意这一判断,那么这个主服务器就会被标记为客观下线
  • 当主服务器被标记为客观下线后,Sentinel 节点会向下线主服务器的所有从服务器发送 INFO 命令的频率从 10 秒一次改为每秒一次
  • Sentinel 节点会协商客观下线主服务器的状态,如果处于 SDOWN 状态,则投票自动选出新的主节点,将剩下从节点指向新的主节点进行数据复制。
  • 当没有足够数量的 Sentinel 节点泳衣主服务器下线是,主服务器的客观下线状态就会被移除。或者当主服务器重新向 Sentinel 的 PING 命令返回有效回复是,主服务器的主观下线状态就会被移除

21)脑裂问题及解决

何为脑裂:

Redis 的集群脑裂是指因为网络问题,导致 redis master 节点跟 redis slave 节点和 sentinel 集群处于不用的网络分区,此时因为 sentinel 集群无法感知到 master 的存在,所以将 slave 节点提升为 master 节点。此时存在两个不同的 master 节点,就像是一个大脑分裂成了两个。这时如果客户端还在基于原来的 master 节点继续写入数据,那么新的 master 节点将无法同步这些数据,当网络问题解决之后,sentinel集群就会将原先的 master 节点降为 slave 节点,此时再从新的 master 中同步数据,将会造成大量的数据丢失

解决:

  • 通常采用隔离(Fencing)机制
  • 共享存储Fencing:确保只有一个 Master 往共享存储中写数据
  • 客户端Fencing:确保只有一个 Master 可以响应客户端的请求
  • Slave Fencing:确保只有一个 Master 可以向 Slave 下发命令
  • 配置文件中修改参数
min-replicas-to-write 3        # 表示连接到 master 的最少slave数量
min-replicas-max-lag 10        # 表示 slave 连接到 master 的最大延迟时间

按照上面配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话 master 就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的 master 节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失

看完不赞,都是坏蛋
看完不赞,都是坏蛋

本文较长,能看到这里的都是好样的,成长之路学无止境
今天的你多努力一点,明天的你就能少说一句求人的话!
很久很久之前,有个传说,据说:
看完不赞,都是坏蛋