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天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
在命令行里,可以通过 save
或 bgsave
,命令来手动立刻触发生成内存快照
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采用的分区方式
预设虚拟槽,每个槽就相当于一个数字,有一定范围。每个槽映射一个数据子集,一般比节点数大
- 把16384槽按照节点数量进行平均分配,由节点进行管理
- 对每个key按照CRC16规则进行hash运算
- 把hash结果对16383进行取余
- 把余数发送给Redis节点
- 节点接收到数据,验证是否在自己管理的槽编号的范围
如果在自己管理的槽编号范围内,则把数据保存到数据槽中,然后返回执行结果
如果在自己管理的槽编号范围外,则会把数据发送给正确的节点,由正确的节点来把数据保存在对应的槽中
需要注意的是: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,而是采用了一种简单的贪心策略。
- 从过期字典中随机 20 个 key;
- 删除这 20 个 key 中已经过期的 key;
- 如果过期的 key 比率超过 1/4,那就重复步骤 1;
配置文件
hz 10 ##默认情况下,每秒执行10次过期key扫描(100ms/次)
redis默认是每隔 100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载。
惰性删除(被动)
所谓惰性策略就是在客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。
总结:定期删除是集中处理,惰性删除是零散处理。
内存淘汰策略
为什么需要内存淘汰策略
有了以上过期策略的说明后,就很容易理解为什么需要淘汰策略了,因为不管是定期采样删除还是惰性删除都不是一种完全精准的删除,就还是会存在key没有被删除掉的场景,同里redis服务器内存容量有限,所以就需要内存淘汰策略进行补充。
通过阅读redis的配置文件,所提供的内存淘汰策略一共有8种
淘汰策略
- noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
- allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
- volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
- allkeys-random:加入键的时候如果过限,从所有key随机删除
- volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
- volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
- volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
- allkeys-lfu:从所有键中驱逐使用频率最少的键
LRU:最久没有使用
LFU:使用频率最少
配置文件
maxmemory-policy noeviction ##redis默认采用的是noeviction策略,即容量满了后,返回错误。不会淘汰任何key。
缓存击穿
概念
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期的某条),这时由于并发用户特别多,同时读取缓存没读到这条数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案
- 设置热点数据永远不过期。
- 加互斥锁,互斥锁参考代码如下
缓存穿透
概念
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截(布隆过滤器);
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存雪崩
概念
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。