Redis基本数据类型、单线程模型、持久化机制、集群模式、淘汰策略、缓存穿透、击穿、雪崩、常见面试题大集合!

Redis基本数据类型、单线程模型、持久化机制、集群模式、淘汰策略、缓存穿透、击穿、雪崩、常见面试题大集合!

文章目录

Redis的数据类型

string

最简单的字符串类型键值对缓存,也是最基本的,二进制安全

相关命令

get/set/del:查询/设置/删除

set rekey data:设置已经存在的key,会覆盖

setnx rekey data:设置已经存在的key,不会覆盖,还是原来的值

set key value ex time:设置带过期时间的数据

expire key:设置过期时间

ttl:查看剩余时间,-1永不过期,-2过期

append key:合并字符串

strlen key:字符串长度

incr key:累加1

decr key:类减1

incrby key num:累加给定数值

decrby key num:累减给定数值

getrange key start end:截取数据,end=-1 代表到最后

setrange key start newdata:从start位置开始替换数据

mset:连续设值

mget:连续取值

msetnx:连续设置,如果存在则不设置

其它命令

select index:切换数据库,总共默认16个

keys *:查看所有的key (不建议在生产上使用,有性能影响)

type key:key的类型

flushdb:删除当前下边db中的数据

flushall:删除所有db中的数据

hash

类似map,存储结构化数据结构,比如存储一个对象(不能有嵌套对象),适合存储一些简单的json格式的数据,这种存储结构可以使数据更加清晰明了

相关命令

hset key property value:

hset user name zhangsan

创建一个user对象,这个对象中包含name属性,name值为zhangsan

hget user name:获得用户对象中name的值

hmset:设置对象中的多个键值对,会覆盖

hmset user age 18 phone 139123123

创建/设置user对象,这个对象中包含age和phone属性,age值为18,phone值为139123123

hmsetnx:设置对象中的多个键值对,不会覆盖

hmget:获得对象中的多个属性

hmget user age phone

hgetall user:获得整个对象的内容

hincrby user age 2:累加属性

hincrbyfloat user age 2.2:累加属性

hlen user:有多少个属性

hexists user age:判断属性是否存在

hkeys user:获得所有属性

hvals user:获得所有值

hdel user:删除对象

list

列表,按照String元素插入顺序排序,类似与栈,先进后出,底层数据结构是压缩列表和双向链表两种

例:

lpush mylist aaa

lpush mylist bbb

lpush mylist ccc

lrange mylist 0 -1(返回“ccc” “bbb” “aaa”)

相关命令

lpush userList 1 2 3 4 5:构建一个list,从左边开始存入数据

rpush userList 1 2 3 4 5:构建一个list,从右边开始存入数据

lrange list start end:获得数据

lpop:从左侧开始拿出一个数据

rpop:从右侧开始拿出一个数据

llen list:list长度

lindex list index:获取list下标的值

lset list index value:把某个下标的值替换

linsert list before/after value:插入一个新的值

lrem list num value:删除几个相同数据

ltrim list start end:截取值,替换原来的list

set

无序集合,通过哈希表实现,不允许重复,添加、删除或查询某一元素等操作。需要说明的是,这些操作的时间复杂度为O(1),可以很轻易的达到去重的效果,比如可以将所有关注某个博主的粉丝存在同一个集合中,同时redis提供了方便的求交集、并集、差集等操作,所以可以非常方便的实现共同关注、共同喜好的功能。

相关命令

sadd set duck pig cow sheep sheep sheep pig:构建一个set,只能存入不重复的4个数据(duck pig cow sheep)

smembers set:查看set中所有的数据

scard set:查看set中有多少个数据

sismembers set pig:判断pig在不在set里,1代表在,0代表不存在

srem set duck:删除set中的duck数据,1代表成功删除1项,0代表失败

spop set count:在set中随机删除count个数据

srandmember set count:从set中随机获取count个数据

smove set1 set2 10:从set1中移动10到set2中,1代表成功移动1项

sdiff set1 set2:列出在set1不在set2的数据,差集

sinter set1 set2:列出在set1且在set2的数据,交集

sunion set1 set2:列出set1和set2并集的数据

zset(sorted set:排序的set)

排序的set,可以去重可以排序,比如可以根据用户积分做排名,积分作为set的一个数值,根据数值可以做排序。set中的每一个memeber都带有一个分数

相关命令

zadd zset 10 value1 20 value2 30 value3:设置member和对应的分数

zrange zset 0 -1:查看所有zset中的内容

zrange zset 0 -1 withscores:带有分数

zrank zset value:获得对应的下标

zscore zset value:获得对应的分数

zcard zset:统计个数

zcount zset 分数1 分数2:统计个数

zrangebyscore zset 分数1 分数2:查询分数之间的member(包含分数1 分数2)

zrangebyscore zset (分数1 (分数2:查询分数之间的member(不包含分数1 和 分数2)

zrangebyscore zset 分数1 分数2 limit start end:查询分数之间的member(包含分数1 分数2),获得的结果集再次根据下标区间做查询

zrem zset value:删除member

其他

用于计数的HyperLogLog,用于支持存储地理位置信息的Geo等

用于布隆过滤器,用于判断数据是否存在(解决缓存穿透的方案之一)

Redis 架构单线程模型原理解析

阻塞与非阻塞

阻塞是值指服务器必须要把第一个客户的请求处理完成,才能处理第二个客户的请求。非阻塞是指服务器可以不能第一个用户响应完成请求即可处理第二甚至多个用户的请求。

多路复用器

多路复用器是值一个用户请求后的后续处理操作不由多路复用器处理,交由后续处理,该多路复用器可以接着处理下一个用户的请求。

Redis的线程模型

建立连接时,Redis-Cli会发送一个Read事件到Redis-Server,然后Redis-Server里就会出现一个Server Socket(即socket连接),再交由多路复用器,多路复用器拿到后就会丢到队列里,队列都会到达文件事件分配器,文件事件分配器检测到是一个Read事件后,就会交由连接应答处理器,这时就代表Redis-Cli和Redis-Server之间建立了连接,之后Read标识的事件就会交由命令请求处理器。后续假设Redis-Cli发送一个set的请求,就会走同样的路线,走到命令请求处理器,处理请求,Write标识的事件代表是回写,如OK,会通过同样的路线走到命令回复处理器。多个客户端同理。redis多路复用器后续操作是基于内存处理的,非常快速。

在这里插入图片描述

举例理解Redis的线程模型

在这里插入图片描述

Redis 的发布(pub)与订阅(sub)

发布与订阅模式

在这里插入图片描述

同一个redis服务器启动三个客户端连接,其中一个客户端模拟发布者,其他两个客户端模拟订阅者。

发布命令

PUBLISH food duck:表示推送food这个”频道“,duck这个数据

订阅命令

SUBSCRIBE food drink:表示订阅food和drink两个”频道“

PSUBSCRIBE China*:批量订阅,表示China这个字符开头的”频道“都订阅

注意

只能起到基本的发布订阅作用,不能保证消息的容错性和可靠性,如果项目中发布的消息比较重要,不建议使用redis做消息处理,推荐使用MQ。

Redis的持久化机制

官网地址

https://redis.io/docs/manual/persistence/

Redis的持久化机制的作用

如果没有使用redis持久化机制,redis中的数据库就会与服务器的存活时间一样,服务器宕机redis数据就会消失。

但是实际开发中,很少使用redis持久化,redis只是作缓存使用,如果想要持久化还是会使用关系型数据库。

Redis的持久化机制 - RDB

RDB: Redis DataBase
AOF: Append Only File

1. 什么是RDB

RDB:每隔一段时间,把内存中的数据写入磁盘的临时文件,作为快照,恢复的时候把快照文件读进内存。如果宕机重启,那么内存里的数据肯定会没有的,那么再次启动redis后,则会恢复。

2. 备份与恢复

内存备份 --> 磁盘临时文件
临时文件 --> 恢复到内存

3. RDB优劣势
  • 优势
    1. 每隔一段时间备份,全量备份
    2. 灾备简单,可以远程传输
    3. 子进程备份的时候,主进程不会有任何io操作(不会有写入修改或删除),保证备份数据的的完整性
    4. 相对AOF来说,当有更大文件的时候可以快速重启恢复
  • 劣势
    1. 发生故障是,有可能会丢失最后一次的备份数据
    2. 子进程所占用的内存比会和父进程一模一样,如会造成CPU负担
    3. 由于定时全量备份是重量级操作,所以对于实时备份,就无法处理了
4. RDB的配置
  1. 保存位置,可以在redis.conf自定义:
    /user/local/redis/working/dump.rdb
  2. 保存机制:
save 900 1    		# 如果1个缓存更新,则15分钟后备份
save 300 10   		# 如果10个缓存更新,则5分钟后备份
save 60 10000       # 如果10000个缓存更新,则1分钟后备份
save 10 3           # 演示:更新3个缓存,10秒后备份

stop-writes-on-bgsave-error yes    		# yes:如果save过程出错,则停止写操作;no:可能造成数据不一致
rdbcompression yes						# yes:开启rdb压缩模式;no:关闭,会节约cpu损耗,但是文件会大,道理同nginx
rdbchecksum yes							# yes:使用CRC64算法校验对rdb进行数据校验,有10%性能损耗;no:不校验
dbfilename dump.rdb						# rdb文件名称
总结

RDB适合大量数据的恢复,但是数据的完整性和一致性可能会不足

Redis的持久化机制 - AOF

1. 引子

RDB会丢失最后一次备份的rdb文件,但是其实也无所谓,其实也可以忽略不计,毕竟是缓存,丢了就丢了,但是如果追求数据的完整性,那就的考虑使用AOF了。

2. AOF特点
  1. 以日志的形式来记录用户请求的写操作。读操作不会记录,因为写操作才会存存储。
  2. 文件以追加的形式而不是修改的形式。
  3. redis的aof恢复其实就是把追加的文件从开始到结尾读取执行写操作。
3. AOF优劣势
  • 优势
  1. AOF更加耐用,可以以秒级别为单位备份,如果发生问题,也只会丢失最后一秒的数据,大大增加了可靠性和数据完整性。所以AOF可以每秒备份一次,使用fsync操作。
  2. 以log日志形式追加,如果磁盘满了,会执行 redis-check-aof 工具
  3. 当数据太大的时候,redis可以在后台自动重写aof。当redis继续把日志追加到老的文件中去时,重写也是非常安全的,不会影响客户端的读写操作。
  4. AOF 日志包含的所有写操作,会更加便于redis的解析恢复。
  • 劣势
  1. 相同的数据,同一份数据,AOF比RDB大
  2. 针对不同的同步机制,AOF会比RDB慢,因为AOF每秒都会备份做写操作,这样相对与RDB来说就略低。 每秒备份fsync没毛病,但是如果客户端的每次写入就做一次备份fsync的话,那么redis的性能就会下降。
  3. AOF发生过bug,就是数据恢复的时候数据不完整,这样显得AOF会比较脆弱,容易出现bug,因为AOF没有RDB那么简单,但是呢为了防止bug的产生,AOF就不会根据旧的指令去重构,而是根据当时缓存中存在的数据指令去做重构,这样就更加健壮和可靠了。
4. AOF的配置
# AOF 默认关闭,yes可以开启
appendonly no

# AOF 的文件名
appendfilename "appendonly.aof"

# no:不同步
# everysec:每秒备份,推荐使用
# always:每次操作都会备份,安全并且数据完整,但是慢性能差
appendfsync everysec

# 重写的时候是否要同步,no可以保证数据安全
no-appendfsync-on-rewrite no

# 重写机制:避免文件越来越大,自动优化压缩指令,会fork一个新的进程去完成重写动作,新进程里的内存数据会被重写,此时旧的aof文件不会被读取使用,类似rdb
# 当前AOF文件的大小是上次AOF大小的100% 并且文件体积达到64m,满足两者则触发重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
5. 到底采用RDB还是AOF呢?
  1. 如果你能接受一段时间的缓存丢失,那么可以使用RDB
  2. 如果你对实时性的数据比较care,那么就用AOF
  3. 使用RDB和AOF结合一起做持久化,RDB做冷备,可以在不同时期对不同版本做恢复,AOF做热备,保证数据仅仅只有1秒的损失。当AOF破损不可用了,那么再用RDB恢复,这样就做到了两者的相互结合,也就是说Redis恢复会先加载AOF,如果AOF有问题会再加载RDB,这样就达到冷热备份的目的了。

Redis 主从(读写分离)架构

主从架构

单实例的redis可以为数据库做到一个屏障的作用,一般来说单机单个节点,redis的并发在5-6万左右,存在瓶颈,可以使用redis作一个主从架构(也叫做读写分离结构),一般情况,用户大部分请求都是读请求,可以交给从redis做,少部分写的请求可以交给主redis做。

在这里插入图片描述

主从原理

从节点slave配置并且启动后,会发送一个ping包给master节点,ping通后,master节点会将一个RDB文件从内存里拿出来,放入磁盘,通过内网传输传递给slave节点,slave节点在拿到RDB文件文件首先进行下载到磁盘,再加载到内存中,这第一次相当于init操作,后续只要有写操作, 都会将相应的数据传输给slave节点,这样slave节点就会修改自己内存中的数据,这样master节点和slave节点之间就可以进行数据同步,slave节点就能为外部提供读的请求了。且master节点的写操作和slave节点的读操作互不影响,互不阻塞,因为这两个节点在数据没有同步完成,都会使用老的数据进行操作,初次是全量同步,后续同步为增量。注意:使用主从架构必须要开启master节点的持久化,如果不开启,一旦master节点宕机,虽然slave节点仍然可以提供读的服务,但是一旦master节点重新恢复运作,此时其内存里是没有任何数据的,这样再次作数据同步时会将slave节点数据清空。

在这里插入图片描述

相关配置
# 配置主节点的ip和端口号
# slaveof <masterip> <masterport>
slaveof 192.168.0.131 6379

# 主节点master的密码
# masterauth <master-password>
masterauth 123456

# 所有的从节点默认yes只有读的权限,没有写的权限
slave-read-only yes在配置主从模式时,通常都是一主二从的模式。

主从模式

一般使用一主二从,从节点过多可能会导致同步时一直在占用内网网络带宽。如果一主二从的模式还是不能满足我们并发的响应需求,可以采取下面这种,从节点再套从节点的模式,实现第二个主从,即树状结构

在这里插入图片描述

无磁盘化复制

上面介绍的主从复制模式是redis默认的磁盘复制,redis还有一种无磁盘化复制,通过内存传输的,是通过socket方式,基于网络的,这种方式主要是一因为服务器的磁盘类型,云服务器的硬盘类型如果是最普通的机械硬盘,那么它的磁盘读写(即IO)是相当底下的,此时这种无磁盘化复制就避免了磁盘读写,从而提高效率。如果此时内网带宽高,就可以采用无磁盘化复制方式替换默认的磁盘复制方式,这种方式默认是关闭的,可以在redis的配置文件中开启。

在这里插入图片描述

相关配置
# 默认关闭无磁盘化复制方式
repl-diskless-sync no

总结

  • 一个主节点,对应着多个从节点,主节点负责写操作,从节点负责读操作
  • 主节点挂了,从节点默认不会推举出一个新的主节点,而是保持slave状态,等待master重新连接
  • 主从无法保证缓存的高可用性
  • 通常使用一主二从的机制,如果有资源配置更多节点,不建议使用主从模式

Redis 缓存过期处理与内存淘汰机制

引子

计算机内存有限,越大越贵,Redis的高并发高性能都是基于内存的,用硬盘的话GG。

Redis 缓存过期机制

redis的高并发、高性能都是基于内存的,Redis 缓存过期机制是针对设置过expire过期时间的key,然后expire时间已经过了,这些key虽然查询不了,但是还是会占用服务器的内存,故存在以下两种策略。

  • (主动)定期删除
    • (主动)定期删除表示redis可以定期定时的检查,抽查随机的key,抽查的时间默认是一秒10次,可以设置,当抽查的key过期就会删除,删除的key就不会占用服务器内存了。
  • (被动)惰性删除
    • (被动)惰性删除表示当客户端请求到过期的key,redis才会将过期的key在内存中删除,这种策略优点对cpu会比较友好,缺点是过期的key在没被请求到一直会占用内存。

redis默认每秒10次的随机检测过期的key,根据自己系统的情况,可用设置1-500次,但是设置的越高,占用的cpu资源越高,通常不建议超过100次

# By default "hz" is set to 10. Raising the value will use more CPU when
# Redis is idle, but at the same time will make Redis more responsive when
# there are many keys expiring at the same time, and timeouts may be
# handled with more precision.
#
# The range is between 1 and 500, however a value over 100 is usually not
# a good idea. Most users should use the default of 10 and raise this up to
# 100 only in environments where very low latency is required.
hz 10

Redis 内存淘汰机制

计算机的内存是有限的,当一台计算机上部署了除redis的如tomcat、kafka等其他中间件,也会占用内存,故redis提供可以设置其占用内存的阈值,设置阈值后,redis会主动清理那些存在内存永久存在没有过期时间的key,可以在配置文件配置。

  • MEMORY MANAGEMENT
    • 内存占满了,可以使用硬盘,来保存,但是没意义,因为硬盘没有内存快,会影响redis性能。所以,当内存占用满了以后,redis提供了一套缓存淘汰机制:MEMORY MANAGEMENT
  • maxmemeory
    • 当内存已使用率到达,则开始清理缓存
6种内存淘汰策略
  • noeviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非设置过期时间的key集合。(推荐使用)
  • allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。(不推荐使用)
  • volatile-lru:从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。
  • volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。
淘汰策略的选择
  • 在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的。
  • 如果所有数据访问概率大致相等时,可以选择allkeys-random。
  • 如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略。
  • 如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的。
  • 由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了。

Redis 哨兵机制

引子

前面说到了redis的主从模式,但是如果Master挂了,slave节点就只能进行读请求,那如何保证可用性,继续实现读写操作呢?

什么是哨兵

Sentinel(哨兵)是用于监控Redis集群中Master状态的工具,是 Redis 高可用解决方案,哨兵可以监视一个或者多个redis master服务,以及这些master服务的所有从服务;当某个master服务宕机后,会把这个master下的某个从服务升级为master来替代已宕机的master继续工作。

在这里插入图片描述

配置哨兵监控master

创建并且配置sentinel.conf:

  • 普通配置

    # 端口号
    port 26379
    
    # 进程的pid,和redis主进程不是同一个pid
    pidfile "/usr/local/redis/sentinel/redis-sentinel.pid"
    
    # 工作空间
    dir "/usr/local/redis/sentinel"
    
    # 配置后台运行哨兵
    daemonize yes
    protected-mode no
    
    # 日志文件的位置
    logfile "/usr/local/redis/sentinel/redis-sentinel.log"                                                      
    
  • 核心配置

    # 配置哨兵的名称 和master的内网ip和端口号 2代表多少个哨兵在同一时间发现master连接不上就进行故障转移机制
    # 假设5个哨兵节点,有2个在同一发现master连接不上,2个节点的其中一个节点就可以开启故障转移了
    sentinel monitor mymaster 127.0.0.1 6379 2
    
    # 密码
    sentinel auth-pass <master-name> <password>
      
    # master被sentinel认定为失效的间隔时间,默认为30S
    sentinel down-after-milliseconds mymaster 30000
      
    # 剩余的slaves重新和新的master做同步的并行个数
    sentinel parallel-syncs mymaster 1
      
    # 主备切换的超时时间,哨兵要去做故障转移,这个时候哨兵也是一个进程,如果他没有去执行,超过这个时间后,会由其他的哨兵来处理
    sentinel failover-timeout mymaster 180000                                                     
    

启动哨兵 x 3

redis-sentinel sentinel.conf

测试

  1. master挂了,看slave是否成为master
  2. master恢复,观察slave状态

解决原Master恢复后不同步问题

问题:原来的Master(191)恢复成Slave后,他的同步状态不OK,状态为master_link_status:down,这是为什么呢?

这是因为我们只设置了192和193的masterauth,这是用于同步master的数据,但是191一开始是master是不受影响的,当master转变为slave后,由于他没有设置masterauth,所以他不能从新的master同步数据,随之导致info replication的时候,同步状态为down,所以只需要修改redis.conf中的masterauth为redis的密码即可。

一般master数据无法同步给slave的方案检查为如下:

  1. 网络通信问题,要保证互相ping通,内网互通。
  2. 关闭防火墙,对应的端口开放(虚拟机中建议永久关闭防火墙,云服务器的话需要保证内网互通)。
  3. 统一所有的密码,通过逐台检查机器以防某个节点被遗漏。

结论

master挂了以后,由于哨兵监控,剩余slave会进行选举,选举后其中一个成为master,当原来的master恢复后,他会成为slave。

图解哨兵

一主多从

在这里插入图片描述

哨兵监控

在这里插入图片描述

故障转移

哨兵节点一般为集群模式,当只有一个哨兵节点觉得我们的redis master节点宕机,其他哨兵节点仍然可以ping通master节点,这种情况称为主观下线,有可能是由于网络的原因,可以设置quorum=2这样有两个哨兵节点感知master节点宕机,这种情况称为客观下线,这个时候就可以断定master节点宕机。之后就可以做故障转移。由于redis哨兵节点如果是双数有可能出现对半分的情况,所以尽量使得redis的节点是单数,提高客观下线准确度。

在这里插入图片描述

选举leader

哨兵之间会通过选举获取一个老大,老大就会负责故障转移的工作,即将slave转变为master,当故障转移成功后就会进行数据的同步,sentinel parallel-syncs master num可以通过这个配置进行数据的同步,num表示一次同步几台节点。

在这里插入图片描述

原master恢复

原来的master恢复后就会成为slave

在这里插入图片描述

部署约定
  • 哨兵节点要有至少3个或者奇数个节点,quorum一般设置为 哨兵节点/2+1
  • 哨兵分布式部署在不同的计算机节点,避免单节点宕机造成哨兵失效
  • 一组哨兵只监听一组主从,只让自己的redis哨兵监听自己的redis主从

哨兵信息检查

# 查看imooc-master下的master节点信息
sentinel master imooc-master

# 查看imooc-master下的slaves节点信息
sentinel slaves imooc-master

# 查看imooc-master下的哨兵节点信息
sentinel sentinels imooc-master

SpringBoot 集成Redis哨兵-配置

spring:
  redis:
    database: 1
    password: 123456
    sentinel:
      master: imooc-master
      nodes: 192.168.1.191:26379,192.168.1.192:26379,192.168.1.193:26379

Redis集群

主从及主从+哨兵模式会产生的问题

通过实现redis的主从结构和哨兵模式可以提高读请求的并发,但是单个master节点的容量是有限的,如果数据达到一定程度,会出现瓶颈,此时可以通过水平扩展的形式可以将其扩展为多个master节点和slave节点,成为多主多从,即集群,这样就可以实现海量数据的存储,实现高并发和高可用。主从结构本身也是一种集群,可以提高读请求的并发,但是容错方面会有问题,比如master节点在同步数据给slave节点,属于异步复制,假设在复制的过程中,master节点宕机,slave节点上面的数据肯定没有master节点上面节点数据新,当数据同步时,肯定存在时间延迟,在这一段时间的数据就会被丢失,即老master节点变为新的salve节点后,因为宕机未复制的数据就会丢失。这个就是主从结构存在的问题。且哨兵选举和故障转移也要耗费零点几秒甚至几秒的时间,在这段时间内,redis是无法提供写操作的,故而无法保证redis的高可用性,为保证缓存的高可用,我们可用采用redis的集群模式(通常是三主三从,也就是6个节点)。

Redis的三主三从集群模式

Redis的三主三从集群模式,每一个master节点下面都会挂上一个slave节点,如果其中某一个master节点宕机,slave节点就会成为一个新的master节点,即主从切换,而且每一个节点都知道彼之间的关系。知道自己的角色(master or slave) 且它们彼此之间可以通信和交互,这些节点的关系可以保存到某一个配置文件中,每个节点都会有该配置文件。如果客户端想要和该集群建立连接,只需要和其中某一个节点建立关系即可,如果其中某一个节点宕机,会有超过半数的节点检测到其宕机,就会发起主从切换,跟之前的哨兵模式一样。三主三从的结构是非常经典的redis架构,对比只有三个主节点形成的集群容错更好。

在这里插入图片描述

搭建Redis的三主三从集群模式

redis.conf 配置
# 开启集群模式
cluster-enabled yes

# 每一个节点需要有一个配置文件,需要6份。每个节点处于集群的角色都需要告知其他所有节点,彼此知道,这个文件用于存储集群模式下的集群状态等信息,这个文件是由redis自己维护,我们不用管。如果你要重新创建集群,那么把这个文件删了就行
cluster-config-file nodes-201.conf

# 超时时间,超时则认为master宕机,随后主备切换
cluster-node-timeout 5000

# 开启AOF
appendonly yes                                                       
启动6个redis实例
  1. 启动6台
  2. 如果启动过程出错,把rdb等文件删除清空
创建集群
#####
# 注意1:如果你使用的是redis3.x版本,需要使用redis-trib.rb来构建集群,最新版使用C语言来构建了,这个要注意
# 注意2:以下为新版的redis构建方式
#####

# 创建集群,主节点和从节点比例为1,1-3为主,4-6为从,1和4,2和5,3和6分别对应为主从关系,这也是最经典用的最多的集群模式
redis-cli -a password --cluster create ip1:port1 ip2:port2 ip3:port3 ip4:port4 ip5:port5 ip6:port6 --cluster-replicas 1
检查集群信息
redis-cli -a password --cluster check 192.168.25.64:6380
重新分配slot槽节点
redis-cli -a password --cluster fix 192.168.218.10:6379
什么是slot槽节点

只有master节点有slot槽节点,slave节点没有

构建集群slot槽节点分配信息(图例)

在这里插入图片描述

槽slot怎么分配(平均分配)

在这里插入图片描述

槽slot怎么存储(通过hash(key)%16384得到的值存到对应的槽slot里,遵循一致性hash原则,同前面的nginx)

在这里插入图片描述

槽slot举例

在这里插入图片描述

相关命令

进入集群控制台命令:redis-cli -c -a 123456 -h 192.168.218.10 -p 6379

检查集群信息:cluster info / cluster nodes

注意

get key命令可以获取集群中任何key的信息,但是keys *命令只能获取本master节点的数据,因为槽slot的原因。槽slot才是真正存储数据的地方,get key命令可以通过Redirected to slot [5798] located at 192.168.218.11:6379获取

Springboot集成Redis集群-配置

spring:
  redis:
    password: 123456
    cluster:
      nodes: 192.168.218.10:6379,192.168.218.11:6379,192.168.218.12:6379,192.168.218.13:6379,192.168.218.14:6379,192.168.218.15:6379

Redis的一致性哈希算法

前景

在Redis 集群模式Cluster中,Redis采用的是分片Sharding的方式,也就是将数据采用一定的分区策略,分发到相应的集群节点中

但是我们使用上述HASH算法进行缓存时,会出现一些缺陷,主要体现在服务器数量变动(根据业务情况动态扩容,或节点宕机等场景)的时候,所有缓存的位置都要发生改变

具体来讲就是说:当缓存服务器数量发生变化时,会引起缓存的雪崩,可能会引起整体系统压力过大而崩溃(大量缓存同一时间失效)。

基本概念

一致性Hash算法使用取模的方法,一致性Hash算法是对2^32取模,什么意思呢?

简单来说:一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32 - 1(即哈希值是一个32位无符号整形),整个哈希环如下:

在这里插入图片描述

将服务器的IP或主机名使用hash算法进行计算,确定其在hash环上的位置。

举例

假设我们有4台缓存服务器,服务器A、服务器B、服务器C,服务器D,那么,在生产环境中,这4台服务器肯定有自己的IP地址或主机名,我们使用它们各自的IP地址或主机名作为关键字进行哈希计算,使用哈希后的结果对2^32取模,可以使用如下公式示意:

 hash(服务器A的IP地址) %  2^32

上述公式算出的结果一定是一个0到2^32-1之间的一个整数,上图中的hash环上必定有一个点与这个整数对应,而我们刚才已经说明,使用这个整数代表服务器A,那么,服务器A就可以映射到这个环上。

如下图,节点ABCD通过一致性的hash算法映射到hash的对应位置

在这里插入图片描述

同样的,将数据的key通过同样的hash运算定位到其在hash环上的位置然后从此位置沿环顺时针“行走”,遇到的第一台服务器就是其应该定位到的服务器

在这里插入图片描述

容错性和可扩展性

节点宕机

现假设Node C不幸宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响,如下所示:

在这里插入图片描述

节点增加

如果在系统中增加一台服务器Node X,如下图所示:此时对象Object A、B、D不受影响,只有C需要重定位到新的Node X

一般的,在一致性Hash算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据不会受到影响。

在这里插入图片描述

总结:一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。

数据倾斜问题

概述

一致性Hash算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中服务器和key分布不均时,如下图所示:

在这里插入图片描述

此时必然造成大量数据集中到服务器1上,而只有极少量会定位到其他服务器上,从而出现hash环偏斜的情况当hash环偏斜以后,缓存往往会极度不均衡的分布在各服务器上

解决方案

如果想要均衡的将缓存分布到服务器上,最好能让这些服务器尽量多的、均匀的分布在hash环上,但是,真实的服务器资源只有4台,我们怎样让它们多起来呢?既然没有多余的物理服务器节点,我们就只能将现有的物理节点通过虚拟的方法复制出来。

这些由实际节点虚拟复制而来的节点被称为**”虚拟节点“,即对每一个服务节点计算多个哈希**,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在服务器IP或主机名的后面增加编号来实现。

例如上面的情况,假设有只有AB两台服务器,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六个虚拟节点:

在这里插入图片描述

总结:对每一个服务节点计算多个哈希,这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布 。

Redis缓存击穿,雪崩,穿透,

缓存击穿

缓存击穿:是指一个热点key,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

比如在电商项目中,这个商品就称为“爆款”。

解决方案

  1. 设置热点key永不过期
  2. 接口做限流、降级、熔断
  3. 加互斥锁(如分布式锁,同时只有一个线程能够访问这个接口,并且从数据库查询后立马缓存到redis中,其他线程放行后可从redis中查询这个key)

缓存穿透

缓存穿透:查询的key在redis中不存在,对应的id在数据库也不存在,此时被非法用户进行攻击,大量的请求会直接打在db上,造成宕机,从而影响整个系统,这种现象称之为缓存穿透。

解决方案

1. 缓存空对象

把空的数据也缓存起来,比如空字符串,空对象,空数组或list。

问题:

  1. 缓存需要更多的存储空间,用来存放更多的key
  2. 即使对空值设置了过期时间,可能会存在缓存层和存储层数据,会有一段时间的数据不一致,这对于需要保持一致性的业务会有影响。比如id=1001的这条数据先在数据库中没有,查询出来为null缓存进入了redis,后续添加了一条id=1001的数据,在其过期时间内查询该条数据结果都是null
2. 布隆过滤器

布隆过滤器是一种数据结构 ,底层维护了一个只包含0,1数据的数组。

布隆过滤器能够迅速判断一个元素是否在一个集合内,本质是二进制存储,非常小,哪怕是上亿的数据也不占内存,而且查询和添加非常快。比如一个数组,是由二进制存储,0代表不存在,1代表存在,如下,称为布隆过滤器。数据经过hash运算(一种随机映射函数),映射到数组的多个下标位置,如果所有对应的下标位置值都为1,则该数据很可能有;如果对应的下标数字有一个不为1,则一定没有该数据。

:布隆过滤器能确定一定没有该数据,但不能判断一定有该数据(可以设置布隆过滤器的参数,维护的数组越大,判断越精确,但消耗的空间也越大)

布隆过滤器的缺点

1、会有1%的误判率

2、假设存入布隆过滤器中的key已经不存在db和redis中了,但是无法在布隆过滤器中移除

3、代码的复杂度会增大,需要使用集合,集合中会包含很多key

布隆过滤器的用处

布隆过滤器可以做邮件、短信的黑名单拦截,或者是缓存穿透等。谷歌对于不良网站的拦截也是使用了布隆过滤器

在这里插入图片描述

布隆过滤器基本思想

布隆过滤器可能具有多个hash函数,db和redis一般保存数据对象,而布隆过滤器则会保存该对象对应的唯一键值,比如主键id,拿出该主键id再经过hash函数再放入布隆过滤器,计算看下图,由于误判率的存在可能subCat:9999的数据会被误判为存在,故布隆过滤器可以判断某个数据一定不存在,但是无法判断一个数据一定存在。假设要删除subCat:1,这样subCat:1和subCat:2重复的格子就会被改成0,这样再次查询subCat:2时无法查询到,所以无法移除布隆过滤器中的数据

在这里插入图片描述

解决缓存穿透的过程
  1. 将数据库中的数据hash后离线存储到布隆过滤器中
  2. 用户请求时直接从布隆过滤器中判断数据是否存在
  3. 如果布隆过滤器中没有该数据,则直接返回
  4. 如果布隆过滤器中有该数据,则从缓存或数据库中查询

在这里插入图片描述

使用布隆过滤器

引入guava依赖

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>28.1-jre</version>
</dependency>

代码:创建布隆过滤器,向过滤器中放入10000个元素,然后统计错误个数和错误率

public class BloomFilterTest extends SpringBootApplicationTest {
    @Test
    public void myTest(){
        /**
         * BloomFilter一共四个create方法,不过最终都是走向第四个。看一下每个参数的含义:
         *    funnel:数据类型(一般是调用Funnels工具类中的)
         *    expectedInsertions:期望插入的值的个数
         *    fpp 错误率(默认值为0.03)
         *    strategy 哈希算法(指定某个算法)
         */
        BloomFilter<CharSequence> bloomFilter = BloomFilter.create(
                Funnels.stringFunnel(Charset.forName("utf-8")), 10000
        );

        for (int i = 0; i < 10000; i++) {
            //向布隆过滤器中放入数据
            bloomFilter.put(String.valueOf(i));
        }
        
        //初始化误判数
        int missCount = 0;
        for (int i = 0; i < 10000; i++) {
            //判断这个数据在布隆过滤器中是否存在
            boolean exist = bloomFilter.mightContain("误判" + i);
            //如果误判了
            if (exist){
                missCount ++;
            }
        }

        System.out.println("误判个数为:" + missCount );
        System.out.println("误判率为:" + BigDecimal.valueOf(missCount).divide(new BigDecimal(10000),2,ROUND_HALF_UP));
    }
}

输出

误判个数为:318
误判率为:0.03

Redis的缓存雪崩

什么是缓存雪崩

当存在某一个时间点,redis大量key过期,但是此时又恰好有很大的并发量融入进来,那么此时所有的请求都会请求数据库上,此时数据库就会宕机崩溃。

在这里插入图片描述

雪崩预防

雪崩这种现象是解决不掉的,只能缓解,做防护措施。

  • 设置redis的key永不过期
    • 一些请求量大的key我们可以设置成永不过期,这样一定程度上可以避免一些热点key突然失效的情况
  • 过期时间错开
    • 如:某些key的过期时间为1个小时,我们可以在1小时后面加上一个随机时间(如随机3-15分钟)
  • 多缓存结合(例:->redis->memcache->db)
    • 采用多级缓存的方式:如redis的key失效时间为1h,memcache的key失效时间为1.5h;甚至当redis或者memcache中有一个宕机时,仍能保证缓存的高可用性
  • 采购第三方Redis

Redis可能会遇到的面试题

1.什么是 Redis?

2.Redis 的数据类型?

3.使用 Redis 有哪些好处?

4.Redis 相比 Memcached 有哪些优势?

5.Memcached 与 Redis 的区别都有哪些?

6.Redis 是单进程单线程的吗?为何它那么快那么高效?

7.一个字符串类型的值能存储最大容量是多少?

8.Redis 的持久化机制是什么?各自的优缺点?

9.Redis 常见性能问题和解决方案有哪些?

10.Redis 过期键的删除策略?

11.Redis 的回收策略(淘汰策略)?

12.为什么Redis 需要把所有数据放到内存中?

13.Redis 的同步机制了解么?

14.Pipeline 有什么好处,为什么要用 Pipeline?

15.是否使用过 Redis 集群,集群的原理是什么?

16.Redis 集群方案什么情况下会导致整个集群不可用?

17.Redis 支持的 Java 客户端都有哪些?官方推荐用哪个?

18.Jedis 与 Redisson 对比有什么优缺点?

19.Redis 如何设置密码及验证密码?

20.说说 Redis 哈希槽的概念?

21.Redis 集群的主从复制模型是怎样的?

22.Redis 集群会有写操作丢失吗?为什么?

23.Redis 集群之间是如何复制的?

24.Redis 集群最大节点个数是多少?

25.Redis 集群如何选择数据库?

26.怎么测试 Redis 的连通性?

27.怎么理解 Redis 事务?

28.Redis 事务相关的命令有哪几个?

29.Redis key 的过期时间和永久有效分别怎么设置?

30.Redis 如何做内存优化?

31.Redis 回收进程如何工作的?

32.都有哪些办法可以降低 Redis 的内存使用情况呢?

33.Redis 的内存用完了会发生什么?

34.一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set他们最多能存放多少元素?

35.MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证Redis 中的数据都是热点数据?

36.Redis 最适合的场景是什么?

37.假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

38.如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

39.使用过 Redis 做异步队列么,你是怎么用的?

40.使用过 Redis 分布式锁么,它是什么回事?

41.如何预防缓存穿透与雪崩?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值