redis缓存详解(从入门到精通)

引言

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

数据结构

Redis是基于内存的数据储存服务,它支持key-value查询操作,value存在以下五种数据类型

  • 字符串(strings)
  • 散列(hashes)
  • 表(lists)
  • 集合(sets)
  • 有序集合(sorted sets)

redis安装

下载

地址:http://download.redis.io/releases/

解压

##解压
tar -xzvf redis-6.0.6.tar.gz

编译

进入解压的目录,运行make命令

##使用gcc编译,gcc版本需要大于5.0,查看 gcc版本:gcc -v
make

##若需要指定安装目录,则加PREFIX选项
make install PREFIX=/usr/local/redis

##如果gcc小于5,需要先升级gcc,依次运行以下命令
yum install centos-release-scl
yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash

常用命令

String

Redis 字符串数据类型的相关命令用于管理 redis 字符串值,常用操作命令如下:

set key value        ##设置key的值为value
mset key1 value1 key2 value2 [key value]      ##同里设置多个
get key        ##获取key的value值
mget key1 key2 key3 [key]        ##同里获取多个key的value值
del key        ##删除key,跟value类型无关
incr key        ##对num型的key进行++1操作,decr减1
incrby key incrememt        ##对num型的key加上increment,decrby减n
append key value        ##将value的值进行字符串连接操作,加在原value末尾
strlen key      ##获取value字符串长度
setnx key value        ##只有key不存在时设置key的值
exists key1 key2 key3        ##判断某个key是否存在,跟value类型无关,返回存在key的个数

Hash

hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象

hset key field1 value1 field2 value2 [field value] ##添加key,给key设置字段以及相应的value值,可以同里设置多个field,hmset同样
hget key field         ##获得key的其中一个field字段的value值
hmget key field         ##获得key的一个或多个field字段的value值
hdel key field1 field2        ##删除key的一个或多个field字段
hexists key field        ##判断某个key中的field字段是否存在
hkeys key        ##获取key的所有存在的field字段
hvals key        ##获取key的所有value
hlen key        ##获取key存在的字段field个数
hsetnx key field value        ##当只有key中的field字段不存在的时候,将field值设为value
hincrby key field increment        ##将key中的某个num字段filed,自增increment 

List

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

lpush key element1 element2 element3    ##从左边往右边推,依次推e1 e2 e3,(lpushx,如果list存在,才插入)
rpush key element1 element2 element3    ##从右边往左边推,依次推e1 e2 e3  (rpushx,如果list存在,才插入)
lpop key    ##从左边往外弹(取),取出最左边的元素,取了该元素就没了
lpop key    ##从右边往外弹(取),取出最右边的元素,取了该元素就没了
lrange start end    ##取出list中某几个值,start和end可以是正反向索引,(正向索引从最左边0开始,反向索引从最右边-1开始),start end都是include
lindex key index    ##根据索引取出list中的元素
llen key    ##list的长度
LSET key index value    ##设置设置list某个位置的元素

##根据参数COUNT的值,移除列表中与参数 VALUE 相等的元素。
###count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
###count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
###count = 0 : 移除表中所有与 VALUE 相等的值。
lrem key count value

##将source列表中最右边的元素取出来,再放入到destination列表中最左边
RPOPLPUSH source destination
##将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
LINSERT key BEFORE|AFTER pivot value
BLPOP key timeout    ##弹出list中最左边的值,如果不存在,则阻塞timout秒
BRLPOP key timeout    ##弹出list中最右边的值,如果不存在,则阻塞timout秒
BRPOPLPUSH source destination timeout    ##将source列表中最右边的元素取出来,再放入到destination列表中最左边,如果source没有元素,则阻塞timeout秒。

Set

集合Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)

sadd key member1 member2 member3    ##向集合中添加1个或多个成员,自动去重
srem key memeber1 memeber2    ##移除集中中一个或多个元素
scrad key    ##获取集合的成员数
SMEMBERS key    ##获取集中中的所有成员
SISMEMBER key member    ##判断memeber是否是集合key中的成员
sdiff key1 key2    ##差集(返回集合1中有的,而集合2中没有的数据)
SDIFFSTORE destination key1 [key2]    ##将差集存在destination当中
SINTER key1 [key2]    ##交集
SINTERSTORE destination key1 [key2]    ##将交集存储在destination
SUNION key1 key2    ##并集
SUNIONSTORE destination key1 [key2]      ##将并集储存在destination
SMOVE source destination member    ##将元素memeber从source集合移动到destination中
spop key      ##移除并返回集合中的一个随机元素

##返回集合中一个或多个随机元素
###如果count>0 且 count<size,则返回count个不重复的元素,如果count>size,则返回集合的全部元素(不重复)
###如果count<0,则返回count绝对值个元素,无论count绝对值是否大于size,都有可能重复。
SRANDMEMBER key [count]

Sorted Set

有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复

ZADD key score1 member1 [score2 member2]        ##向有序集合添加一个或多个成员,或者更新已存在成员的分数
zcard key        ##返回有序集合中的成员数量
zrange key start end [withscores]        ##返回指定索引区间的memeber,withscores可以把sroce也返回来,,ZREVRANGE从高到低
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]        ##返回分数在指定范围内的成员
scount key min max        ##返回分数在min和max区间范围内的成员,min和max都是Including
ZINCRBY key increment member        ##对有序集合中key的member成员分数加上increment
zrank key member        ##返回member的索引(它的索引是score排过序后的索引),ZREVRANK,从高到低
zrem key member [member ...]        ##移除有序集合中一个或多个成员
zremrangebyrank key start top        ##移除有序集合中指定rank区间的成员
zremrangebyscore key min max        ##移除有序集中中指定score区间的成员,ZREVRANGEBYSCORE从高到低
zscore key member        ##返回有序集合,成员的分数值

HyperLogLog

HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

PFADD key element1 element2 element3        ##添加元素
PFCOUNT key        ##计算基数
PFMERGE destination key1 key2        ##将多个 HyperLogLog 合并为一个 HyperLogLog

bitmap

bitmap就是通过最小的单位bit来进行0或者1的设置,表示某个元素对应的值或者状态。
一个bit的值,或者是0,或者是1;也就是说一个bit能存储的最多信息是2。

setbit key offset value      ##将key这个bitmap上的offset位置设为value,value只能是0或1
getbit key offset       ##获取第offset位置上的value值

##以下的区间是的字节为b为单位,即0 0,则第1个8个二进制位
bitcount key [start end]     ##统计bit为1的个数,可以指定区间,如果没有指定,则获取全部
BITPOS key bit [start end]     ##查找bitmap中,首个值为bit(0或1)的位置(索引),可以指定区间
bitOp        ##对二进制位进行位运算 and or 

redis事务

multi        ##标记一个事务块开始
exec        ##执行事务块中的所有命令
DISCARD        ##取消事务

unwatch        ##取消watch命令对所有key的监视
watch key1 key2 ...      ##监视一个或多个key,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。类似Java中的cas操作

其它操作

auth password        ##如果有密码,登陆后,要输入密码
select index        ##选择数据库 0- 15 
dbsize        ##当前库中key的数量
keys parttern        ##查看当前库中parttern匹配成功的所有key
flushdb      ##删除当前库中的所有数据
flushall        ##删除整个redis的数据
expire key seconds        ##设置过期时间,单位秒,pexpire毫秒
EXPIREAT timestamp        ##设置过期时间,在指定时间点过期,接受unix timestamp,pexpireat毫秒时间戳
ttl key        ##返回key的过期时间,pttl毫秒
PERSIST key      ##移除key的过期时间
exists key      ##检查key是否存在
move key dbindex      ##将当前数据库中的Key-value移至指定的db中
rename key newname      ##为key修改名字,新名字newname
type key    ##返回key所存储值的类型
save      ##立即生成内存快照rdb文件

redis配置

基本配置

bind 0.0.0.0     ##绑定IP
port 6379      ##监听端口
dir "/home/server/redis-16379/data"        ##数据存储目录   rdb aof log文件的存放目录
pidfile /var/run/redis_6379.pid      ##pid文件位置
logfile ""      ##log文件位置,默认没有,则输出到 /dev/null
databases 16      ##数据库个数,默认16,即0-15
daemonize no      ##是否后台运行
supervised no      ##  no upstart systemd auto
requirepass 123456   ##设置auth认证时的密码123456,默认没有密码
rename-command FLUSHDB FSAEWQFREWQFEWQ23        ##禁用flushdb
rename-command FLUSHALL FSAEWQFREWQFEWQ32s      ##禁用flushall
maxmemory 200m      ##最大可占用内存,一般设为物理内存的80%左右,默认大小等于物理内存
maxclients 10000      ##最大client连接数量
maxmemory-policy noeviction        内存满了后,数据淘汰策略

redis持久化

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

RDB内存快照

RDB是redis默认开启的持久化方式

工作方式

当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:

  • Redis 调用forks. 同时拥有父进程和子进程。
  • 子进程将数据集写入到一个临时 RDB 文件中。
  • 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益,与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
在命令行里,可以通过 savebgsave,命令来手动立刻触发生成内存快照

RDB配置

dbfilename dump.rdb      ##内存快照物理文件地址
save 60 1000        ##该设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集,可以设置多个

##配置文件里面,有以下默认配置
save 900 1
save 300 10
save 60 10000

AOF日志追加

工作方式

AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制:

  • Redis 执行 fork() ,现在同时拥有父进程和子进程。
  • 子进程开始将新 AOF 文件的内容写入到临时文件。
  • 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
  • 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
  • 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。
    AOF吸取RDB的优点,在第一次使用AOF的时候,

AOF配置

appendfilename "appendonly.aof"        ##aof文件存储位置
appendonly yes        ##打开aof功能,默认no

###aof写入磁盘的机制,关闭aof后,可以将他们都注释,默认是appendfsync everysec,以下三种方式3选1
# appendfsync always          ##每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全
# appendfsync everysec      ##每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。(默认)
# appendfsync no          ##从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。

aof-use-rdb-preamble yes        ##aof结合rdb的功能,默然开启,建议开启

在redis的配置文件中,大概1000多行的位置,有一个描述

redis的AOF持久功能,实际是结合了rdb的功能的,在首次进行AOF追加的时候,会将内存现有的数据以RDB快照的形式储存在aof文件的开始位置,然后如果有追加,就会在AOF文件中将command加入到末尾。
也可以通过命令BGREWRITEAOF,将redis现在的数据,以RDB快照的形式重写进aof文件中,并覆盖aof文件中原有的数据(命令记录就消失,只有rdb快照,文件变小了))。
存有rdb快照的aof文件内容如下:

redis集群

redis主从模式

在 Redis 复制的基础上,使用和配置主从复制非常简单,能使得从 Redis 服务器(下文称 slave)能精确得复制主 Redis 服务器(下文称 master)的内容。每次当 slave 和 master 之间的连接断开时, slave 会自动重连到 master 上,并且无论这期间 master 发生了什么, slave 都将尝试让自身成为 master 的精确副本。
这个系统的运行依靠三个主要的机制:

  • 当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave , :包括客户端的写入、key 的过期或被逐出等等。
  • 当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。
  • 当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。

配置文件(slave从机上配置即可)

replicaof 172.17.0.5 6379         ##在从机上指定master主机的IP和端口
masterauth 123456          ##指定主机的认证密码
replica-read-only yes        ##从机使用只读模式

Sentinel哨兵模式

Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:

  • 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。


Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
虽然 Redis Sentinel 释出为一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis 服务器, 你可以在启动一个普通 Redis 服务器时通过给定 –sentinel 选项来启动 Redis Sentinel 。

配置文件(sentinel.conf)

##Sentinel 去监视一个名为 mymaster 的主服务器, 
##这个主服务器的 IP 地址为 127.0.0.1 , 端口号为 6379 , 
##而将这个主服务器判断为失效至少需要 2 个 Sentinel 同意 (只要同意 Sentinel 的数量不达标,自动故障迁移就不会执行)
sentinel monitor mymaster 127.0.0.1 6379 2

##master服务器的认证密码
sentinel auth-pass mymaster 123456          

##指定Sentinel 认为服务器已经断线所需的毫秒数。
##不过只有一个 Sentinel 将服务器标记为主观下线并不一定会引起服务器的自动故障迁移: 
##只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 
##服务器才会被标记为客观下线(objectively down, 简称 ODOWN ), 这时自动故障迁移才会执行。
sentinel down-after-milliseconds mymaster 60000

##多个sentinel之间投票表决超时时间
sentinel failover-timeout mymaster 180000

##parallel-syncs 选项指定了在执行故障转移时, 
##最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长。
sentinel parallel-syncs mymaster 1

启动方式

##第一种
redis-server /path/to/sentinel.conf --sentinel

##第二种
redis-sentinel /path/to/sentinel.conf 

集群启动顺序

1. 启动sentinel集群
2. 启动redis master主机
3. 启动redis slave从机

下以是当主机挂掉后,在sentinel控制台打印的日志信息

需要注意的是:
sentinel监听到master主机掉线后,会通过选举算法,产生新的master主机,并通知其它在线的从机(除刚刚被选为master的那台以外)连接到新的主机上面,所以在master和slave上的认证密码应该相同,需要在master和slave所有redis机器上面配置 masterauth password,刚刚断掉的主机如果重新启动的话,会被sentinel集群当作从机,并连接到选举产生的主机上面。同里sentinel会将现在的master监听信息固化(持久化)到自己的sentinel.conf配置文件里

Cluster模式

为什么要实现redis cluster?

1. 主从复制不能实现高可用
2. 随着公司发展,用户数量增多,并发越来越多,业务需要更高的QPS,而主从复制中单机的QPS可能无法满足业务需求
3. 数据量的考虑,现有服务器内存不能满足业务数据的需要时,单纯向服务器添加内存不能达到要求,此时需要考虑分布式需求,把数据分布到不同服务器上
4. 网络流量需求:业务的流量已经超过服务器的网卡的上限值,可以考虑使用分布式来进行分流
5. 离线计算,需要中间环节缓冲等别的需求

常见的分区方式

全量数据,单机Redis节点无法满足要求,按照分区规则把数据分到若干个子集当中

  • 节点取余分区:

    节点取余方式是非常简单的一种分区方式
    节点取余分区方式有一个问题:即当增加或减少节点时,原来节点中的80%的数据会进行迁移操作,对所有数据重新进行分布
    节点取余分区方式建议使用多倍扩容的方式,例如以前用3个节点保存数据,扩容为比以前多一倍的节点即6个节点来保存数据,这样只需要适移50%的数据。数据迁移之后,第一次无法从缓存中读取数据,必须先从数据库中读取数据,然后回写到缓存中,然后才能从缓存中读取迁移之后的数据
  • 一致性哈希分区:

    对每一个key进行hash运算,被哈希后的结果在哪个token的范围内,则按顺时针去找最近的节点,这个key将会被保存在这个节点上。

    在上面的图中,有4个key被hash之后的值在在n1节点和n2节点之间,按照顺时针规则,这4个key都会被保存在n2节点上,
    如果在n1节点和n2节点之间添加n5节点,当下次有key被hash之后的值在n1节点和n5节点之间,这些key就会被保存在n5节点上面了
    在上面的例子里,添加n5节点之后,数据迁移会在n1节点和n2节点之间进行,n3节点和n4节点不受影响,数据迁移范围被缩小很多
    同理,如果有1000个节点,此时添加一个节点,受影响的节点范围最多只有千分之2
    一致性哈希一般用在节点比较多的时候
  • 虚拟槽位分配:
    虚拟槽分区是Redis Cluster采用的分区方式
    预设虚拟槽,每个槽就相当于一个数字,有一定范围。每个槽映射一个数据子集,一般比节点数大
    1. 把16384槽按照节点数量进行平均分配,由节点进行管理
    2. 对每个key按照CRC16规则进行hash运算
    3. 把hash结果对16383进行取余
    4. 把余数发送给Redis节点
    5. 节点接收到数据,验证是否在自己管理的槽编号的范围
      如果在自己管理的槽编号范围内,则把数据保存到数据槽中,然后返回执行结果
      如果在自己管理的槽编号范围外,则会把数据发送给正确的节点,由正确的节点来把数据保存在对应的槽中

需要注意的是:Redis Cluster的节点之间会共享消息,每个节点都会知道是哪个节点负责哪个范围内的数据槽

redis cluster槽位管理

把16384个槽平均分配给节点进行管理,每个节点只能对自己负责的槽进行读写操作
由于每个节点之间都彼此通信,每个节点都知道另外节点负责管理的槽范围

客户端访问任意节点时,对数据key按照CRC16规则进行hash运算,然后对运算结果对16383进行取作,如果余数在当前访问的节点管理的槽范围内,则直接返回对应的数据
如果不在当前节点负责管理的槽范围内,则会告诉客户端去哪个节点获取数据,由客户端去正确的节点获取数据

1.每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系
2.客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与16383取余,计算自己的槽和对应节点
3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端
4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常
5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息
6.客户端向目标节点发送命令,获取命令执行结果

key过期策略

定期删除(主动)

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key。
Redis 默认会每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

  1. 从过期字典中随机 20 个 key;
  2. 删除这 20 个 key 中已经过期的 key;
  3. 如果过期的 key 比率超过 1/4,那就重复步骤 1;

配置文件

hz 10          ##默认情况下,每秒执行10次过期key扫描(100ms/次)

redis默认是每隔 100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载。

惰性删除(被动)

所谓惰性策略就是在客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。
总结:定期删除是集中处理,惰性删除是零散处理。

内存淘汰策略

为什么需要内存淘汰策略

有了以上过期策略的说明后,就很容易理解为什么需要淘汰策略了,因为不管是定期采样删除还是惰性删除都不是一种完全精准的删除,就还是会存在key没有被删除掉的场景,同里redis服务器内存容量有限,所以就需要内存淘汰策略进行补充。
通过阅读redis的配置文件,所提供的内存淘汰策略一共有8种

淘汰策略

  1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
  2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
  3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
  4. allkeys-random:加入键的时候如果过限,从所有key随机删除
  5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
  6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  8. allkeys-lfu:从所有键中驱逐使用频率最少的键

LRU:最久没有使用
LFU:使用频率最少

配置文件

maxmemory-policy noeviction        ##redis默认采用的是noeviction策略,即容量满了后,返回错误。不会淘汰任何key。

缓存击穿

概念

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

解决方案

  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁参考代码如下

缓存穿透

概念

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截(布隆过滤器);
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存雪崩

概念

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。
posted @ 2021-06-10 14:58  心若向阳花自开  阅读(2610)  评论(0编辑  收藏  举报