Redis 学习笔记
整理自B站狂神说
文章目录
什么是Redis
Redis是一种非关系型数据库(NoSQL)
NoSQL
单机MySQL 瓶颈:数据量过大、数据的索引过大、访问量(读写混合)过大
优化数据结构和索引->文件缓存(IO)->Memcached
Memcached(缓存)+MySQL+垂直拆分(读写分离)
分库分表+水平拆分+MySQL集群
数据量多、变化快、需要存储一些博客、图片等大文件,MySQL等关系型数据库不够用
用户个人信息、社交网络、地理位置。用户自己产生的数据、用户日志等爆发式增长。所以需要NoSQL
NoSQL Not Only SQL。数据类型存储不需要固定格式,不需要多余操作就能横向扩展
特点:方便扩展、大数据量高性能、数据类型多样(不需要事先设计数据库、随取随用)、高可用
没有固定的查询语言
存储方式:键值对存储、列存储、文档存储、图形数据库(社交关系)
最终一致性
CAP定理和BASE(异地多活)
关系型数据库 (RDBMS,Relational Database Management System)
结构化组织(表格、行、列)、数据和关系都存在单独的表中,SQL(Structured Query Language),操作语言,数据定义语言、严格的一致性、基础的事物
大数据时代的3V+3高
3V 主要是描述问题的:海量Volume、多样Variety、实时Velocity
3高 主要是对程序的要求:高并发、高可拓、高性能
NoSQL分类
KV键值对:
应用场景:内容缓存、日志
- 新浪:Redis
- 美团:Redis + Tair
- 阿里、百度:Redis + Memcached
文档型数据库(bson格式和json一样):
应用场景:Web应用
- MongoDB:基于分布式文件存储的数据库,C++编写,主要用来处理大量文档。是一个介于RDBMS和NoSQL的中间产物。是NoSQL中最像RDBMS的
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
存放关系而非图形,比如:朋友圈社交网络、广告推荐
- Neo4j,InfoGrid
Redis
Remote Dictionary Server 远程字典服务
开源的使用C语言编写的Key-Value型数据库,提供多种语言的API,支持网络,基于内存
也被称为结构化数据库
一秒读取11万、写8万
Redis能做什么?
- 内存存储,持久化(内存中断电即失)(持久化机制:RDB、AOF)
- 效率高,可以用于高速缓存
- 发布订阅系统**(消息中间件)**
- 地图信息分析
- 计时器、计数器(浏览量)
特性
- 多样的数据类型
- 持久化:会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在此基础上实现了master-slave(主从)同步
- 集群
- 事务
单线程
Redis非常快,因为Redis是基于内存操作的,CPU不是Redis性能瓶颈,而是机器的内存和网络带宽。
误区
- 高性能的服务器一定是多线程的?
- 多线程一定比单线程效率高?(CPU上下文切换影响效率)
速度:CPU>内存>硬盘
Redis将所有数据全部放在内存中,而多线程带来的CPU上下切换会影响效率,所以用单线程的操作效率最高。
Redis Keys基本命令
redis> PING
"PONG"
redis> SET mykey "Hello" # set key
"OK"
redis> SETEX mykey 10 "Hello" # set key 并设置过期时间
"OK"
redis> SETNX mykey "World" # SET if Not eXists,set成功返回1,不成功返回0。在分布式锁中常用
(integer) 0
redis> GET mykey # get key value
"Hello"
redis> EXISTS mykey # 查看key是否存在,存在返回1,不存在为0
(integer) 1
redis> EXPIRE mykey 10 # 设置过期时间为10秒(可以用于单点登录)
(integer) 1
redis> TTL mykey # 查看剩余时间
(integer) 10
redis> DEL mykey key1 # 移除key,返回移除的数量
(integer) 1
redis> MOVE mykey 1 # 将key移动到目标数据库,移动成功返回1,当前数据库没有该key或目标数据库已存在该key,不移动,返回0
(integer) 1
redis> TYPE myKey # 查看key对应value的数据类型
"string"
redis> FLUSHDB # 清除当前数据库内容
"OK"
redis> FLUSHALL # 清除所有数据库内容
"OK"
更多命令,详见官方文档
数据类型
常见五大数据类型
String
redis> APPEND mykey " World" # 向字符串追加内容,返回字符串长度。如果当前key不存在,相当于set key
(integer) 11
redis> STRLEN mykey # 获取字符串长度
(integer) 11
redis> KEYS * # 获取所有key,支持正则表达式
1) "mykey"
redis> INCR/DECR key1 # 自增/自减,返回结果
(integer) 11
redis> INCRBY/DECRBY key1 5 # 增/减运算,带步长,返回结果
(integer) 11
redis> GETRANGE mykey 0 4 # 截取[0,4]的字符串,可以使用负偏移量,比如-1指倒数第一个字符
"Hello"
redis> SETRANGE mykey 6 "Redis" # 从第6位开始替换。如果key不存在,会新建并补位
(integer) 11
redis> MSET key1 "Hello" key2 "World" # 批量set0
"OK"
redis> MGET key1 key2 # 批量get
1) "Hello"
2) "World"
redis> MSETNX key2 "new" key3 "world" # 因为key2存在,所以key3设置失败
(integer) 0
##### 实际应用 #####
redis> SET user:1 {name:zhangsan,age:3}
"OK"
### 或者 user:{id}:{field}
redis> MSET user:1:name zhangsan user:1:age 3
"OK"
redis> MGET user:1:name user:1:age
1) "zhangsan"
2) "3"
redis> GETSET mykey "World" # 先get再set
"Hello"
List
可以用于实现栈、队列、阻塞队列。
实际上是一个链表,可以在某值前后插入,从头尾插入。
如果key不存在,则创建新列表,存在则插入值。如果移除所有值,则不存在。
在两边插入或改动值,效率最高!中间元素,相对效率较低。
可以做消息排队、消息队列。
redis> LPUSH mylist "world" # 从头部插入,返回list大小,允许重复值
(integer) 1
redis> LPUSH mylist "hello"
(integer) 2
redis> LRANGE mylist 0 -1 # 范围查询,支持负偏移量
1) "hello"
2) "world"
redis> RPUSH mylist "hellor" # 从尾部插入
(integer) 1
redis> LPOP/RPOP mylist # 从头部/尾部弹出,返回弹出值
"one"
redis> LINDEX mylist 0 # 通过下标获取值
"Hello"
redis> LLEN mylist # 返回列表长度
(integer) 2
redis> LREM mylist -2 "hello" # 移除指定个数的指定值,-2指后两个
(integer) 2
redis> LTRIM mylist 1 -1 # 截断操作,保留从第一个到最后一个
"OK"
redis> RPOPLPUSH mylist myotherlist # 从尾部移除并从头部插入另一列表,返回该值
"three"
redis> LSET mylist 0 "four" # 更新指定下标的值,不存在该下标则报错
"OK"
redis> LINSERT mylist BEFORE/AFTER "World" "There" # 在某值前/后插入值
(integer) 3
Set(无序集合)
无序不重复集合。
redis> SADD myset "Hello" # 添加值,返回成功添加的个数。可以批量添加。
(integer) 1
redis> SISMEMBER myset "Hello" # 查询值是否存在
(integer) 1
redis> SCARD myset # 获取set中元素个数
(integer) 2
redis> SREM myset "four" # 移除值,不存在则返回0
(integer) 0
redis> SRANDMEMBER myset 2 # 随机抽选指定个数元素,不指定默认为1。正数k返回结果不超过set容量;-k返回k个可以有重复的随机值。
1) "one"
2) "three"
redis> SPOP myset 3 # 随机移除指定个数元素并返回,不指定默认为1
1) "four"
2) "one"
3) "five"
# 将指定值从一个set移动到另一个set,如果目标set已有该元素,只移除。当前set如果没有该值,则返回0
redis> SMOVE myset myotherset "two"
(integer) 1
redis> SDIFF key1 key2 # 返回在key1中但是不在key2中的元素(差集)
1) "a"
2) "b"
redis> SINTER key1 key2 # 返回key1和key2的交集 (共同好友,互相关注)
1) "c"
redis> SUNION key1 key2 # 返回key1和key2的并集
1) "b"
2) "c"
3) "d"
4) "a"
5) "e"
Hash
Map集合,操作上和String没有太大区别
可以用于一些变更的数据,尤其是用户信息
redis> HSET myhash field1 "Hello" # 添加值,可以批量,返回成功添加的个数
(integer) 1
redis> HGET myhash field1 # 取值
"Hello"
redis> HMSET myhash field1 "Hello" field2 "World" # 批量添加
"OK"
redis> HMGET myhash field1 field2 nofield # 批量取值
1) "Hello"
2) "World"
3) (nil)
redis> HGETALL myhash # 取所有值
1) "field1"
2) "Hello"
3) "field2"
4) "World"
redis> HDEL myhash field1 # 删除指定的key,不存在返回0
(integer) 1
redis> HLEN myhash # 返回hash的字段数量
(integer) 2
redis> HEXISTS myhash field1 # 判断指定字段是否存在,存在返回1,不存在返回0
(integer) 1
redis> HKEYS myhash # 返回所有keys
1) "field1"
2) "field2"
redis> HVALS myhash # 返回所有值
1) "Hello"
2) "World"
redis> HINCRBY myhash field -1 # 对指定字段的值自增-1
(integer) 5
redis> HSETNX myhash field "Hello" # 如果不存在则添加(分布式锁)
(integer) 1
Zset(有序集合)
应用场景:
成绩单、工资表排序;消息权重;排行榜
redis> ZADD myzset 2 "two" 3 "three" # 添加多个值,按score排序,返回成功添加的个数
(integer) 2
redis> ZRANGE myzset 0 -1 WITHSCORES # 可以不withscores
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
redis> ZRANGEBYSCORE myzset (1 3 # 返回区间,参数分别是最小值和最大值,min可以是-inf,max可以是+inf,(代表开区间
1) "two"
2) "three"
redis> ZREVRANGE myzset 0 -1 # 降序,与ZRANGE用法类似。ZREVRANGESCORE与ZRANGEBYSCORE用法类似
1) "three"
2) "two"
3) "one"
redis> ZREM myzset "two" # 移除指定元素
(integer) 1
redis> ZCARD myzset # 返回集合中的元素个数
(integer) 2
redis> ZCOUNT myzset (1 3 # 返回指定区间的元素个数
(integer) 2
三种特殊数据类型
geospatial
定位、附近的人、距离计算
# 添加地理位置经纬度,经度范围为-180~180,纬度范围为-85.05112878~85.05112878。可以批量添加。南北两极无法直接添加。一般用批量导入
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEODIST Sicily Palermo Catania # 查询两个位置之间的直线距离,可以添加单位m/km/mi/ft,默认为m。最多有0.5%误差
"166274.1516"
# 查询半径内的地理位置,单位有m/km/mi/ft,可选项[WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km # 通过成员查找
1) "Agrigento"
2) "Palermo"
redis> GEOPOS Sicily Palermo Catania NonExisting # 查询定位,因为add时存入的是52位GEOHASH,所以取出时会有小误差
1) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
3) (nil)
redis> GEOHASH Sicily Palermo Catania # 将经纬度表示为长度为11的字符串
1) "sqc8b49rny0"
2) "sqdtr74hyu0"
底层实现原理其实是Zset,所以可以用Zset相关命令操作
HyperLogLog
统计基数:集合去重后元素的个数
应用场景:统计网站UV(Unique Visitor),一个用户访问多次计为1次。使用Set也可以解决,但是数据量较大时比较占内存。
优点:占用内存量固定,2^64个不同元素只需12KB
缺点:0.81%错误率。但是比如统计UV等场景,可以忽略不计。对于精确统计场景,不能使用HyperLogLog
redis> PFADD hll1 foo bar zap a # 向集合中添加元素。如果至少成功添加了一个,返回1;没有成功添加,返回0
(integer) 1
redis> PFADD hll2 a b c foo
(integer) 1
redis> PFMERGE hll3 hll1 hll2 # 将hll1和hll2合并成hll3
"OK"
redis> PFCOUNT hll3 # 返回集合的基数
(integer) 6
Bitmaps
位存储,对于只有两种状态的数据可以用0/1表示。
redis> SETBIT mykey 7 1 # 第7位置1
(integer) 0
redis> GETBIT mykey 0 # 查询第0位
(integer) 0
redis> SET mykey "foobar"
"OK"
redis> BITCOUNT mykey 1 1 # 查询1的个数,可选项[start end]
(integer) 6
事务
ACID原则
Atomicity 原子性:事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
Consistency 一致性:事务的运行不改变数据库中数据的一致性
Isolation 隔离性:多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其他事务运行效果
Durability 持久性:事务完成后,该事务对数据库所做的更改持久保存在数据库中,不会被回滚
Redis单条命令保证原子性,但是事务不保证原子性
本质:一组命令的集合
特征:
- 一次性:在一个队列中一次性执行
- 顺序性:按照顺序执行
- 排他性:执行过程中不受干扰
Redis事务没有隔离级别的概念:所有命令在事务中没有被直接执行,发起EXEC命令时才执行
redis> MULTI # 开启事务
"OK"
redis> SET k1 v1 # 命令入队
QUEUED
redis> SET k2 v2
QUEUED
redis> GET k1
QUEUED
redis> EXEC # 执行事务
1) "OK"
2) "OK"
3) "v1"
###
redis> DISCARD # 放弃事务,事务中所有命令不会被执行
"OK"
事务中的一组命令出现:
- 编译型异常:代码有错,所有命令都不会被执行
- 运行时异常:操作报错,其他指令正常执行,错误命令报错
监控(乐观锁)
悲观锁:认为什么时候都会出问题,无论做什么都会加锁
乐观锁:认为什么时候都不会出问题,所以不会上锁。更新时先获取version并比较
redis> WATCH money # 监视money
"OK"
redis> MULTI
"OK"
redis> DECRBY money 10
QUEUED
redis> INCRBY out 10
QUEUED
redis> EXEC # 在执行前有另外一个线程修改了money,则执行失败(乐观锁)
(nil)
redis> UNWATCH # 解锁
"OK"
redis> WATCH money # 重新监视money
"OK"
Jedis
<!--导入jedis包-->
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<!--fashjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
package com.TracyLiu;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
api同上
SpringBoot整合
SpringBoot2.x之后,jedis被替换为lettuce
jedis:采用直连,多个线程操作不安全,需要使用jedis pool连接池。更像BIO
lettuce:采用netty,实例可以在多个线程中共享。更像NIO
持久化
为什么持久化?
内存数据库必须将数据库状态保存到磁盘,否则服务器进程退出时,服务器中的数据库状态也会消失
在指定时间间隔内,Redis会将内存中的数据集快照写入磁盘,恢复时将快照文件读到内存里
Redis会单独创建(fork)一个子进程进行持久化,将数据写入一个临时文件,持久化完成后用这个临时文件替换上次持久化的文件。整个过程中,主进程不进行任何IO操作,这就确保了极高的性能
如果需要大规模数据恢复,并且对数据恢复的完整性不是非常敏感,那么RDB比AOF更加高效
只做缓存的情况下,不需要持久化,因为只需要数据在服务器运行的时候存在
如果同时开启两种持久化方式,默认优先载入aof文件,因为完整性更高
RDB(Redis Database)
Redis默认是RDB方式,一般情况下不需要修改这个配置
RDB保存的文件是dump.rdb
,配置信息在redis.conf中
在主从复制中,在从机上使用以备用
默认15分钟备份一次,即
save 900 1
触发机制:
- 满足save规则
- 执行flushall
- 退出redis
恢复数据:
将rdb文件放在redis启动目录(/usr/local/bin/
),redis启动时会自动检查dump.rdb
,恢复其中数据
有时候在生产环境,会对dump.rdb
进行备份
优点:
- 适合大规模的数据恢复
- 对数据完整性要求不高
缺点
- 需要一定的时间间隔进行操作,如果redis意外宕机,最后一次持久化后的数据会丢失
- fork进程的时候,会占用一定的内容
AOF(Append Only File)
以日志形式将所有命令都记录在appendonly.aof
文件中,只许追加文件不许改写文件,恢复时将该文件执行一次
配置在redis.conf
中的appendonly no
,默认是不开启的,需要手动配置
如果aof文件有错误,可以用redis-check-aof --fix appendonly.aof
修复
默认如果appendonly.aof
文件大于64M,fork一个新的进程重写aof文件:读取数据库中当前的所有值,并用一条指令记录
优点:
appendfsync always
每一次修改都同步,文件的完整性会更好,但是消耗性能appendfsync everysec
默认每秒同步一次,可能会丢失一秒的数据appendfsync no
从不执行sync,操作系统自己同步数据,效率最高
缺点:
- 相对于数据文件来说,
appendonly.aof
远远大于dump.rdb
- aof运行效率比rdb慢,所以redis默认配置是rdb
Redis发布订阅
发布订阅(pub/sub)是一种消息通信模式:发布者(pub)将消息发送到频道,订阅者(sub)订阅频道以获取消息。比如微信、微博、关注系统
Redis客户端可以订阅任意数量的频道
redis> PSUBSCRIBE pattern [pattern ...] # 订阅一个或多个符合指定模式的频道,支持正则表达式
redis> PUBLISH channel message # 将信息发送到指定频道
redis> SUBSCRIBE channel [channel ...] # 订阅一个或多个频道
redis> UNSUBSCRIBE [channel [channel ...]] # 退订一个或多个频道
源码
publish.c
,可以了解发布订阅机制的底层实现
应用:实时消息系统、聊天室、订阅关注系统
复杂场景会用消息中间件MQ做
Redis主从复制(Master/Leader-Slave/Follower)
数据复制是单向的,只能从Master到Slave
读写分离:Master以写为主,Slave只能读(写会报错)。因为大部分情况是读操作,主从复制读写分离可以减缓服务器压力
默认情况下,每台Redis服务器都是主节点(从节点需要配置)。一个主节点可以有多个从节点,一个从节点只能有一个主节点
作用:
- 数据冗余:主从复制实现了数据的热备份
- 故障恢复:主节点出现问题时,可以由从节点提供服务
- 负载均衡:主从复制配合读写分离
- 高可用基石:主从复制是哨兵和集群的基础,所以主从复制是Redis高可用的基础
实践中,为防止宕机,至少要保证一主二从:
- 防止单点故障,并且一台服务器处理所有的请求负载,压力较大
- 单个Redis服务器内存有限。一般来说,单台Redis最大使用内存不应该超过20G
从节点配置
命令配置
redis> info replication # 查看主从信息
redis> SLAVEOF 127.0.0.1 6379 # 将当前服务器设置为127.0.0.1:6379的从节点
如果从节点服务器重启,主从关系解散
Slave成功连接到Master后会发送一个sync同步命令,Master接到命令,启动后台存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到Slave,并完成一次完全同步
全量复制:slave接受到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给Slave,完成同步
只要重新连接Master,自动执行完全同步(全量复制)
配置文件配置
修改redis.conf
中replicaof <masterip> <masterport>
另一种结构:上一个从节点是下一个主节点。如果最开始的主节点宕机,可以使用SLAVEOF NO ONE
手动将从节点变为主节点。此时如果原主节点恢复,只能重新配置主从关系
哨兵模式
开启一个独立的哨兵进程,发送命令等待Redis服务器响应。如果主机出现故障,则从Slaves中选举出新的主机
为防止一个哨兵进程出现问题,会开启多个哨兵互相监控
假如一个哨兵监控到Master宕机,此时认为是主观下线。当监控到Master宕机的哨兵达到一定数量时,哨兵之间会发起一次投票,选出新的主机。投票结果由一个哨兵发起,开始failover(故障转移)操作。切换成功后,哨兵会通过发布订阅模式,让各个哨兵把自己监控的从服务器更改主从配置,此时是客观下线。如果原Master恢复,此时自动变为新Master的Slave
优点:
- 主从切换,故障转移,系统可用性会更好
- 自动切换,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容会十分麻烦
- 配置麻烦
配置哨兵
# config/sentinel.conf
# sentinel端口
port 26379
# sentinel工作目录
dir /tmp
# 1代表多少个哨兵认为主观下线后,发起投票
sentinel monitor mymaster 127.0.0.1 6379 1
# 当在Redis实力中开启了requirepass foobared授权密码,哨兵也需要密码
sentinel auth-pass mymaster MySUPER--secret-0123passwOrd
# 多少ms没有响应,认为主观下线,默认30000
sentinel down-after-milliseconds mymaster 30000
# 指定failover时最多有多少个Slave同时对新Master同步
# 越少,完成failover用时越长;越多,则越多的Slave因为主备切换而不可用
# 可以设为1,保证每次只有一个Slave处于不能处理命令请求的状态
sentinel parallel-syncs mymaster 1
# 1.同一个sentinel对同一个Master两次failover时间间隔
# 2.Slave从一个错误的Master同步数据开始,到纠正Master的时间间隔
# 3.取消依次failover所需要的时间
# 4.failover时,配置所有Slaves指向新的Master所需最大时间。即使超过该时间,也会完成正确配置,只不过此时不按照parallel-syncs的规则配置
# 默认三分钟
sentinel failover-timeout mymaster 180000
# 通知脚本:sentinel有警告级事件发生时,会通过邮件等方式通知管理员
# 如果脚本执行后返回1,则稍后会再次执行,默认重复次数为10;如果返回2,或大于2,则不会重复执行
# 如果执行时收到中断信号或执行时间超过60s而被中断,稍后会再次执行
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
启动哨兵
redis-sentinel config/sentinel.conf
Redis缓存穿透和雪崩
缓存穿透
客户端发起读的请求,会先到缓存中查询。如果缓存中没有,就会去数据库中查询
用户很多的时候,缓存都没有击中,都去请求了持久层数据库,就会给持久层数据库带来很大压力,就相当于出现了缓存穿透(秒杀)
解决方案
-
布隆过滤器
一种数据结构。对所有可能查询的参数以hash形式存储,在控制层先进性校验,不符合则丢弃
-
缓存空对象
当存储层不命中后,即使返回的空对象也对其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取
存在两个问题:
- 缓存需要更多空间缓存更多空值的键
- 即使对空值设置了过期时间,缓存层和存储层的数据会有一段时间窗口不一致,对需要保持一致性的业务会有影响
缓存击穿
某一个key非常热点。大并发击中对这一个点进行访问,由于缓存过期,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。
解决方案
- 设置热点数据永不过期
- 分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,只能等待
缓存雪崩
在某一个时间段,缓存集中过期失效,比如某一个时间集体写入的缓存会同时失效,或者服务节点宕机。
解决方案
- redis高可用:多增设几台Redis,异地多活
- 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量
- 数据预热:正式部署前,先把可能的数据预先访问一遍,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效时间点尽量均匀