redis
redis
安装教程
Centos7安装Redis - heqiuyong - 博客园 (cnblogs.com)
基本操作
通用指令是部分数据类型的,都可以使用的指令,常见的有如下表格所示
指令 | 描述 |
---|---|
KEYS | 查看符合模板的所有key,不建议在生产环境设备上使用 |
DEL | 删除一个指定的key |
EXISTS | 判断key是否存在 |
EXPIRE | 给一个key设置有效期,有效期到期时该key会被自动删除 |
TTL | 查看一个KEY的剩余有效期 |
redis:0>select 1 #切换到1这个数据库
"OK"
redis:1>keys * # 查看当前数据所有键
redis:1>select 0 # 切换0 数据库
"OK"
redis:0>keys *
1) "name"
String 类型
String类型,也就是字符串类型,是Redis中最简单的存储类型。
其value是字符串,不过根据字符串的格式不同,又可以分为3类:
string
:普通字符串int
:整数类型,可以做自增、自减操作float
:浮点类型,可以做自增、自减操作
String的常见命令有如下表格所示
序号 | 命令及描述 |
---|---|
1 | SET key value 设置指定 key 的值 |
2 | GET key 获取指定 key 的值。 |
3 | GETRANGE key start end 返回 key 中字符串值的子字符 |
4 | GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
5 | GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
6 | MGET key1 [key2..] 获取所有(一个或多个)给定 key 的值。 |
7 | SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
8 | SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
9 | SETNX key value 只有在 key 不存在时设置 key 的值。 |
10 | SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
11 | STRLEN key 返回 key 所储存的字符串值的长度。 |
12 | MSET key value [key value ...] 同时设置一个或多个 key-value 对。 |
13 | MSETNX key value [key value ...] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
14 | PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
15 | INCR key 将 key 中储存的数字值增一。 |
16 | INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。 |
17 | INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
18 | DECR key 将 key 中储存的数字值减一。 |
19 | DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。 |
20 | APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 |
redis:0>set name "wang" # set 键 值
"OK"
redis:0>get name # get 键 查看值
"wang"
redis:0>set age 10
"OK"
redis:0>set school 希望小学
"OK"
redis:0>keys * # 查看所有的键
1) "age"
2) "school"
3) "name"
# exists 键 :判断是否存在 不存在返回0 存在返回1
redis:0>exists schol
"0"
redis:0>exists school
"1"
# expire 键 过期时间: redis数据库中保留时间
redis:0>Expire name 10
"1"
redis:0>ttl name
"6"
redis:0>ttl name
"5"
redis:0>ttl name
"4"
redis:0>ttl name
"3"
redis:0>ttl name
"2"
redis:0>ttl name
"1"
redis:0>ttl name
"0"
redis:0>get name
null
# append 键 值 :在原有的值后面添加值
redis:0>append name anb
"7"# 表示当前值所对应的字节的长度
redis:0>get name # 获取键所对应的值
"wanganb"
redis:0>strlen name # 计算当前键的值所对应的字节长度
"7"
# 自增 incr 键 :自增键所对应的值
redis:0>set views 0
"OK"# 添加成功
redis:0>get views
"0"
redis:0>incr views
"1"
redis:0>incr views
"2"
redis:0>incr views
"3"
redis:0>incr views
"4"
redis:0>get views
"4"
# incrby 键 数a :表示原有的值增加数a 自增一个范围
redis:0>incrby views 10
"14"
# decrby 键 数a: 表示原有的值减a 自减一个范围
redis:0>decrby views 10
"4"
# decr 键: 自减键对应的值
redis:0>decr views
"3"
redis:0>get views
"3"
# stlen 键: 计算值所对应字节的长度
redis:0>get school
"希望小学中国"
redis:0>strlen school # 一个中文汉字默认是3个字节
"18"
#getrange 键 索引起始 索引结束: 获取索引起始:结束 范围所对应的值
redis:0>getrange school 0 4 # 获取索引[0;4] 字节中的值:总共5个字节
"希"
redis:0>getrange school 0 5
"希望"
redis:0>get name
"wanganb"
redis:0>strlen name
"7"
redis:0>getrange name 0 4 # 获取0:4范围字符串的值
"wanga"
redis:0>getrange name 0 6
"wanganb"
# setrange 键名 索引起始位置 替换字符
redis:0>get name
"wanganb"
redis:0>setrange name 4 text
"8"
redis:0>get name
"wangtext"
#setex(set with expire): 设置过期日期
#setex 键 过期时间 值
redis:0>setex testsetex 30 测试
"OK"
redis:0>get testsetex
"测试"
redis:0>ttl testsetex
"16"
redis:0>ttl testsetex
"-2"
redis:0>get testsetex
null
# setnx(set if not exist) #:不存在再设置(在分布式锁中会常常使用)
# 存在赋值失败
redis:0>setnx mykey "redis" # 不存在设置成功
"1"
redis:0>get mykey
"redis"
redis:0>setnx mykey "testredis" # 存在设置失败
"0" # 0表示设置失败
redis:0>get mykey
"redis"
# mset 键 值 键 值 键 值。。。。。 :批量设置值
redis:0>flushdb # 清空数据库
"OK"
redis:0>mset k1 v1 k2 v2 k3 v3
"OK"
redis:0>keys *
1) "k2"
2) "k3"
3) "k1"
# msetnx 键 值 键 值。。。。:原子性操作:要么一起成功 要么一起失败
redis:0>msetnx k1 v1 k4 v4
"0"
redis:0>keys *
1) "k2"
2) "k3"
3) "k1"
# 设置对象: 设置一个user:1 对象 值为json字符来保存一个对象
redis:0>flushdb
"OK"
redis:0>mset user:1:name 李四 user:1:age 2
"OK"
redis:0>keys *
1) "user:1:age"
2) "user:1:name"
redis:0>mget user:1:name user:1:age
1) "李四"
2) "2"
string 类型的应用场景
1.可以当自增主键,具有原子性
- string在redis内部存储默认就是一个字符串,当遇到增减类操作incr, decr时会转成数值型进行计算。
- redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。
- 注意: 按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错。9223372036854775807( java中long型数据最大值, Long.MAX_VALUE)
适用:
- redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
- 此方案适用于所有数据库,且支持数据库集群
参考博客:Redis的String类型 - 天宇轩-王 - 博客园 (cnblogs.com)
2.时效性
“最强女生”启动海选投票,只能通过微信投票,每个微信号每 4 小时只能投1票。
电商商家开启热门商品推荐,热门商品不能一直处于热门期,每种商品热门期维持3天, 3天后自动取消热门。
用法:
SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
#setex(set with expire): 设置过期日期
#setex 键 过期时间 值
redis:0>setex testsetex 30 测试
"OK"
redis:0>get testsetex
"测试"
redis:0>ttl testsetex
"16"
redis:0>ttl testsetex
"-2"
redis:0>get testsetex
null
3.存储信息
redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可
eg: user:id:3506728370:fans → 12210947
eg: user:id:3506728370:blogs → 6164
eg: user:id:3506728370:focuss → 83
在redis中以json格式存储大V用户信息,定时刷新(也可以使用hash类型)
eg: user🆔3506728370 →
{"id":3506728370,"name":"春晚","fans":12210862,"blogs":6164, "focus":83}
上面2种各有优缺点,第一种改的方便,第二个查的方便
4.注意事项
- string 类型数据操作的注意事项
① 表示运行结果是否成功
(integer) 0 → false 失败
(integer) 1 → true 成功
② 表示运行结果值
(integer) 3 → 3 3个**
(integer) 1 → 1 1个**
- 数据未获取到
( nil)等同于null
- 数据最大存储量
512MB
- 数值计算最大范围( java中的long的最大值)
9223372036854775807
List 类型
- List常用命令
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征也与LinkedList
类似:
- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等.
序号 | 命令及描述 |
---|---|
1 | BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
2 | BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
3 | BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
4 | LINDEX key index 通过索引获取列表中的元素 |
5 | LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素 |
6 | LLEN key 获取列表长度 |
7 | LPOP key 移出并获取列表的第一个元素 |
8 | LPUSH key value1 [value2] 将一个或多个值插入到列表头部 |
9 | LPUSHX key value 将一个值插入到已存在的列表头部 |
10 | LRANGE key start stop 获取列表指定范围内的元素 |
11 | LREM key count value 移除列表元素 |
12 | LSET key index value 通过索引设置列表元素的值 |
13 | LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
14 | RPOP key 移除列表的最后一个元素,返回值为移除的元素。 |
15 | RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
16 | RPUSH key value1 [value2] 在列表中添加一个或多个值 |
17 | RPUSHX key value 为已存在的列表添加值 |
##########################################################
# 添加元素
# lpush将一个值或者多个值插入列表头部
redis:0>lpush list one
"1"
redis:0>lpush list two
"2"
redis:0>lpush list three
"3"
# 通过lrange 获取列表中区间的值
redis:0>lrange list 0 -1
1) "three"
2) "two"
3) "one"
# rpush 将一个值或者多个值插入道列表的尾部
redis:0>rpush list rone
"4"
redis:0>lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "rone"
##########################################################
# 删除元素
redis:0>lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "rone"
# 移除列表最左端的元素
redis:0>lpop list
"three"
redis:0>lrange list 0 -1
1) "two"
2) "one"
3) "rone"
# 移除列表最右侧的元素
redis:0>rpop list
"rone"
redis:0>lrange list 0 -1
1) "two"
2) "one"
##########################################################
# 索引 lindex
redis:0>lrange list 0 -1
1) "two"
2) "one"
# 获取列表第一个元素
redis:0>lindex list 0
"two"
# 获取列表第二个元素
redis:0>lindex list 1
"one"
redis:0>
##########################################################
# 长度 len
redis:0>lrange list 0 -1
1) "two"
2) "one"
#获取长度
redis:0>llen list
"2"
##########################################################
# 移除 特定的值 lrem 键值 移除数量 value值
redis:0>lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
# 移除 list 中1 个 one值
redis:0>lrem list 1 one
"1"
redis:0>lrange list 0 -1
1) "three"
2) "three"
3) "two"
# 移除 list 中2 个 three值
redis:0>lrem list 2 three
"2"
redis:0>lrange list 0 -1
1) "two"
##########################################################
# trim 截断操作
redis:0>lrange list 0 -1
1) "h0"
2) "h1"
3) "h2"
4) "h3"
5) "h4"
# 截断 list 从下标1 开始 指定长度。list 已经改变
redis:0>ltrim list 1 2
"OK"
redis:0>lrange list 0 -1
1) "h1"
2) "h2"
redis:0>
##########################################################
# 移除列表最后一个元素,并将添加到另一个list
redis:0>lrange list 0 -1
1) "h1"
2) "h2"
# 移除列表list 中最右侧的元素加入到 list2 中最左侧
redis:0>rpoplpush list list2
"h2"
redis:0>lrange list 0 -1
1) "h1"
redis:0>lrange list2 0 -1
1) "h2"
##########################################################
# 判断列表中是否存在一个值
redis:0>exists list # 判断列表存不存在
"0"
redis:0>lset list 0 item # 不存在列表就报错
"ERR no such key"
redis:0>
##########################################################
# 列表 插入 insert
redis:0>lrange list 0 -1
1) "h0"
2) "h2"
# 在h2之前插入值
redis:0>linsert list before "h2" "h1"
"3"
redis:0>lrange list 0 -1
1) "h0"
2) "h1"
3) "h2"
# 在h2之后插入值
redis:0>linsert list after "h2" "h3"
"4"
redis:0>lrange list 0 -1
1) "h0"
2) "h1"
3) "h2"
4) "h3"
list实际上是一个链表,before ,after, left ,right 都可以插入值。 如果key不存在,创建新的链表。 如果key存在,新增内容,如果移除所有值,空链表,也代表不存在。 在链表插入如或者改动效率最
redis 中List可以实现栈,队列,阻塞队列 lpush 添加值
业务场景
list 类型数据扩展操作
微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息
如果取消点赞,移除对应好友信息
#LREM key count value 移除列表元素
#LRANGE key start stop 获取列表指定范围内的元素
阿里云远程服务器:0>rpush key a b c d e e f
"7"
阿里云远程服务器:0>lrange key 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "e"
7) "f"
阿里云远程服务器:0>lrem key 1 d
"1"
阿里云远程服务器:0>lrange key 0 -1
1) "a"
2) "b"
3) "c"
4) "e"
5) "e"
6) "f"
# 取消点赞
阿里云远程服务器:0>lrem key 2 e
"2"
阿里云远程服务器:0>lrange key 0 -1
1) "a"
2) "b"
3) "c"
4) "f"
twitter、新浪微博、腾讯微博中个人用户的关注列表需要按照用户的关注顺序进行展示
,粉丝列表需要将最近关注的粉丝列在前面
阿里云远程服务器:0>rpush key a b c d e e f
"7"
阿里云远程服务器:0>lrange key 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "e"
7) "f"
# 新粉丝
阿里云远程服务器:0>lpush key o
"8"
阿里云远程服务器:0>lrange key 0 -1
1) "o"
2) "a"
3) "b"
4) "c"
5) "d"
6) "e"
7) "e"
8) "f"
list 类型应用场景
解决方案
- 依赖list的数据具有顺序的特征对信息进行管理
- 使用队列模型解决多路信息汇总合并的问题
- 使用栈模型解决最新消息的问题
list 类型数据操作注意事项
- list中保存的数据都是string类型的,数据总容量是有限的,最多2 32 - 1 个元素 (4294967295)。
- list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
- 获取全部数据操作结束索引设置为-1
- list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载
参考博客:Redis的List类型 - 天宇轩-王 - 博客园 (cnblogs.com)
set 类型
集合命令
序号 | 命令及描述 |
---|---|
1 | SADD key member1 [member2] 向集合添加一个或多个成员 |
2 | SCARD key 获取集合的成员数 |
3 | SDIFF key1 [key2] 返回给定所有集合的差集 |
4 | SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中 |
5 | SINTER key1 [key2] 返回给定所有集合的交集 |
6 | SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中 |
7 | SISMEMBER key member 判断 member 元素是否是集合 key 的成员 |
8 | SMEMBERS key 返回集合中的所有成员 |
9 | SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合 |
10 | SPOP key 移除并返回集合中的一个随机元素 |
11 | SRANDMEMBER key [count] 返回集合中一个或多个随机数 |
12 | SREM key member1 [member2] 移除集合中一个或多个成员 |
13 | SUNION key1 [key2] 返回所有给定集合的并集 |
14 | SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中 |
15 | SSCAN key cursor [MATCH pattern] [COUNT count] 迭代集合中的元素 |
##########################################################
# SADD key member1 [member2] 向集合添加一个或多个成员
阿里云远程服务器:0>sadd key v1 v2
"2"
##########################################################
# SMEMBERS key 返回集合中的所有成员
阿里云远程服务器:0>smembers key
1) "v2"
2) "v1"
##########################################################
# SCARD key 获取集合的成员数
阿里云远程服务器:0>scard key
"2"
##########################################################
# SPOP key 移除并返回集合中的一个随机元素
阿里云远程服务器:0>smembers key1
1) "v4"
2) "v3"
3) "v1"
阿里云远程服务器:0>spop key1# 移除随机返回一个元素
"v1"
##########################################################
# SRANDMEMBER key [count] 返回集合中一个或多个随机数
阿里云远程服务器:0>srandmember key1 1
1) "v4"
阿里云远程服务器:0>srandmember key1 1
1) "v3"
##########################################################
# SREM key member1 [member2] 移除集合中一个或多个成员
阿里云远程服务器:0>smembers key1 # 集合
1) "v2"
2) "v1"
3) "v4"
4) "v3"
5) "v5"
阿里云远程服务器:0>srem key1 v1 v2 # 删除元素
"2"
阿里云远程服务器:0>smembers key1
1) "v3"
2) "v4"
3) "v5"
##########################################################
# SISMEMBER key member 判断 member 元素是否是集合 key 的成员
阿里云远程服务器:0>smembers key
1) "v2"
2) "v1"
阿里云远程服务器:0>sismember key v3 # 失败返回0
"0"
阿里云远程服务器:0>sismember key v2# 成功返回1
"1"
##########################################################
# 交集 SINTER key1 [key2] 返回给定所有集合的交集
阿里云远程服务器:0>smembers key1 # 集合1
1) "v4"
2) "v3"
3) "v1"
阿里云远程服务器:0>smembers key# 集合2
1) "v2"
2) "v1"
阿里云远程服务器:0>sinter key key1 #交集
1) "v1"
# 交集 SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中
阿里云远程服务器:0>sinterstore storekey key key1 # 并集存储到另一个临时
"1"
阿里云远程服务器:0>smembers storekey
1) "v1"
##########################################################
# 并集 SUNION key1 [key2] 返回所有给定集合的并集
阿里云远程服务器:0>smembers key1 # 集合1
1) "v4"
2) "v3"
3) "v1"
阿里云远程服务器:0>smembers key# 集合2
1) "v2"
2) "v1"
阿里云远程服务器:0>sunion key key1# 集合1 和集合2 并集
1) "v2"
2) "v3"
3) "v4"
4) "v1"
# SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中
阿里云远程服务器:0>sunionstore unionkey key key1
"4"
阿里云远程服务器:0>smembers unionkey
1) "v2"
2) "v3"
3) "v4"
4) "v1"
##########################################################
# 差集 SDIFF key1 [key2] 返回给定所有集合的差集
阿里云远程服务器:0>smembers key1 # 集合1
1) "v4"
2) "v3"
3) "v1"
阿里云远程服务器:0>smembers key# 集合2
1) "v2"
2) "v1"
阿里云远程服务器:0>sdiff key key1 # 集合2 -集合1
1) "v2"
阿里云远程服务器:0>sdiff key1 key# 集合1-集合2
1) "v3"
2) "v4"
# SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中
阿里云远程服务器:0>sdiffstore diffstore key key1
"1"
阿里云远程服务器:0>smembers diffstore
1) "v2"
业务场景
每位用户首次使用今日头条时会设置3项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户对其他信息类别逐渐产生兴趣,增加客户留存度,如何实现?
业务分析
系统分析出各个分类的最新或最热点信息条目并组织成set集合
随机挑选其中部分信息 配合用户关注信息分类中的热点信息组织成展示的全信息集合
阿里云远程服务器:0>sadd role r1 r2 r3 r4# 分析出各个分类的最新或最热点信息条目并组织成set集合
"4"
阿里云远程服务器:0>srandmember role # 随机挑选其中部分信息
"r1"
阿里云远程服务器:0>srandmember role
"r2"
阿里云远程服务器:0>srandmember role
"r1"
阿里云远程服务器:0>srandmember role
"r3"
阿里云远程服务器:0>srandmember role
"r4"
阿里云远程服务器:0>spop role
"r1"
阿里云远程服务器:0>spop role
"r3"
阿里云远程服务器:0>smembers role # 配合用户关注信息分类中的热点信息组织成展示的全信息集合
1) "r4"
2) "r2"
redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
zset 类型
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
SortedSet具备下列特性:
- 可排序
- 元素不重复
- 查询速度快
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
序号 | 命令及描述 |
---|---|
1 | ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
2 | ZCARD key 获取有序集合的成员数 |
3 | ZCOUNT key min max 计算在有序集合中指定区间分数的成员数 |
4 | ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment |
5 | ZINTERSTORE destination numkeys key [key ...] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
6 | ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量 |
7 | ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员 |
8 | ZRANGEBYLEX key min max [LIMIT offset count] 通过字典区间返回有序集合的成员 |
9 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员 |
10 | ZRANK key member 返回有序集合中指定成员的索引 |
11 | ZREM key member [member ...] 移除有序集合中的一个或多个成员 |
12 | ZREMRANGEBYLEX key min max 移除有序集合中给定的字典区间的所有成员 |
13 | ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员 |
14 | ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员 |
15 | ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
16 | ZREVRANGEBYSCORE key max min [WITHSCORES] 返回有序集中指定分数区间内的成员,分数从高到低排序 |
17 | ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
18 | ZSCORE key member 返回有序集中,成员的分数值 |
19 | ZUNIONSTORE destination numkeys key [key ...] 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
20 | ZSCAN key cursor [MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值) |
##########################################################
#ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
redis:0>zadd key 1 a 2 b 3 c 4 d
"4"
##########################################################
# ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员 低到高
redis:0>zrange key 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
##########################################################
# ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低
redis:0>zrevrange key 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
##########################################################
# ZCARD key 获取有序集合的成员数
redis:0>zcard key
"4"
##########################################################
# ZRANK key member 返回有序集合中指定成员的索引
redis:0>zrank key a
"0"
redis:0>zrank key c
"2"
hash类型
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的
HashMap
结构。
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD
Hash的常见命令有:
序号 | 命令及描述 |
---|---|
1 | HDEL key field1 [field2] 删除一个或多个哈希表字段 |
2 | HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。 |
3 | HGET key field 获取存储在哈希表中指定字段的值。 |
4 | HGETALL key 获取在哈希表中指定 key 的所有字段和值 |
5 | HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
6 | HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
7 | HKEYS key 获取所有哈希表中的字段 |
8 | HLEN key 获取哈希表中字段的数量 |
9 | HMGET key field1 [field2] 获取所有给定字段的值 |
10 | HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
11 | HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。 |
12 | HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。 |
13 | HVALS key 获取哈希表中所有值 |
14 | HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。 |
# HSET key field value
redis:0>hset hash k v k1 v1 k2 v2
"3"
# HGET key field
redis:0>hget hash k
"v"
#HMGET
redis:0>hmget hash k k1 k2
1) "v"
2) "v1"
3) "v2"
# hkeys
redis:0>hkeys hash
1) "k"
2) "k1"
3) "k2"
# hvals
redis:0>hvals hash
1) "v"
2) "v1"
3) "v2"
# HSETNX
redis:0>hsetnx hash k3 v3
"1"
redis:0>hkeys hash
1) "k"
2) "k1"
3) "k2"
4) "k3"
redis:0>hvals hash
1) "v"
2) "v1"
3) "v2"
4) "v3"
业务场景
127.0.0.1:6379> hset u1 g01 100 g02 200
(integer) 2
127.0.0.1:6379> HGETALL u1
1) "g01"
2) "100"
3) "g02"
4) "200"
127.0.0.1:6379> HMSET u2 g01 100 g02 100
OK
127.0.0.1:6379> HGETALL u2
1) "g01"
2) "100"
3) "g02"
4) "100"
127.0.0.1:6379> hset u1 g03 5
(integer) 1
127.0.0.1:6379> HGETALL u1
1) "g01"
2) "100"
3) "g02"
4) "200"
5) "g03"
6) "5"
127.0.0.1:6379> hdel u1 g01
(integer) 1
127.0.0.1:6379> HGETALL u1
1) "g02"
2) "200"
3) "g03"
4) "5"
127.0.0.1:6379> HINCRBY g02 100
(error) ERR wrong number of arguments for 'hincrby' command
127.0.0.1:6379> HINCRBY u1 g02 100
(integer) 300
127.0.0.1:6379> HGETALL u1
1) "g02"
2) "300"
3) "g03"
4) "5"
127.0.0.1:6379> HMSET u3 g01:nums 100 g01:info {.....}
OK
127.0.0.1:6379> HGETALL u3
1) "g01:nums"
2) "100"
3) "g01:info"
4) "{.....}"
127.0.0.1:6379> HMSET u3 g02:nums 200 g02:info {.....}
OK
127.0.0.1:6379> HGETALL u3
1) "g01:nums"
2) "100"
3) "g01:info"
4) "{.....}"
5) "g02:nums"
6) "200"
7) "g02:info"
8) "{.....}"
127.0.0.1:6379>
解决方案
以客户id作为key,每位客户创建一个hash存储结构存储对应的购物车信息
将商品编号作为field,购买数量作为value进行存储
添加商品:追加全新的field与value
浏览:遍历hash
更改数量:自增/自减,设置value值
删除商品:删除field
清空:删除key
当购物车保存商家信息的时候,可以把商家信息保存成一个Hash,把标记存到购物车的Hash种就可以了
127.0.0.1:6379> hmset p01 c30 1000 c50 1000
OK
127.0.0.1:6379> HINCRBY p01 c30 -20
(integer) 980
127.0.0.1:6379> HGETALL p01
1) "c30"
2) "980"
3) "c50"
4) "1000"
127.0.0.1:6379>
解决方案
以商家id作为key
将参与抢购的商品id作为field
将参与抢购的商品数量作为对应的value
抢购时使用降值的方式控制产品数量
hash 类型数据操作的注意事项
hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到,对应的值为( nil)hash 类型数据操作的注意事项
每个 hash 可以存储 232 - 1 个键值对
hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈
其他数据类型
Redis的三大特殊类型 - Primary丶 - 博客园 (cnblogs.com)
spring redis操作
SpringDataRedis
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
springboot 操作redis
-
首先新建一个Spring Boot工程
-
然后引入连接池依赖
<!--连接池依赖--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
-
编写配置文件
application.yml
(连接池的配置在实际开发中是根据需求来的)spring: redis: host: 192.168.230.88 #指定redis所在的host port: 6379 #指定redis的端口 password: 132537 #设置redis密码 lettuce: pool: max-active: 8 #最大连接数 max-idle: 8 #最大空闲数 min-idle: 0 #最小空闲数 max-wait: 100ms #连接等待时间
-
编写测试类执行测试方法
@SpringBootTest class RedisDemoApplicationTests { @Resource private RedisTemplate redisTemplate; @Test void testString() { // 1.通过RedisTemplate获取操作String类型的ValueOperations对象 ValueOperations ops = redisTemplate.opsForValue(); // 2.插入一条数据 ops.set("blogName","Vz-Blog"); // 3.获取数据 String blogName = (String) ops.get("blogName"); System.out.println("blogName = " + blogName); } }
RedisTemplate的两种序列化
- 自定义RedisTemplate序列化
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
// 1.创建RedisTemplate对象
RedisTemplate<String ,Object> redisTemplate = new RedisTemplate<>();
// 2.设置连接工厂
redisTemplate.setConnectionFactory(factory);
// 3.创建序列化对象
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 4.设置key和hashKey采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// 5.设置value和hashValue采用json的序列化方式
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
}
}
此时我们已经将RedisTemplate的key设置为String序列化
,value设置为Json序列化
的方式,再来执行方法测试
@Autowired
private RedisTemplate redisTemplate;
@Test
void testSaveUser() {
redisTemplate.opsForValue().set("user:100", new User("Vz", 21));
User user = (User) redisTemplate.opsForValue().get("user:100");
System.out.println("User = " + user);
}
{
"@class": "com.example.review.demos.web.User",
"name": "小红",
"age": 10
}
为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。
那么我们如何解决这个问题呢?我们可以通过下文的StringRedisTemplate
来解决这个问题。
2. StringRedisTemplate
-
使用StringRedisTemplate
-
写入Redis时,手动把对象序列化为JSON
-
读取Redis时,手动把读取到的JSON反序列化为对象
-
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程
@SpringBootTest
class RedisStringTemplateTest {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
void testSaveUser() throws JsonProcessingException {
// 1.创建一个Json序列化对象
ObjectMapper objectMapper = new ObjectMapper();
// 2.将要存入的对象通过Json序列化对象转换为字符串
String userJson1 = objectMapper.writeValueAsString(new User("Vz", 21));
// 3.通过StringRedisTemplate将数据存入redis
stringRedisTemplate.opsForValue().set("user:100",userJson1);
// 4.通过key取出value
String userJson2 = stringRedisTemplate.opsForValue().get("user:100");
// 5.由于取出的值是String类型的Json字符串,因此我们需要通过Json序列化对象来转换为java对象
User user = objectMapper.readValue(userJson2, User.class);
// 6.打印结果
System.out.println("user = " + user);
}
}
{
"name": "Vz",
"age": 21
}
redis事务
redis事务本质
Redis事物的本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的中,会按照顺序执行:一次性、顺序性、排他性!执行一系列的命令
Redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。
除此之外,*Redis的事务还能保证一个事务内的命令依次执行而不被其他命令插入*。试想客户端A需要执行几条命令,同时客户端B发送了一条命令,如果不使用事务,则客户端B的命令可能会插入到客户端A的几条命令中执行。如果不希望发生这种情况,也可以使用事务。
redis事务命令
事务执行流程图
- Multi :开启事务
- Exec :执行事务
- discard:放弃事务
- watch: 监控事务: 加锁
- unwatch: 取消监控: 去掉锁
MULTI命令用于开启一个事务,它总是返回OK
。
MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
另一方面,通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务。
WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
UNWATCH:取消WATCH对所有key的监视
- 没开启事务:两台客户端对redis 操作
客户端一:
127.0.0.1:6379> set name test1
OK
127.0.0.1:6379> get name
"hahha"
客户端二:
aliyun:0>set name hahha
"OK"
客户端一写后,在读之前 ,客户端二又写入,导致读取的是客户端二的结果。
- 开启事务和执行事务
客户端一:开启事务
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) "18"
127.0.0.1:6379> get age
"18"
客户端二:写入age
aliyun:0>set age 100
"OK"
aliyun:0>get age
"18"
- 放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set id 100
QUEUED
127.0.0.1:6379> get id
QUEUED
127.0.0.1:6379> discard# 放弃事务
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
放弃事务
当执行 DISCARD 命令时,事务会被放弃,事务队列会被清空,并且客户端会从事务状态中退出:
redis事务错误
- 语法错误
如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令。
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set team 1
QUEUED
127.0.0.1:6379> getset team # 语法错误
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec # 执行事务:事务队列中所有数据都不执行
(error) EXECABORT Transaction discarded because of previous errors.
- 运行错误
指命令格式正确,但是无法正确的执行。例如对list进行incr操作
能够正确运行的命令会执行,运行错误的命令不会被执行 ,那就需要自己回滚一些脏数据了。
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set name xiaobai
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> set name xiaohong
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> lpush name a b c #命令正确:但是错误的命令
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) "xiaobai"
3) OK
4) "xiaohong"
5) (error) WRONGTYPE Operation against a key holding the wrong kind of value
6) "xiaohong"
127.0.0.1:6379> get name # 事务中争取的命令执行了,错误的命令没有执行
"xiaohong"
注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。
redis 监控事务:乐观锁
乐观锁:
- 很乐观,认为什么时候都不会有问题,所以不会上锁! 更新数据的时候去判断一下,在此期间是否有人修改过数据
- 获取version
- 更新数据的时候比较version
悲观锁:
- 很悲观,认为任何情况都会出问题,所以无论做什么都会加锁!
- 单线程
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
- 多个线程
# 线程一
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> get out
"20"
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
# 线程二:在线程一执行事务之前:修改了值
42.57.78.34:6379> set money 1000
OK
#线程一:执行事务 失败
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get money
"1000"
127.0.0.1:6379> unwatch #事务执行失败手动解锁
OK# 解锁
watch:加锁 事务执行成功自动解锁
unwatch:解锁 事务执行失败 需要自己手动解锁
参考博客
Redis进阶 - 事务:Redis事务详解 | Java 全栈知识体系 (pdai.tech)
Redis的事务 - Primary丶 - 博客园 (cnblogs.com)
Redis的事务 - Primary丶 - 博客园 (cnblogs.com)
redis.conf
文件包含
################################## INCLUDES ###################################
# Include one or more other config files here. This is useful if you
配置多个redis的配置文件
# include /path/to/local.conf
# include /path/to/other.conf
网络设置
# 绑定的IP
bind 127.0.0.1
# 保护模式 no:关闭 外网可以直接访问redis yes:开启保护模式,开启的情况下,只能通过绑定的ip[bind 127.0.0.1]或者密码验证访问
protected-mode yes
# 启动的端口号
port 6379
通用general
daemonize no:设置redis的运行方式
- no:客户端运行,客户端关闭redis服务也停止
- yes:以守护进程的方式运行,即后台运行,客户端关闭redis服务也在运行
################################# GENERAL #####################################
# 设置运行方式 默认值:no,需要改为yes,以守护进程的方式运行
daemonize no
# 管理守护进程的,这个默认是no,一般不用动
supervised no
# 配置文件的pid文件,如果以后台方式运行,就需要指定一个pid文件
pidfile /var/run/redis_6379.pid
# 日志级别设置
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing) 适用于开发环境或测试环境
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 适用于生产环境
# warning (only very important / critical messages are logged)
loglevel notice
# 日志文件位置名
logfile ""
# redis数据库个数
databases 16
# 是否总是显示redis的LOGO,启动时看到的redis的LOGO就是这里配置的,yes:显示 no:不显示
always-show-logo yes
快照的相关配置
快照是用于持久化的,因为redis是内存数据库,如果不进行持久化处理,万一部署的服务器断电,那么所有的redis数据都会丢失。
快照配置一般不需要改动,可能会涉及需要改动的是执行持久化的策略save配置.
save 间隔时间 修改量。例如:
save 10 1:表示在10s内如果至少有1个key进行了修改,就会触发持久化操作
################################ SNAPSHOTTING ################################
# 持久化保存策略配置
# 在900s内,如果至少有1个key进行了修改,就进行持久化操作
save 900 1
# 在300s内,如果至少有10个key进行了修改,就进行持久化操作
save 300 10
# 在60s内,如果至少有10000个key进行了修改,就进行持久化操作
save 60 10000
# 配置如果持久化出错,Redis是否禁止写入命令 yes:禁止写入命令,no:允许写入命令(存在数据丢失风险)
stop-writes-on-bgsave-error yes
# 配置是否压缩rdb文件。[开启(yes)的话,会消耗一定的cpu资源]
rdbcompression yes
# 保存rdb文件的时候,进行错误的检查校验
rdbchecksum yes
# 默认持久化保存后的文件名
dbfilename dump.rdb
# rdb文件保存的目录
dir ./
安全配置SECURITY
################################## SECURITY ###################################
# 设置访问、登录的密码,设置requirepass 您的密码
requirepass 密码
客户端设置
客户端CLIENTS的配置 maxmemory-policy:redis到达最大容量之后的处理策略
- volatile -lru:只对设置了过期时间的key进行LRU(默认值)
- allkeys-lru:删除lru算法的key
- volatile-random:随机删除即将过期的key
- allkeys-random:随机删除
- volatile-ttl:删除即将过期的
################################### CLIENTS ####################################
# 设置客户端最大连接数,该配置一般无需修改,使用默认值即可
# maxclients 10000
############################## MEMORY MANAGEMENT ################################
# redis配置的最大内存容量
# maxmemory <bytes>
# 到达内存容量限制之后的处理策略
# maxmemory-policy noeviction
持久化策略AOF 设置
############################## APPEND ONLY MODE ###############################
# 是否开启aof持久化模式,默认值:no,不开启。redis的默认持久化策略是edb模式
appendonly no
# 持久化文件名称
appendfilename "appendonly.aof"
# 持久化策略设置
# appendfsync always # 每次修改都进行持久化操作
appendfsync everysec # 每秒执行一次持久化操作
# appendfsync no # 不执行持久化操作,相当于未开启aof持久化策略
# 设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes
no-appendfsync-on-rewrite no
# AOF自动重写配置,默认值为100
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# Redis在以AOF方式恢复数据时,对最后一条可能出问题的指令的处理方式,默认值yes
aof-load-truncated yes
# 当重写AOF文件时,Redis能够在AOF文件中使用RDB前导码,以便更快地重写和恢复,启用此选项时,重写的AOF文件由两个不同的节组成:[RDB file][AOF tail],当加载AOF文件时,Redis通过以 “REDIS” 字符串开头的AOF文件识别出此文件是由RDB和AOF组合而成的,Redis会先加载RDB部分,然后再加载AOF部分,默认值yes
aof-use-rdb-preamble yes
博客
Redis配置文件redis.conf配置详解-CSDN博客
redis持久化
为什么需要持久化
Redis是个基于内存的数据库。那服务一旦宕机,内存中的数据将全部丢失。通常的解决方案是从后端数据库恢复这些数据,但后端数据库有性能瓶颈,如果是大数据量的恢复,1、会对数据库带来巨大的压力,2、数据库的性能不如Redis。导致程序响应慢。所以对Redis来说,实现数据的持久化,避免从后端数据库中恢复数据,是至关重要的
RDB
RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。
rdb执行流程
Redis 会单独创建(fork)一个子进程来进行持久化
- Fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
- 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”
- 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
先将数据写入到一个临时文件中待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件(dump.rdb)
- 在redis.conf中配置持久化文件名称,默认为dump.rdb
- rdb文件的保存路径,也可以修改。默认为Redis启动时命令行所在的目录下
整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能,RDB的缺点是最后一次持久化后的数据可能丢失。
rdb 执行策略
配置文件中默认的快照配置:修改配置后重启redis服务器
# 周期性执行条件的设置格式为
save <seconds> <changes>
# 默认的设置为:
save 900 1
save 300 10
save 60 10000
# 以下设置方式为关闭RDB快照功能
save ""
以上三项默认信息设置代表的意义是:
- 如果900秒内有1条Key信息发生变化,则进行快照;
- 如果300秒内有10条Key信息发生变化,则进行快照;
- 如果60秒内有10000条Key信息发生变化,则进行快照。读者可以按照这个规则,根据自己的实际请求压力进行设置调整
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes
优缺点
优点
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
缺点
- Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
- 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改
8.Redis的持久化机制之快照持久化(一)_哔哩哔哩_bilibili
AOF
AOF(Append Only File):以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
AOF执行步骤
如何实现AOF
AOF日志记录Redis的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)。
- 命令追加 当AOF持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的 aof_buf 缓冲区。
- 文件写入和同步 关于何时将 aof_buf 缓冲区的内容写入AOF文件中,Redis提供了三种写回策略:
配置文件打开:修改配置后重启redis
# appendonly参数开启AOF持久化
appendonly no
# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./
# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no
# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载aof出错如何处理
aof-load-truncated yes
# 文件重写策略
aof-rewrite-incremental-fsync yes
aof 文件修复: redis-check-aof --fix
数据恢复
- redis重启时判断是否开启aof,如果开启了aof,那么就优先加载aof文件;
- 如果aof存在,那么就去加载aof文件,加载成功的话redis重启成功,如果aof文件加载失败,那么会打印日志表示启动失败,此时可以去修复aof文件后重新启动;
- 若aof文件不存在,那么redis就会转而去加载rdb文件,如果rdb文件不存在,redis直接启动成功;
- 如果rdb文件存在就会去加载rdb文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么redis重启成功,且使用rdb文件恢复数据;
那么为什么会优先加载AOF呢?因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据。
博客
Redis进阶 - 持久化:RDB和AOF机制详解 | Java 全栈知识体系 (pdai.tech)
主从复制:读写分离
主从复制 概述和作用
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
主从复制的作用
主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
主从复制 原理
主从库之间采用的是读写分离的方式。
- 读操作:主库、从库都可以接收;
- 写操作:首先到主库执行,然后,主库将写操作同步给从库
主从复制原理
- 1、当从连接上主服务器之后,从服务器向主服务器发送数据同步消息;
- 2、主服务器接到从服务器发送过来同步消息,把主服务器数据持久化为 rdb 文件,把 rdb 文件发送给从服务器,从服务器拿到 rdb 进行读取;(全量复制)
- 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
- 3、之后每次主服务器进行写操作之后,和从服务器进行数据同步(增量复制)(即第一次需要从服务器请求,之后每次主服务器主动同步)
- 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
- 从机挂掉,重新连接主机,自动执行一次全量复制
一主二仆模拟
- 复制配置文件
[root@iZ2vc2rht08hck1x6us154Z bin]# ls
appendonly.aof my.rdb redis-benchmark redis-check-aof redis-check-rdb redis-cli redis.conf redis-sentinel redis-server
[root@iZ2vc2rht08hck1x6us154Z bin]# mkdir conf
[root@iZ2vc2rht08hck1x6us154Z bin]# cp redis.conf ./conf/redis78.conf
[root@iZ2vc2rht08hck1x6us154Z bin]# cp redis.conf ./conf/redis77.conf
[root@iZ2vc2rht08hck1x6us154Z bin]# cd conf
[root@iZ2vc2rht08hck1x6us154Z conf]# ll
total 256
-rw-r--r-- 1 root root 62187 Dec 23 13:05 redis77.conf
-rw-r--r-- 1 root root 62187 Dec 23 13:05 redis78.conf
- 修改复制的配置文件
[root@iZ2vc2rht08hck1x6us154Z conf]# vim redis77.conf
# 修改配置文件
port 6377
pidfile /var/run/redis_6377.pid
loglevel notice
logfile "6377.log"
dbfilename my6377.rdb
# 修改配置文件
[root@iZ2vc2rht08hck1x6us154Z conf]# vim redis78.conf
port 6378
pidfile /var/run/redis_6378.pid
loglevel notice
logfile "6378.log"
dbfilename my6378.rdb
- 配置一主二仆
# 启动6377 和6378 服务器
[root@iZ2vc2rht08hck1x6us154Z bin]# ./redis-server conf/redis77.conf
[root@iZ2vc2rht08hck1x6us154Z bin]# ./redis-server conf/redis78.conf
#客户端 连接6377redis 服务器
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -h 127.0.0.1 -p 6377
127.0.0.1:6377> ping
PONG
127.0.0.1:6377> info replication
# Replication
role:master
connected_slaves:0
master_replid:a7f787a1c8519adc64f592a6db0b4d3a1b57e6ae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6377>
#客户端 连接6378redis 服务器
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -h 127.0.0.1 -p 6378
127.0.0.1:6378> ping
PONG
127.0.0.1:6378> info replication
# Replication
role:master # 没有开启主从配置:每一个服务器都是主服务器
connected_slaves:0 # 从服务器 为0
master_replid:a7f787a1c8519adc64f592a6db0b4d3a1b57e6ae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6378>
# 配置 以 6379 为主 :6377 6378为从: salveof 主服务器ip 主服务器redis端口
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -h 127.0.0.1 -p 6378
127.0.0.1:6378> ping
PONG
127.0.0.1:6378> info replication
# Replication
role:master
connected_slaves:0
master_replid:a7f787a1c8519adc64f592a6db0b4d3a1b57e6ae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6378> SLAVEOF 127.0.0.1 6379 # 配置主服务器
OK
127.0.0.1:6378> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379 # 主服务器:
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:1078
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:b963c6443caf85599109f93a059d14bb5c60ebb1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1078
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1023
repl_backlog_histlen:56
- 测试主写 从读
# 查看服务器
[root@iZ2vc2rht08hck1x6us154Z ~]# ps -ef |grep redis
root 6393 1 0 13:14 ? 00:00:00 ./redis-server *:6377
root 12846 1 0 13:24 ? 00:00:00 ./redis-server *:6378
root 14936 30460 0 13:25 pts/0 00:00:00 redis -h 127.0.0.1 -p 6377
root 15923 1 0 13:17 ? 00:00:00 /usr/local/redis/bin/redis-server *:6379
root 16977 14781 0 13:17 pts/1 00:00:00 redis
root 18344 18327 0 13:34 pts/3 00:00:00 grep --color=auto redis
root 19137 18065 0 13:26 pts/2 00:00:00 redis -h 127.0.0.1 -p 6378
# 主写
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set id 1
OK
127.0.0.1:6379> set name wang
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> keys *
1) "name"
2) "id"
3) "age"
127.0.0.1:6379> get id
"1"
127.0.0.1:6379> get name
"wang"
127.0.0.1:6379> get age
"18"
# 从读
127.0.0.1:6377> keys *
1) "name"
2) "age"
3) "id"
127.0.0.1:6377> get id
"1"
127.0.0.1:6377> get name
"wang"
127.0.0.1:6377> get age
"18"
127.0.0.1:6377> set name 123 # 从写失败
(error) READONLY You can't write against a read only replica.
127.0.0.1:6378> keys *
1) "id"
2) "age"
3) "name"
127.0.0.1:6378> get id
"1"
127.0.0.1:6378> get name
"wang"
127.0.0.1:6378> get age
"18"
127.0.0.1:6378> set name xiao # 从写失败
(error) READONLY You can't write against a read only replica.
关闭主从配置:
127.0.0.1:6377> slaveof no one
从头开始复制;可以
从机是否可以写?set可否?
不可以
主机shutdown后情况如何?从机是上位还是原地待命?
主机挂掉,从机原地待命
主机又回来了后,主机新增记录,从机还能否顺利复制?
可以
其中一台从机down后情况如何?依照原有它能跟上大部队吗?
从机挂掉重启后需要重设:slaveof 127.0.0.1 6377
# 挂掉主机
127.0.0.1:6379> SHUTDOWN
# 查看服务
[root@iZ2vc2rht08hck1x6us154Z ~]# ps -ef |grep redis
root 6393 1 0 13:14 ? 00:00:01 ./redis-server *:6377
root 10823 18327 0 13:41 pts/3 00:00:00 grep --color=auto redis
root 12846 1 0 13:24 ? 00:00:00 ./redis-server *:6378
root 14936 30460 0 13:25 pts/0 00:00:00 redis -h 127.0.0.1 -p 6377
root 19137 18065 0 13:26 pts/2 00:00:00 redis -h 127.0.0.1 -p 6378
# 从机 可以读
127.0.0.1:6378> keys *
1) "id"
2) "age"
3) "name"
127.0.0.1:6378> get id
"1"
127.0.0.1:6378> get name
"wang"
127.0.0.1:6378> get age
"18"
# 主机从新开机
[root@iZ2vc2rht08hck1x6us154Z ~]# systemctl start redis
[root@iZ2vc2rht08hck1x6us154Z ~]# redis
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set address henan
OK
127.0.0.1:6379> keys *
1) "address"
2) "age"
3) "name"
4) "id"
127.0.0.1:6379>
# 从机继续复制
127.0.0.1:6378> keys *
1) "id"
2) "name"
3) "age"
4) "address"
主从从
通过分析主从库间第一次数据同步的过程,你可以看到,一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。
如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生成 RDB 文件,进行数据全量复制。
- fork 这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。
- 传输 RDB 文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。
其实是有的,这就是“主 - 从 - 从”
我们可以通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。
简单来说,我们在部署主从集群的时候,可以手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后,我们可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。
从变主:手动配置
当一个 master 宕机后,后面的 slave 可以立刻升为 master,其后面的 slave 不用做任何修改。用 slaveof no one
将从机变为主机
127.0.0.1:6377> slaveof 127.0.0.1 6378
OK
127.0.0.1:6378> slaveof 127.0.0.1 6379
OK
#主从从模式 6379 主-》6378 从-》6377 从
如果6379还是主:6378 无法写
当6379 挂掉:通过slaveof no one 6378变主 才可以写
读写分离及其中的问题
在主从复制基础上实现的读写分离,可以实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);在读负载较大的应用场景下,可以大大提高Redis服务器的并发量。下面介绍在使用Redis读写分离时,需要注意的问题。
- 延迟与不一致问题
前面已经讲到,由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度程度较低,可能的优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;使用集群同时扩展写负载和读负载等。
在命令传播阶段以外的其他情况下,从节点的数据不一致可能更加严重,例如连接在数据同步阶段,或从节点失去与主节点的连接时等。从节点的slave-serve-stale-data参数便与此有关:它控制这种情况下从节点的表现;如果为yes(默认值),则从节点仍能够响应客户端的命令,如果为no,则从节点只能响应info、slaveof等少数命令。该参数的设置与应用对数据一致性的要求有关;如果对数据一致性要求很高,则应设置为no。
- 数据过期问题
在单机版Redis中,存在两种删除策略:
惰性删除
:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。定期删除
:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。
Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。
- 故障切换问题
在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。
详细参考 原文链接:https://pdai.tech/md/db/nosql-redis/db-redis-x-copy.html
哨兵:从->主 自动配置
redis高可用
Redis实现高可用相关的技术。它们包括:持久化、复制、哨兵和集群,其主要作用和解决的问题是:
- 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
- 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
- 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
- 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
哨兵功能
哨兵的核心功能是主节点的自动故障转移
深入学习Redis(4):哨兵 - 编程迷思 - 博客园 (cnblogs.com)
哨兵架构
它由两部分组成,哨兵节点和数据节点:
- 哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。
- 数据节点:主节点和从节点都是数据节点。
部署哨兵节点
- 部署主从节点
#6377.conf
port 6377
daemonize yes
logfile "6377.log"
dbfilename 2022dump.rdb
#redis2021.conf
port 2021
daemonize yes
logfile "2021.log"
dbfilename 2021dump.rdb
#启动redis
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -p 2021
127.0.0.1:2021>
#redis.conf: 主节点
port 2023
daemonize yes
logfile "2023.log"
dbfilename 2023dump.rdb
#启动redis
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -p 2023
127.0.0.1:2021>
- 配置主从复制
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -p 2021
127.0.0.1:2021> slaveof 127.0.0.1 2023
OK
127.0.0.1:2021> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:2023
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:42
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:996bfb788821f60cb61575929158fef080c84028
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:42
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:42
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -p 2022
127.0.0.1:2022> ping
PONG
127.0.0.1:2022> slaveof 127.0.0.1 2023
OK
127.0.0.1:2022> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:2023
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:28
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:996bfb788821f60cb61575929158fef080c84028
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:14
127.0.0.1:2022>
[root@iZ2vc2rht08hck1x6us154Z bin]# redis -p 2023
127.0.0.1:2023> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=2021,state=online,offset=126,lag=1
slave1:ip=127.0.0.1,port=2022,state=online,offset=126,lag=0
master_replid:996bfb788821f60cb61575929158fef080c84028
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:126
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:126
127.0.0.1:2023>
- 编写哨兵配置
sentinel.conf
应用问题
1. 缓存穿透
问题描述
key 对应的数据在数据源并不存在,每次针对此 key 的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
- 1、应用服务器压力变大了
- 2、redis 命中率降低
- 3、 一直查询数据库
一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
解决方案
- 1、对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
- 2、设置可访问的名单(白名单):使用 bitmaps 类型定义一个可以访问的名单,名单 id作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在bitmaps 里面,进行拦截,不允许访问。
- 3、采用布隆过滤器:布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。将所有可能存在的数据哈希到一个足够大的布隆过滤器中,一个一定不存在的数据会被这个布隆过滤器拦截掉,从而避免了对底层存储系统的查询压力。
- 4、进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
2.缓存击穿
问题描述
key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。
- 1、数据库访问压力瞬时增加
- 2、redis 某个 key 过期了,大量访问使用这个key
- 3、redis 正常运行
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
解决方案
-
1、预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
-
2、实时调整:现场监控哪些数据热门,实时调整key的过期时长
-
3、使用锁:
- 1.就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
- 2.先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
- 3.当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
- 4.当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
3.缓存雪崩
问题描述
key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。缓存雪崩与缓存击穿的区别在于这里针对很多 key 缓存,前者则是某一个 key。
- 1、数据库压力变大服务器崩溃
- 2、在极少时间段,查询大量 key 的集中过期
缓存失效时的雪崩效应对底层系统的冲击非常可怕!
解决方案
- 1、 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)
- 2、使用锁或队列:用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
- 3、 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
- 4、将缓存失效时间分散开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
博客
Redis进阶 - 高可用:主从复制详解 | Java 全栈知识体系 (pdai.tech)
本文来自博客园,作者:我爱读论文,转载请注明原文链接:https://www.cnblogs.com/life1314/p/17948951