Redis相关学习

  • 0. redis可视化工具
  • 1. 简单介绍
  • 2. redis缓存数据的流程
  • 2.1 redis作为缓存的原因
  • 3. redis的基本命令
  • 4. redis支持的五种数据结构以及相关命令
  • 4.1 String 字符串
  • 4.2 list 链表
  • 4.3 hash 哈希表
  • 4.4 set 集合
  • 4.5 zset 有序集合
  • 5. redis事务
  • 5.1 what's 事务
  • 5.2 redis事务操作命令
  • 5.3 redis事务的特性
  • 5.4 redis事务的实现
  • 6. redis的缓存雪崩、缓存穿透、缓存击穿
  • 6.1 缓存雪崩
  • 6.1.1 概念
  • 6.1.2 处理流程
  • 6.1.3 解决办法
  • 6.2 缓存穿透
  • 6.2.1 概念
  • 6.2.2 处理流程
  • 6.2.3 解决办法
  • 6.2.3-1 关于布隆过滤器
  • 6.2.3-1 布隆过滤器元素存入原理
  • 6.3 缓存击穿
  • 6.3.1 概念
  • 6.3.2 处理流程
  • 6.3.3 解决办法
  • 7. Redis面试20问
  • 7.1 单线程的 Redis为什么这么快?
  • 原因有以下几点:
  • 7.2 Redis 和 Memcached 的区别和共同点
  • 共同点 :
  • 区别 :
  • 7.3 redis 缓存设置过期相关
  • 7.3.1为什么Redis 要给缓存数据设置过期时间
  • 7.3.2 Redis key的过期时间和永久有效分别怎么设置?
  • 7.3.3 过期时间除了有助于缓解内存的消耗,还有什么其他作用?
  • 7.4 说一说Redis的数据过期淘汰策略
  • 7.4.1 定期删除、惰性删除策略是什么?
  • 7.4.2 两种策略结合起来以后的工作原理?
  • 7.5 说一说Redis的内存淘汰策略 / 机制----内存相关
  • Redis内存淘汰机制有以下6种策略:
  • 总结:


0. redis可视化工具

  • 连接里面新建,然后连接你的本地或者线上redis;
  • 左边可以看到默认的16个数据库,编号从0-15;
  • 选中某个数据库我们点击新增,就可以进行数据插入:可以看到redis支持的五种数据类型

1. 简单介绍

  • Redis 是基于 KV 结构的作为 Cache 使用的 NoSQL 数据库。KV:key、value。我们可以把Redis可以看成一个大Map,key存储值,value存储数据。
  • Redis在项目中是当缓存使用,可以控制数据的存和取,处理速度快,可以将经常访问的数据放到redis中。
    特点: Redis 的数据存在内存中,读写速度非常快
    用处: Redis做缓存 , Redis 来做分布式锁,Redis做消息队列。

总结:通常我们使用redis作为缓存,来减少数据库压力,提高系统性能

2. redis缓存数据的流程

  1. 如果用户请求的数据在缓存中就直接返回。
  2. 缓存中不存在的话就看数据库中是否存在。
  3. 数据库中存在的话就更新缓存中的数据。
  4. 数据库中不存在的话就返回空数据。

redis的decr为什么不能防止超卖呢_spring boot

总结:service层首先从redis中查询数据,若有直接拿;若没有,执行dao层,在去db中拿,dao层将数据返回给service,service首先将数据保存在redis中,下次若再取该数据,直接从redis中拿,redis中数据保存在内存中,用作缓存。

2.1 redis作为缓存的原因

我们在日常工作生活中的业务并发都是成千上万的, 高并发下的业务必然带来系统性能的迟钝甚至宕机, 影响用户使用体验,所以为了提升用户使用体验, 以及抗住更大流量的并发, 我们经常会使用redis作为用户和数据库直接的中间缓存 。

redis的decr为什么不能防止超卖呢_redis_02

3. redis的基本命令

redis 默认为 16 个库 (在 redis.conf 文件可配置,该文件很重要,后续很多操作都是这个配置文件) redis 默认自动使用 0 号库,可以用select 1切换库

  • ping: 沟通命令,查看状态
127.0.0.1:6379> ping
PONG
解释:输入 ping,redis 给我们返回 PONG,表示 redis 服务运行正常
  • dbsize:查看当前数据库中 key 的数目
127.0.0.1:6379> dbsize //查看0号库key数目
(integer) 5
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> dbsize //查看1号库key数目
(integer) 0
作用:返回当前数据库的key 的数量。
返回值:数字,key 的数量
  • select index:切换库命令
127.0.0.1:6379> select 1
OK
  • flushdb:删除当前库的数据
127.0.0.1:6379> dbsize
(integer) 5
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> dbsize
(integer) 0
  • redis默认使用16个库:Redis 默认使用 16 个库,从 0 到 15。
对数据库个数的修改,在 redis.conf 文件中databases 16

redis的decr为什么不能防止超卖呢_redis_03

  • redis自带的客户端退出当前redis连接:exit或quit
  • redis的key的操作命令
  • keys
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> keys k?
1) "k1"
2) "k2"
127.0.0.1:6379> keys ?1
1) "k1"

语法:keys pattern
作用:查找所有符合模式pattern的 key. pattern可以使用通配符。
通配符:
*:表示 0-多个字符,例如:keys * 查询所有的 key。 
?:表示单个字符,例如:wo?d , 匹配 word , wood
  • exists
127.0.0.1:6379> exists k1
(integer) 1
127.0.0.1:6379> exists k1 k2
(integer) 2
127.0.0.1:6379> exists k1 k2 k3
(integer) 2
127.0.0.1:6379> exists k
(integer) 0

语法:exists key [key…]
作用:判断 key 是否存在
返回值:整数;
存在 key 返回 1,其他返回 0;
使用多个 key,返回存在的 key 的数量。
  • expire
127.0.0.1:6379> expire k1 10
(integer) 1
127.0.0.1:6379> get k1
(nil)

语法:expire key seconds
作用:设置 key 的生存时间,超过时间,key 自动删除。单位是秒。
返回值:设置成功返回数字 1,其他情况是 0 。
  • ttl
127.0.0.1:6379> expire k2 60
(integer) 1
127.0.0.1:6379> ttl k2
(integer) 57
127.0.0.1:6379> ttl k2
(integer) 56
127.0.0.1:6379> ttl k2
(integer) 55

语法:ttl key
作用:以秒为单位,返回 key 的剩余生存时间(ttl: time to live)
返回值:
-1 :没有设置 key 的生存时间, key永不过期。
-2:key 不存在
数字:key 的剩余时间,秒为单位
  • -注意:在redis中存储数据时,设计时就要考虑给该数据设置多长的存活时间,否则数据在内存中会越堆越多
  • expire与ttl命令的使用:验证码的失效时间

用户点击生成验证码,后台生成一个验证码code,将该code使用set code code存到redis缓存中,并用expire code 60设置验证码有效时间为60s;用户输入验证码开始验证,后台接收到用户发送的code,进行ttl code若值大于等于0表示code正确并且没有过期。验证通过。注意:验证码不能重复,因为是key。以上的优点是redis走缓存,性能好,灵活。

  • type
127.0.0.1:6379> type k1
string
127.0.0.1:6379> type k2
string

语法:type key
作用:查看 key 所存储值的数据类型
返回值:字符串表示的数据类型
none (key不存在)、string (字符串)、
hash (哈希表)、list (列表)、set (集合)、zset (有序集)
  • del
127.0.0.1:6379> del k1
(integer) 1
127.0.0.1:6379> keys *
1) "k2"

语法:del key [key…]
作用:删除存在的 key,不存在的 key 忽略。
返回值:数字,删除的 key 的数量。

4. redis支持的五种数据结构以及相关命令

4.1 String 字符串

  • 字符串类型是 Redis 中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据,序列化后的数据,JSON 格式数据。
  • 数据结构: key-value 类型
  • redis中所有类型的key都是文本类型,即String

应用场景 :一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
常用命令:set,get,strlen,exists,dect,incr,setex 等等。

  • 1.基本命令
  • set
127.0.0.1:6379> set k1 v1   # 设置 key-value 类型的值
 OK
 127.0.0.1:6379> type k1
 string					  # redis中所有类型的key都是文本类型,即String
 127.0.0.1:6379> set k1 v2
 OK
 127.0.0.1:6379> get k1      #根据 key获得对应的 value
 "v2"                        # 向已经存在的 key 设置新的 value,会覆盖原来的值
 127.0.0.1:6379> exists key  # 判断某个 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key   # 返回 key 所储存的字符串值的长度。
(integer) 5
127.0.0.1:6379> del key      # 删除某个 key 对应的值
(integer) 1
  • get
127.0.0.1:6379> set k1 v2
OK
127.0.0.1:6379> get k1
"v2"
获取 key 中设置的字符串值
语法:get key
例如:获取 username 这个 key 对应的 value
  • incr:原子性操作,相当于i++,在多线程环境下一定能保证数据的正确性、安全性、一致性。可以做全局计数器(例如记录用户登录数,全局主键)
127.0.0.1:6379> set k2 1
OK
127.0.0.1:6379> incr k2
(integer) 2
127.0.0.1:6379> get k2
"2"

将 key 中储存的数字值加 1,如果 key 不存在,则 key 的值先被初始化为 0 再执行
incr 操作(只能对数字类型的数据操作)
语法:incr key

127.0.0.1:6379> type k1
string
127.0.0.1:6379> incr k1
(error) ERR value is not an integer or out of range
对非数字的值操作是不行的
  • decr:原子性操作,相当于i - -,可以记录当前用户在线数

注意:incr与decr若操作不存在的key,首先会set出key然后进行incr或decr

  • append
127.0.0.1:6379> get k1
 "v2"
 127.0.0.1:6379> append k1 hello
 (integer) 7
 127.0.0.1:6379> get k1
 "v2hello"
 
 语法:append key value
 说明:如果 key 存在,则将 value 追加到 key 原来旧值的末尾
 如果 key 不存在,则将 key 设置值为 value
 返回值:追加字符串之后的总长度
  • 2.常用命令
  • strlen
127.0.0.1:6379> get k1
"v2hello"
127.0.0.1:6379> strlen k1
(integer) 7

语法:strlen key
说明:返回 key 所储存的字符串值的长度
返回值:
如果key存在,返回字符串值的长度
key不存在,返回0
  • getrange:截取子串,相当于substring(),不过注意是左闭右闭
127.0.0.1:6379> getrange k1 2 6
"hello"
127.0.0.1:6379> getrange k1 0 -1
"v2hello"

语法:getrange key start end
作用:获取 key 中字符串值从 start 开始到 end 结束的子字符串,包括 start 和 end, 负数表示从字符串的末尾开始,-1 表示最后一个字符
返回值:截取的子字符串。
使用的字符串 key: school, value: bjpowernode

127.0.0.1:6379> getrange k1 0 100
"v2hello"
超出字符串范围的截取,获取合理的子串
  • setrange:替换子串,相当于replace(),左闭右闭
127.0.0.1:6379> setrange k1 2 world
(integer) 7
127.0.0.1:6379> get k1
"v2world"
127.0.0.1:6379> setrange k1 0 hello
(integer) 7
127.0.0.1:6379> get k1
"hellold"

语法:setrange key offset value
说明:用 value 覆盖(替换)key 的存储的值从 offset 开始,不存在的 key 做空白字符串。
返回值:修改后的字符串的长度

注意:修改只需要给个其实下标,相当于根据value覆盖原串

  • mset、mget:一次执行多个set、get。若没有对应key,返回nil(等同null)
127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
OK
127.0.0.1:6379> mget key1 key2 				# 批量获取多个 key 对应的 value
1) "value1"
2) "value2"
  • 过期
127.0.0.1:6379> expire key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value 
# 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56

4.2 list 链表

  • Redis 的 list 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销
  • Redis 链表是简单的字符串链表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

应用场景: 发布与订阅或者说消息队列、慢查询。
常用命令:lpush,lpop,rpush,rpop,lrange、llen 等。

  • 1.基本命令
  • lpush:头插法
127.0.0.1:6379> lpush mylist a b c
(integer) 3

语法:lpush key value [value…]
作用:将一个或多个值 value 插入到列表 key 的表头(最左边);
	 从左边开始加入值,从左到右的顺序依次插入到表头
返回值:数字,新列表的长度

redis的decr为什么不能防止超卖呢_数据库_04

  • rpush:尾插法
127.0.0.1:6379> rpush mylist a b c
(integer) 3

redis的decr为什么不能防止超卖呢_spring cloud_05

  • lpop / rpop:取元素
  • 通过 rpush/lpop 实现队列:先进先出
127.0.0.1:6379> rpush mylist a b c  #尾插法插入三个元素 实际顺序为:a b c
(integer) 3
127.0.0.1:6379> lpop myList         # 取出list的尾部第一个元素(最左边)
"a"
  • 通过 rpush/rpop 实现栈:先进后出
127.0.0.1:6379> rpush myList2 value1 value2 value3
(integer) 3
127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
"value3"
  • 如下图:
  • lrange:查看某个范围的元素
    通过 lrange 命令,你可以基于 list 实现分页查询,性能非常高。
# 我们假设尾插法插入 abc三个元素 实际顺序是 a b c
								  #下标:0 1 2
127.0.0.1:6379> rpush myList a b c
(integer) 3
127.0.0.1:6379> lrange mylist 0 3      #查看对应下标的list列表 0是start 3是end [0,3]
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> lrange mylist 0 2
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> lrange mylist 0 -1    #查看list列表中的所有元素,-1代表倒数第一个元素
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> lrange mylist -2 -1   #查看倒数第二到倒数第一个元素
1) "b"
2) "c"

语法:lrange key start stop
作用:获取列表 key 中指定区间内的元素;
0 表示列表的第一个元素,以 1 表示列表的第二个元素;
start , stop 是列表的下标值,也可以负数的下标;
-1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
start ,stop 超出列表的范围不会出现错误。
返回值:指定区间的列表元素
  • llen:通过 llen 查看链表长度:
127.0.0.1:6379> llen mylist
(integer) 3
  • lindex:获取指定下标元素,只是看看不删除
127.0.0.1:6379> lindex mylist 0
"a"
127.0.0.1:6379> lindex mylist -1
"c"
语法:lindex key index
作用:获取列表 key 中下标为指定 index 的元素,列表元素不删除,只是查询。
0 表示列表的第一个元素,以 1 表示列表的第二个元素;
start , stop 是列表的下标值,也可以负数的下标,
 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
返回值:指定下标的元素;index 不在列表范围,返回 nil(null)
  • 2.常用命令
  • lrem
127.0.0.1:6379> lrem mylist 2 a  # 表示从列表头部开始找到两个与a相等的元素且删除
(integer) 2

语法:lrem key count value
作用:根据参数 count 的值,移除列表中与参数 value 相等的元素;
count >0 ,从列表的左侧向右开始移除;
count < 0 从列表的尾部开始移除;
count = 0 移除表中所有与 value 相等的值。
返回值:数值,移除的元素个数

redis的decr为什么不能防止超卖呢_redis_06


redis的decr为什么不能防止超卖呢_数据库_07

  • lset:替换元素
127.0.0.1:6379> lset mylist 0 hello
OK

语法:lset key index value
作用:将列表 key 下标为 index 的元素的值设置为 value。
返回值:设置成功返回 ok ; key 不存在或者 index 超出范围返回错误信息
  • linsert
127.0.0.1:6379> linsert mylist before hello hi  
#将hi插入到列表mylist当中,位于值hello之前
(integer) 6
127.0.0.1:6379> linsert mylist after hello world 
#将world插入到列表mylist当中,位于值hello之后
(integer) 7
# 最后我们可以得到列表 hi hello world
语法:linsert key BEFORE|AFTER pivot value
作用:将值 value 插入到列表 key 当中位于值 pivot 之前或之后的位置。
1.key 不存在;pivot不在列表中,则不执行任何操作。
2.若有多个pivot值,则从左第一个pivot是真正的pivot
返回值:命令执行成功,返回新列表的长度。没有找到 pivot 返回 -1, key 不存在返回 0。

4.3 hash 哈希表

  • hash 类似于 JDK1.8 前的 HashMap,内部差不多是(数组 + 链表)的形式;
  • redis中的hash 是一个 string 类型的 field 和 value 的映射表,(hash) 特别适合用于存储对象;相当于Map<String, Map<String, String>>
  • 后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等;

应用场景: 系统中对象数据的存储。
常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。

  • 1.基本命令
  • hset 、hget
127.0.0.1:6379> hset website baidu www.baidu.com
 # filed域:baidu   value值:www.baidu.com
(integer) 1
127.0.0.1:6379> hget website baidu
"www.baidu.com"

语法:hset key(hash表) field value
作用:将哈希表 key 中的域 field 的值设为 value;
如果 key 不存在,则新建 hash 表,执行赋值;如果有 field ,则覆盖值。
返回值:
①如果 field 是 hash 表中新 field,且设置值成功,返回 1 
②如果 field 已经存在,旧值覆盖新值,返回 0

语法:hget key filed
作用:在hash表key中根据域filed 得到对应value(# 获取存储在哈希表中指定字段的值)
  • hmset、hmget:给hash设置多个值,获取多个值
127.0.0.1:6379> hmset student zhangsan 22 lisi 20 wangwu 23
OK
# hash表 student filed1:zhangsan ;value1:22
			    #filed2:lisi;	   value2:20
			 	#filed3:wangwu;  value3:23
127.0.0.1:6379> hmget student zhangsan lisi wangwu zhaoliu
1) "22"
2) "20"
3) "23"
4) (nil) # 没有zhaoliu这个域所以返回null(nil)
  • hgetall:获取哈希表 key 中所有的域和值
127.0.0.1:6379> hgetall student
1) "zhangsan" 	key
2) "22" 		value
3) "lisi" 		key
4) "20" 		value
5) "wangwu" 	key
6) "23" 		value

语法:hgetall key
作用:获取哈希表 key 中所有的域和值
返回值:以列表形式返回 hash 中域和域的值,key 不存在,返回空 hash
  • hdel:# 删除hash表中一个或者多个指定的key
127.0.0.1:6379> hdel student zhangsan lisi
(integer) 2
127.0.0.1:6379> hgetall student
1) "wangwu"
2) "23"

语法:hdel key field [field…]
作用:删除哈希表 key 中的一个或多个指定域 field,不存在 field 直接忽略
返回值:成功删除的 field 的数量
  • 2.常用命令
  • hkeys:# 获取 key 列表 (获取hash表中所有的key)
127.0.0.1:6379> hkeys student
1) "zhangsan"
2) "lisi"
3) "wangwu"

语法:hkeys key
作用:查看哈希表 key 中的所有 field 域
返回值:包含所有 field 的列表,key 不存在返回空列表
  • hvals:# 获取 value 列表 (获取hash表中所有的value)
127.0.0.1:6379> hvals student
1) "22"
2) "20"
3) "23"

语法:hvals key
作用:返回哈希表 中所有域的值
返回值:包含哈希表所有域值的列表,key 不存在返回空列表
  • hexists:# 查看 key 对应的 value中指定的字段是否存在。
127.0.0.1:6379> hexists student lisi
(integer) 1
127.0.0.1:6379> hexists student zhaoliu
(integer) 0

语法:hexists key field
作用:查看哈希表 key 中,给定域 field 是否存在
返回值:如果 field 存在,返回 1,其他返回 0

4.4 set 集合

  • redis 的 Set 是 string 类型的无序集合,集合成员是唯一的,即集合中不能出现重复的数据。相当于java中的HashSet
  • 集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口

应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景(如:Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能;是一个求交集的过程。)
常用命令:sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。

  • 1.基本命令
  • sadd:添加元素到set集合
#127.0.0.1:6379> sadd mySet value1 value2 
#(integer) 2
127.0.0.1:6379> sadd mySet value1 value2 value3 # 添加元素进去
(integer) 3
127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
(integer) 0

语法:sadd key member1 member2...
作用:将一个或多个 member 元素加入到集合 key 当中;
已经存在于集合的 member 元素将被忽略,不会再加入。
返回值:加入到集合的新元素的个数(不包括被忽略的元素)。
  • smembers:查看 set 中所有的元素
127.0.0.1:6379> smembers myset
1) "value1"
2) "value2"

语法:smembers key
作用:获取集合中的所有成员元素,不存在的集合视为空集合
  • sismember:查看某一个元素是否存在集合中 存在返回1;否则返回0
127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
(integer) 1
127.0.0.1:6379> sismember mySet value5 # 检查某个元素是否存在set 中,只能接收单个元素
(integer) 0

语法:sismember key member
作用:判断 member 元素是否是集合 key 的成员
返回值:member 是集合成员返回 1,其他返回 0
  • scard:获取集合中元素的个数(集合的长度)
127.0.0.1:6379> scard myset
(integer) 2

语法:scard key
作用:获取集合里面的元素个数
返回值:数字,集合中的元素个数。其他情况返回 0 。
  • srem:删除集合中的一个或者多个元素
127.0.0.1:6379> srem myset value1 value3
(integer) 2
127.0.0.1:6379> smembers myset
1) "value2"

语法:srem key member1 member2...
作用:删除集合中的一个或多个 member 元素,不存在的元素被忽略。
返回值:数字,成功删除的元素个数,不包括被忽略的元素。
  • 2.常用命令
  • srandmember:随机返回一个或者多个元素
127.0.0.1:6379> sadd sql select update insert delete # 添加元素进去
(integer) 4
127.0.0.1:6379> srandmember sql  # 没有加count 随机返回一个元素
"update"
127.0.0.1:6379> srandmember sql
"select"
127.0.0.1:6379> srandmember sql
"select"
127.0.0.1:6379> srandmember sql 2
 #count>0, 返回 count 个数元素的集合,元素各不相同
1) "select"
2) "insert"
127.0.0.1:6379> srandmember sql -3
# count<0, 返回 |count |个数元素的集合(元素可能重复多次)
1) "update"
2) "update"
3) "update"

语法:srandmember key [count]
作用:只提供 key,随机返回集合中一个元素,元素不删除,依然在集合中;
提供了count:
count >0, 返回包含 count 个数元素的集合,集合元素各不相同。
count <0,返回一个 count 绝对值的长度的集合,集合中元素可能会重复多次。
返回值:一个元素;多个元素的集合
  • spop:随机删除(取出)一个或多个元素
127.0.0.1:6379> spop sql
1)"select"
127.0.0.1:6379> spop sql 2
1) "insert"
2) "delete"
127.0.0.1:6379> spop sql 2 #此时集合sql里面只剩元素:update
1) "update"
127.0.0.1:6379> spop sql 2
(empty list or set)

语法:spop key [count]
作用:随机从集合中删除一个元素, count 是删除的元素个数。
返回值:被删除的元素,key 不存在或空集合返回 nil
  • sinterstore :
127.0.0.1:6379> sadd mySet1 value1 value2
(integer) 2
127.0.0.1:6379> sadd mySet2 value2 value3
(integer) 2
127.0.0.1:6379> sinterstore mySet3 mySet mySet2 
# 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
(integer) 1
127.0.0.1:6379> smembers mySet3
1) "value2"

4.5 zset 有序集合

  • redis 有序集合zset是String元素的可排序集合,集合元素不可重复。相当于sortedset
  • 和 set 相比,zset 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。

应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。
常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem 等。

  • 1.基本命令
  • zadd:添加;添加进zset集合的元素按照权重从小到大排序
# 添加元素到 zhangsan lisi wangwu 到 set 中;权重分别为:85 90 80
127.0.0.1:6379> zadd student 85 zhangsan 90 lisi 80 wangwu
(integer) 3

语法:zadd key score member [score member…]
作用:将一个或多个 member 元素及其 score 值加入到有序集合 key 中;
如果 member 存在集合中,则更新值;score 可以是整数或浮点数
返回值:数字,新添加的元素个数

redis的decr为什么不能防止超卖呢_spring cloud_08

  • zrange:升序查找zset
127.0.0.1:6379> zrange student 0 2
1) "wangwu"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrange student 0 5
1) "wangwu"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrange student 0 -1
1) "wangwu"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrange student 1 2
1) "zhangsan"
2) "lisi"
127.0.0.1:6379> zrange student -2 -1
1) "zhangsan"
2) "lisi"

127.0.0.1:6379> zrange student -2 -1 withscores
1) "zhangsan"
2) "85"
3) "lisi"
4) "90"

语法:zrange key start stop [WITHSCORES]
作用:查询有序集合,指定区间的内的元素。
集合成员按 score 值从小到大来排序。
start,stop 都是从 0 开始。0 是第一个元素,1 是第二个元素,依次类推。
以 -1 表示最后一个成员,-2 表示倒数第二个成员。
WITHSCORES 选项让 score 和 value 一同返回。
返回值:自定区间的成员集合
  • zrevrange:降序查找zset
127.0.0.1:6379> zrevrange student 0 -1 withscores
1) "lisi"
2) "90"
3) "zhangsan"
4) "85"
5) "wangwu"
6) "80"

语法:zrevrange key start stop [WITHSCORES]
作用:返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递减(从大到小)来排列。其它同 zrange 命令。
返回值:自定区间的成员集合
  • zscore:查看某个元素的权重
127.0.0.1:6379> zadd myZset 3.0 value1 2.0 value2 1.0 value3 # 一次添加多个元素
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3.0"
  • zrem:删除元素
127.0.0.1:6379> zrem student zhangsan lisi
(integer) 2

语法:zrem key member [member…]
作用:删除有序集合 key 中的一个或多个成员,不存在的成员被忽略
返回值:被成功删除的成员数量,不包括被忽略的成员。
  • zcard:查看集合元素的数量
127.0.0.1:6379> zcard student # 查看 sorted set 中的元素数量
(integer) 1

语法:zcard key
作用:获取有序集 key 的元素成员的个数
返回值:k返回集合元素的个数, 集合不存在,返回 0
  • 2.常用命令
  • zrangebyscore
127.0.0.1:6379> zrange student 0 -1 withscores
1) "zhaoliu"
2) "70"
3) "lisi"
4) "85"
5) "zhangsan"
6) "90"
7) "wangwu"
8) "95"
//withscores会显示score
127.0.0.1:6379> zrangebyscore student 80 90 withscores
1) "lisi"
2) "85"
3) "zhangsan"
4) "90"
// (表示不包含,默认是闭区间
127.0.0.1:6379> zrangebyscore student 80 (90 withscores
1) "lisi"
2) "85"
127.0.0.1:6379> zrangebyscore student 80 +inf withscores
1) "lisi"
2) "85"
3) "zhangsan"
4) "90"
5) "wangwu"
6) "95"
// -inf:负无穷 +inf:正无穷
127.0.0.1:6379> zrangebyscore student -inf +inf withscores
1) "zhaoliu"
2) "70"
3) "lisi"
4) "85"
5) "zhangsan"
6) "90"
7) "wangwu"
8) "95"
//limit offset count表示从下标offset开始后的count个元素
127.0.0.1:6379> zrangebyscore student -inf +inf withscores limit 0 1
1) "zhaoliu"
2) "70"
127.0.0.1:6379> zrangebyscore student -inf +inf withscores limit 2 3
1) "zhangsan"
2) "90"
3) "wangwu"
4) "95"

语法:zrangebyscore key min max [WITHSCORES ] [LIMIT offset count]
作用:获取有序集 key 中,所有 score 值介于 min 和 max 之间(包括 min 和 max)的成员,有序成员是按递增(从小到大)排序。
min ,max 是包括在内,使用符号( 表示不包括。 min, max 可以使用 -inf ,+inf 表示最小和最大limit 用来限制返回结果的数量和区间
withscores 显示 score 和 value
返回值:指定区间的集合数据
  • zrevrangebyscore:与zrangebyscore相同,只不过是降序
  • zcount
127.0.0.1:6379> zcount student -inf +inf
(integer) 4
127.0.0.1:6379> zcount student 80 100
(integer) 3

语法:zcount key min max
作用:返回有序集 key 中,score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量

5. redis事务

5.1 what’s 事务

  • 事务就是一系列操作,这些操作要么同时成功、要么同时失败。
  • 对于mysql来说,执行多条DML语句就要使用事务,mysql的事务具有ACID、隔离级别。
  • 对于redis来说,执行多条命令,redis事务保证这些命令被执行时中间不会被任何其他操作打断。

总结:我们可以将Redis中的事务就理解为 :Redis事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。

5.2 redis事务操作命令

Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH

  • multi:标志着事务的开始,multi后的redis命令放到等待队列中暂存不执行。

语法: multi
作用:标记一个事务的开始。事务内的多条命令会按照先后顺序被放进一个队列当中。
即: 使用 MULTI命令后可以输入多个命令。但Redis不会立即执行这些命令,而是将它们放到队列,当调用了EXEC命令将执行所有命令。
返回值:总是返回 ok

  • exec:提交事务。执行所有事务块中的命令

语法:exec
作用:执行所有事务块内的命令
返回值:事务内的所有执行语句内容,事务被打断,返回 nil

  • discard:取消事务

语法:discard
作用:取消事务,放弃执行事务块内的所有命令
返回值:总是返回 ok

  • watch:监控数据

语法:watch key [key …]
作用:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
返回值:总是返回 ok

  • unwatch:取消监视

语法:unwatch
作用:取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后, EXEC 命令 或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
返回值:总是返回 ok

5.3 redis事务的特性

Redis 的事务和我们平时理解的关系型数据库的事务不同。
我们知道事务具有四大特性: 1. 原子性 2. 隔离性 3. 持久性 4. 一致性

原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
一致性(Consistency): 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;

但是Redis 是不支持 roll back (回滚) 的,也就是说: 事务中如果有一条命令执行失败, 他后面的命令仍然会被执行, 没有回滚; 因而不满足原子性

总结:redis不支持回滚, 不保证原子性

  • Redis事务是一个单独的隔离操作:事务中的所有命令都会被序列化,按顺序的执行,事务在执行过程中不会被其他客户端发来的命令所打断。
  • Redis事务没有隔离级别的概念:队列中没有 提交之前都不会被执行。
  • 不保证原子性:事务中如果有一条命令执行失败,其他的命令仍然执行,不保证回滚

5.4 redis事务的实现

  • 事务开启后,若redis命令有语法错误,redis会立刻检测出来,然后对事务撤销,即事务中的正确命令也不会执行。
127.0.0.1:6379> get k1
"10"
127.0.0.1:6379> multi		#开启事务
OK
127.0.0.1:6379> set k1 hello	#修改k1值
QUEUED
127.0.0.1:6379> ladd k1		#错误命令,语法错误
(error) ERR unknown command `ladd`, with args beginning with: `k1`.
//redis立即检测到错误命令
127.0.0.1:6379> exec	#提交事务
(error) EXECABORT Transaction discarded because of previous errors.		
//取消事务由于存在错误
127.0.0.1:6379> get k1	#获取k1值,发现5行的修改命令没有起作用
"10"
  • 事务开启后,若redis命令语法没错误,但是执行后有错误,redis会提交事务。原因是redis的事务没有回滚,这也是redis性能好的原因之一
127.0.0.1:6379> get k1
"10"
127.0.0.1:6379> multi	#开启事务
OK
127.0.0.1:6379> set k1 hello	#修改k1
QUEUED
127.0.0.1:6379> lpush k1 100	#语法正确
QUEUED
127.0.0.1:6379> exec	#提交事务
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
//报错,因为redis是大map,k1是String类型
127.0.0.1:6379> get k1	#k1值修改成功
"hello"
  • 放弃事务:discard表示取消事务,在multi开启事务后,提交命令,即使命令语法正确也没有错,但是最后若执行了discard,表示之前所有命令取消。
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 10
QUEUED
127.0.0.1:6379> set k1 20
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> get k1
"hello"
  • redis的watch机制
  • redis没有锁的概念,多个客户端操作同一个数据时,为了保证数据的准确性、一致性,是通过watch机制来实现的,watch可以监控某个数据是否被修改。
  • redis使用watch机制的好处是不用对数据上锁,读取数据更快,性能更好。没有竞争,一秒可以进行近10万次的读和写,两个事务操作同一个数据的碰撞几率小,redis的watch机制类似与mysql的乐观锁
  • 实例:如有一个数据K1,它的值为V1,A、B两个客户端操作K1,首先A进行watch K1,发现值是V1,之后A开启事务,若在提交事务时发现K1的值还是V1,那么提交事务;否则表示K1的值被B客户端修改了,此时A会放弃事务。相反,B同理。watch机制的原理是若数据发生变化,则放弃事务,保证了该数据任意时刻只有一个事务去修改数据。
//事务A
127.0.0.1:6379> set k1 v1	#事务A设置k1值为v1
OK
127.0.0.1:6379> watch k1	#事务A监控k1
OK
127.0.0.1:6379> multi		#开启事务
OK
127.0.0.1:6379> set k2 v2	#设置k2值为v2
QUEUED

//事务B
127.0.0.1:6379> get k1	
"v1"
127.0.0.1:6379> set k1 v111	#事务B此时修改k1值
OK

//事务A
127.0.0.1:6379> exec	
#提交事务,此时事务A监控到了k1变化,因此事务A放弃事务,k2取消
(nil)
127.0.0.1:6379> get k2	#获取k2,发现k2不存在
(nil)

6. redis的缓存雪崩、缓存穿透、缓存击穿

雪崩:高并发下,大量的缓存key,在同一时间同时失效
击穿:某一个热点key缓存突然失效,大量的请求直接打到数据库上
穿透:(恶意数据攻击数据库)redis没有这个数据,直接穿过redis打到数据库上

首先我么来看正常的一个缓存流程

redis的decr为什么不能防止超卖呢_缓存_09

6.1 缓存雪崩

6.1.1 概念

缓存雪崩缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。

这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。
举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。
关键词: 热点缓存+同一时刻

6.1.2 处理流程

如下图所示

redis的decr为什么不能防止超卖呢_spring cloud_10

6.1.3 解决办法

  • 1.针对 Redis 服务不可用的情况:
  • 采用 Redis 集群部署,避免单机redis出现问题,使整个缓存服务都没办法使用。

把热点的key放到不同的节点上去(让这些热点的缓存平均分布在不同的redis节点上)

  • 限流,避免同时处理大量的请求。
  • 2.针对热点缓存失效的情况:
  • 设置不同的缓存失效时间,避免大量key集体失效。比如随机设置缓存的失效时间,让这些缓存不要在同一时间失效。
setRedis(key,value,time+Math.random()*10000);
  • 设置缓存永不失效。(比较暴力)
  • 跑定时任务,定时的刷新缓存。比如我设置这个缓存三小时失效,那我们就在失效之前,重新把缓存同步到redis中去,再设置三小时失效。

6.2 缓存穿透

6.2.1 概念

缓存穿透: 说简单点就是大量请求的 key 根本不存在于缓存redis中,导致请求直接到了数据库上(且数据库中也没有这样的key),根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。

例如: 大量的制造id为负数的数据,这种数据不存在redis中,直接穿透缓存,打到数据库上,导致数据库负载过大挂掉宕机

6.2.2 处理流程

如下图所示

redis的decr为什么不能防止超卖呢_缓存_11

6.2.3 解决办法

  1. 对参数进行合法性校验;判断这个参数不合法时候直接抛异常到客户端

最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

  1. 拉黑恶意用户的ip;
  2. 缓存无效key

当恶意数据穿透缓存打到数据库上时候,无论数据库查询出什么样的结果,都再缓存到redis中去;这样下次再用同一个参数请求的时候,就不会穿透redis了;并设置一个短期过期时间,避免缓存时间过长影响用户使用
具体命令如下: SET key value EX 10086 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 60秒。
一般情况下我们是这样设计 key 的: 表名:列名:主键名:主键值

  1. 代码展示如下:
public Object getObjectInclNullById(Integer id) {
    // 从缓存中获取数据
    Object cacheValue = cache.get(id);
    // 缓存为空
    if (cacheValue == null) {
        // 从数据库中获取
        Object storageValue = storage.get(key);
        // 缓存空对象
        cache.set(key, storageValue);
        // 如果存储数据为空,需要设置一个过期时间(600秒)
        if (storageValue == null) {
            // 必须设置过期时间,否则有被攻击的风险
            cache.expire(key, 60 * 10);
        }
        return storageValue;
    }
    return cacheValue;
}
  1. 使用布隆过滤器:将所有可能存在的的数据hash到一个足够大的位图中,当请求过来的数据不存在这个位图中时直接被拦截掉。
6.2.3-1 关于布隆过滤器
  • 用法: 把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
  • 缓存流程:

注: 虽然布隆过滤器可能会存在误判的情况,但是布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。

redis的decr为什么不能防止超卖呢_spring boot_12

6.2.3-1 布隆过滤器元素存入原理

上面提到布隆过滤器可能会误判, 因此我们来简单看下元素存入过滤器的原理

  • 当一个元素加入布隆过滤器中的时候,如何进行操作的:
  1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
  2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
  • 当我们需要判断一个元素是否存在于布隆过滤器的时,如何进行操作:
  1. 对给定元素再次进行相同的哈希计算;
  2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

总结: 所以可能会出现这样一种情况:不同的字符串可能哈希出来的位置相同。(hsah冲突) (可以适当增加位数组大小或者调整我们的哈希函数来降低概率)

6.3 缓存击穿

6.3.1 概念

缓存击穿: 某个热点key缓存到redis中,当到了设置的缓存失效时间以后,依旧还有大量的请求,此时redis里面已经没有数据,就会直接打到数据库上。这就是缓存击穿。
关键词: 某个key

6.3.2 处理流程

如下图所示

redis的decr为什么不能防止超卖呢_spring cloud_13

6.3.3 解决办法

  1. 设置缓存永久有效(此方法不太好)
  2. 使用分布式锁(若是单体系统,就使用互斥锁)此方法最好

(分布式)锁:当redis中无数据被击穿的时候,我们在数据库之前加上一把锁,这个时候大量的线程过来,只有一个线程可以抢到这个锁获得资源(此时数据库的压力就很小),当查询到数据之后,重新把结果缓存到redis中去;而其他没有抢到锁的线程先sleep一段时间(几毫秒);当其他线程再次访问时候,redis里面已经有这个数据,就不用再去数据库中查询数据,也不用再去竞争这个分布式锁,是直接在redis那一步就返回了。

7. Redis面试20问

7.1 单线程的 Redis为什么这么快?

Redis有多快?官方给出的答案是读写速度10万/秒,如果说这是在单线程情况下跑出来的成绩,你会不会惊讶?为什么单线程的Redis速度这么快?

原因有以下几点:

纯内存操作:

  • Redis是完全基于内存的,所以读写效率非常的高,当然Redis存在持久化操作,在持久化操作是都是fork子进程和利用Linux系统的页缓存技术来完成,并不会影响Redis的性能;
  • 单线程操作:单线程并不是坏事,单线程可以避免了频繁的上下文切换,频繁的上下文切换也会影响性能的;
  • 合理高效的数据结构;
  • 采用了非阻塞IO多路复用机制:多路I/O复用模型是利用select、poll、epoll 可以同时监祭多个流的I/O事件能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有IO事件时,就从阻塞中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

7.2 Redis 和 Memcached 的区别和共同点

共同点 :
  1. 都是基于内存的数据库,一般都用来当做缓存使用。
  2. 都有过期策略。
  3. 两者的性能都非常高。
区别 :
  • 存储方式不同:
  • memcache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小;
  • Redis有部份存在硬盘上,这样能保证数据的持久性。

Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。

也就是redis 的灾难恢复机制, 即:可以把缓存中的数据持久化到磁盘上

  • 数据支持类型不同:memcache对数据类型支持相对简单;Redis有复杂的数据类型。

Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型

  • 使用底层模型不同:它们之间底层实现方式,以及与客户端之间通信的应用协议不一样,Redis自己构建了vm机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
  • value值大小不同:Redis最大可以达到1GB; memcache只有1MB。
  • 过期策略使用不同:Memcached过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。

7.3 redis 缓存设置过期相关

7.3.1为什么Redis 要给缓存数据设置过期时间

一般情况下,我们保存缓存数据的时候都会设置一个过期时间。因为内存是有限的,如果缓存中的所有数据都是一直保存的话,内存就会分分钟直接Out of memory。

Redis 自带了给缓存数据设置过期时间的功能,比如:

127.0.0.1:6379> exp key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56

注意:Redis中除了字符串类型有自己独有设置过期时间的命令 setex 外,其他方法都需要依靠 expire 命令来设置过期时间 ; 另外, persist 命令可以移除一个键的过期时间

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

EXPIRE和PERSIST命令

7.3.3 过期时间除了有助于缓解内存的消耗,还有什么其他作用?

在我们日常工作的大部分中,我们的业务场景就是需要某个数据只在某一时间段内存在;

比如我们的短信验证码可能只在1分钟内有效,用户登录的 token 可能只在 1 天内有效

如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多

7.4 说一说Redis的数据过期淘汰策略

相关系列问题:

  1. 我们知道通过expire来设置key的过期时间,那么对过期的数据怎么处理呢?
  2. 如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
    答:引出过期淘汰策略?

Redis 中数据过期策略采用:定期删除 + 惰性删除策略 结合的方式来处理过期数据。

7.4.1 定期删除、惰性删除策略是什么?

  • 定期删除策略:定时清理过期的内存

Redis 启用一个定时器定时监视所有的 key,每隔一段时间抽取一批 key 执行删除过期key操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响;
优点:对内存更加友好
缺点:每次都遍历内存中所有的数据,非常消耗CPU资源,并且当key已过期,但是定时器还处于未唤起状态,这段时间内key仍然可以用。

  • 惰性删除策略:只有在访问一个key时,才会判断该key是否已过期,过期则清除

只会在取出key的时候才对数据进行过期检查。当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存
优点:对CPU最友好
缺点:1.如果这个key一直未被使用,那么它一直在内存中,其实它已经过期了,会浪费大量的空间;2.每次请求都要判断缓存是否失效,逻辑比较复杂

7.4.2 两种策略结合起来以后的工作原理?

这两种策略互补,结合起来之后,

定时删除策略就发生了一些改变:不在是每次扫描全部的key了,而是随机抽取一部分key进行检查,这样就降低了对CPU资源的损耗;
惰性删除策略互补了没有检查到的key,基本上满足了所有要求。

7.5 说一说Redis的内存淘汰策略 / 机制----内存相关

相关系列问题:

1. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
2. 仅通过给 key 设置过期时间会存在这样的问题:可能存在定期删除和惰性删除漏掉了很多过期 key 的情况,因此就导致大量过期 key 堆积在内存里,就容易Out of memory
3. redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

答:
没关系,还有内存淘汰机制,当内存不够用时,内存淘汰机制就会上场;(引出内存淘汰机制)

Redis内存淘汰机制有以下6种策略:

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

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;
  • volatile-ttl:从已设置过期时间的数据集((server.db[i].expires)中挑选将要过期的数据淘汰;
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;

全局的键空间选择性移除

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key (这个是最常用的);
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;
  • no-eviction:禁止驱逐数据,永不过期,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。(默认值)

4.0 版本后增加以下两种:

  • volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  • allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
总结:
  1. Redis的内存淘汰策略的选取并不会影响过期的key的处理。
  2. 内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。