1. Redis 数据结构
# String
set `key` `value`
setnx `key` `value` # 不存在才set
setex `key` `value` `ttl`
incrby `key` `increment` # 自增# Hash 哈希表
hset `key` `field` `value` `field` `value`
hget `key` `field`
hsetnx `key` `value` # 不存在才set# List 双向链表
LPUSH `key` `element1` `element2` ... # 从左侧插入一个或多个元素
LPOP `key` # 移除左侧第一个元素,没有返回null
RPUSH `key` `element1` `element2` ...
RPOP `key`
LRANGE `key` `start` `end` # 返回start到end之间的元素# Set 无序集合
SADD `key` `member1` `member2` # 添加
SREM `key` `member1` `member2` # 删除
SCARD `key` # 返回set中元素的个数
SISMEMBER `key` `membern` # 判断是否是key成员
SmemberS `key` # 查询key的所有成员
SINNER `key1` `key2` # 获取交集
SDIFF `key1` `key2` # 求差集, key1有,key2没有
SUNION `key1` `key2` # 求并集# Zset/SortedSet ### 跳表 + Hash表, 有序集合,查询效率高
ZADD `key` `score1` `member1` `score2` `member2` ... # 添加一个或多个元素到sorted set,如果存在更新score
ZREM `key` `member` # 删除
ZSCORE `key` `member` # 获取元素的score
ZRANK `key` `member` # 获取元素排名
ZCARD `key` # 获取元素个数
ZCOUNT `key` `min` `max` # 求指定范围score的元素个数
ZINCRBY `key` `increment` `member` # 对指定member自增
ZRANGE `key` `min` `max` # 获取指定排名范围的元素
ZRANGEBYSCORE `key` `min` `max` # 获取指定score范围内的元素
2. Redis 线程模型
3. 缓存解决方案
3.1 缓存击穿、缓存雪崩、缓存穿透
缓存击穿 | 缓存雪崩 | 缓存穿透 | |
---|---|---|---|
定义 | 单个热点 key 过期时,大量并发请求瞬间击穿缓存直达数据库 | 大量缓存 key 同时失效或缓存服务宕机,导致请求洪涌压垮数据库 | 请求不存在的数据,缓存与数据库均未命中,反复穿透缓存 |
核心解决方案 | 互斥锁 | 1. 随机过期时间:基础 TTL + 随机偏移量 2. 多级缓存:本地缓存(Caffeine)+ 分布式缓存(Redis) 3. 熔断限流:Hystrix/Sentinel 保护数据库 |
1. 布隆过滤器:拦截非法请求(误判率可控) 2. 缓存空值: NULL 结果短时缓存(如 5 分钟) 3. 参数校验:过滤非法 ID(如 ID≤0) |
3.2 Redis 内存淘汰策略
- noeviction: 默认不淘汰,新增数据时直接报错
- volatile-TTL: 如果设置了过期时间,淘汰即将过期的数据
- volatile-random: 从设置了过期时间中的数据随机淘汰
- allkeys-random: 从所有数据中随机淘汰
- allkeys-lru: 在所有数据中,按照最近最少使用的数据进行淘汰
- volatile-lru: 在设置了过期时间中的数据,按照最近最少使用的数据进行淘汰。
3.3 全局唯一ID
全局唯一Id生成策略:
- UUID
- Redis自增
INCR key
- 雪花算法
- 数据库自增
redis自增策略:
- 每天一个key,方便统计订单量
- id构造是:时间戳+计数器
4. 分布式缓存
Redis 主要通过三种模式实现分布式缓存,每种模式适用于不同的场景和需求。
特性 | 主从复制 (Replication) | 哨兵模式 (Sentinel) | 集群模式 (Cluster) |
---|---|---|---|
核心目标 | 数据备份、读写分离 | 高可用、自动故障转移 | 水平扩展、高可用、数据分片 |
数据分布 | 全量复制,所有节点数据相同 | 全量复制,所有节点数据相同 | 数据分片到16384个槽,每个节点负责部分槽 |
高可用性 | 手动故障转移 | 自动故障转移 | 自动故障转移 |
扩展性 | 读扩展(添加从节点) | 读扩展(添加从节点) | 读写扩展(添加主节点) |
适用场景 | 数据备份、读多写少 | 对可用性有要求的业务 | 大数据量、高并发、需水平扩展 |
4.1 主从模式
4.1.1 主从结构
4.1.2 数据同步原理
(1)全量同步
- slave执行replicaof命令,建立连接
- slave请求数据同步
- master判断是否时第一次请求同步,是第一次请求则slave获取master数据版本信息
- slave保持版本信息
- master异步执行bgsave,生成RDB
- master发送RDB文件给slave
- slave清空本地数据,加载RDB文件
- master记录生成RDB后的所有命令到
repl_baklog
- 发送repl_baklog中的命令到slave
- 循环同步
master如何判断slave是否是第一次来同步数据的?
slave做数据同步,必须向master声明自己的replication id
和offset
, master才可以判断到底需要同步哪些数据。判断是否是第一次来,只需要判断master和slave的replid是否一致即可。
Replication Id
: 是数据集的标识,id一致说明是同一个数据集。每一个master都有唯一的replid, slave 则会继承master的replid。
offset
: 偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完全同步时也会记录当前同步的offset。如果slave的offset小于master的offset。说明slave数据落后于master,需要更新。
(4)增量同步
- slave 重启
- slave请求数据同步
- 判断replid一致,回复slave:continue
- 去repl_baklog中获取slave offset后的数据
- 发送offset后的命令
repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。
(3)主从同步优化
- 在master中配置
repl-diskless-sync yes
启用无磁盘复制,避免全量同步时写入RDB文件的磁盘IO,直接将RDB IO流直接发送给slave。 - Redis单节点上的内存占用要太大,减少RDB导致的过多磁盘IO。
- 适当提高
repl_baklog
的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步 - 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主从-从链式结构,减少master压力
(4)总结
简述全量同步和增量同步的区别?
- 全量同步:master将完整的内存数据生成RDB,发送RDB文件到slave。在生成RDB文件之后的命令操作,则记录在repl_baklog, 逐个发送给slave;
- 增量同步:slave提交自己的offset到master,master获取repl_baklog中offset偏移量之后的命令给slave
什么时候执行全量同步?
- slave初次同步
- slave的偏移量offset超过了repl_baklog的大小
什么时候执行增量同步?
- slave从故障中恢复并且offset在repl_baklog中能找到时
4.1.2. 哨兵模式
主从模式中slave宕机后恢复可以从master中同步数据,如果是master宕机呢?
哨兵的作用和原理
Redis 提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:
- 监控:Sentinel会不断检查您的master和slave是否按预期工作
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也会以新的master为主,实现主从切换。
- 通知: Sentinel充当Redis客户端的服务发现来源。当Redis集群发送故障转移时,会将最新的信息推送到Redis客户端。
(1)服务状态监控
Sentinel基于心跳机制检测服务状态,每隔一秒向集群的每个实例发送一个ping命令:
- 主观下线:如果某个sentinel节点发现某个实例未在规定的时间响应,则认为该实例主观下线。
- 客观下线:若超过指定数量(
quorum
)的sentinel都认为该实例下线,则该实例客观下线。quorum
最好超过实例数量的一半。
(2)选举master
一旦发现master故障,sentinel需要在slave中选择一个作为新的master。
- 断开时间:首先判断slave节点与master节点断开时间的长短,如果超过指定值(
down-after-milliseconds * 10
)则会排除该slave节点 - 优先级:然后判断slave节点的
slave-priority
值,越小的优先级越高,如果是0则永不参与选举。 - 偏移值
offset
:如果slave-priority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高。 - 运行id:最后判断slave节点的运行id大小,越小优先级越高(运行id是redis自动生成的id,也就是说这里随机挑选一个slave作为master)
(3)故障转移
当选中了一个slave作为新的master节点之后,需要进行故障转移。
- sentinel给备选的slave发送
slaveof no one
命令,让该节点成为master - sentinel给所有其他slave发送
slaveof 192.168.2.2 7002
命令,让其他slave成为新master的从节点,开始从新的master上同步数据。 - 最后,sentinel将故障节点标记未slave,当故障节点恢复后会自动成为新master节点的slave节点。
(4)总结
-
sentinel的三个作用是什么?
- 状态监控
- 故障修复
- 故障恢复通知
-
Sentinel如何判断一个redis实例是否健康?
- 心跳机制:基于心跳机制,每隔一秒向每个实例发送一个ping命令
- 主观判断:如果某个实例没有在规定时间内响应,则认为该节点主观下线。
- 客观判断:如果超过指定数量的sentinel都认为该节点主观下线,则该实例客观下线。
-
故障转移步骤有哪些?
- master选举
- sentinel给备选slave发送
slaveof no one
命令,让该节点成为master - sentinel给其他slave节点发送该节点的
ip + 端口
信息,让其他slave节点成为新master节点的从节点。 - sentinel把故障master标记为slave,恢复后会成为新master的slave节点。
搭建哨兵集群
RedisTemplate的哨兵模式
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新信息。Spring的RedisTemplate底层利用了lettuce实现了节点的感知和自动切换。
- 在pom文件中引入redis的starter依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在配置文件中配置sentinel信息
spring:redis:sentinel:master: mymaster # 指定master名称nodes: # 指定redis-sentinel集群信息- 192.168.56.2:27001- 192.168.56.3:27001- 192.168.56.4:27001
- 配置主从读写分离
@Bean
public LettuceClientConfigurationBuilderCustomer configurationBuilderCustomer(){return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这里ReadFrom是配置Redis的读写策略。
- MASTER:从节点读取
- MASTER_PREFERRED: 优先从master节点读取
- REPLICA: 仅从slave节点读取
- REPLICA_PREFERRED: 优先从slave节点读取