Resid学习记录
Redis 简介
Redis(Remote Dictionary Server)是一个开源的内存数据库,遵守 BSD 协议,它提供了一个高性能的键值(key-value)存储系统,常用于缓存、消息队列、会话存储等应用场景。
特点:
- 性能极高:能够支持每秒数十万次的读写操作
- 丰富的数据结构:Redis 不仅支持基本的键值存储,还提供了丰富的数据类型,包括字符串、列表、集合、哈希表、有序集合等。
- 原子性操作:Redis 的所有操作都是原子性的,这意味着操作要么完全执行,要么完全不执行。
- 持久化:Redis 支持数据的持久化,可以将内存中的数据保存到磁盘中,以便在系统重启后恢复数据
- 支持发布/订阅模式:Redis 内置了发布/订阅模式(Pub/Sub),允许客户端之间通过消息传递进行通信。
- 主从复制:Redis 支持主从复制,可以通过从节点来备份数据或分担读请求,提高数据的可用性和系统的伸缩性。
使用方式
- CLI:命令行
- API:java 或 python
- GUI:用户界面
安装
Windows
wsl 来安装,使用 wsl 来安装 linux 系统,在通过 linux 系统的方式来安装 redis
- powershell 执行
wsl --install - 进入 unbuntu 终端
- sudo apt update
- sudo apt install redis-server -y
- 启动 Redis
sudo systemctl start redis-server - 检查状态
sudo systemctl status redis-server - 设置开机自启
sudo systemctl enable redis-server - 客户端连接
redis-cli
- powershell 执行
Docker,下载 redis 镜像
- github 文件地址下载
RedisInsight
是一个 redis 的图形化操作界面,从官网安装即可
Redis 常用命令
- 设置一个键值对:SET key value
- 获取一个键的值:GET key
- 删除一个键:DEL key
- 查询一个键是否存在:EXISTS key
- 查看存在哪些键:KEYS *
- 删除所有键:FLUSHALL
- 查询键的过期时间:TTL key
- 设置过期时间:EXPIRE key 过期时间
- 设置带有过期时间的键值对:SETEX key 过期时间 value
- 只有当键不存在才设置设置键的值:SETNX key value
数据结构
列表
类似于数组
- 在列表头部添加元素:LPUSH list_name value
- 在列表尾部添加元素:RPUSH list_name value
- 查询区间列表元素:LRANGE list_name 左区间 右区间
- 删除头部元素:LPOP list_name 元素个数(默认为一)
- 删除尾部元素:RPOP list_name 元素个数
- 查看元素个数:LLEN list_name
- 删除指定区间以外的元素:LTRIM list_name 左区间 右区间
Set(无序集合)
不可以重复,支持集合运算
- 添加一个元素:SADD key value
- 删除一个元素:SREM key value
- 查看集合元素:SMEMBERS key
- 判断一个元素是否在集合中:SISMEMBER key value
SortedSet(有序集合)
每个元素会关联一个浮点数类型的分数,来对集合中的元素进行从小到大的排序
- 添加一个元素:ZADD key score value1
- 删除一个元素:ZREM key value
- 查看集合元素:ZRANGE key 左区间 右区间 withscores(添加此属性会将分数值一同输出)
- 查看某个元素的分数:ZSCORE key value
- 查看某个元素的排名:ZRANK key value
- 反转一个集合后查看排名:ZREVERANK key value
hash(哈希)
字符类型的字段和值的一个映射表,一个键值对的集合
- 添加一个键值对:HSET key field value
- 获取一个字段的值:HGET key field
- 获取映射表的所有键值对:HGETALL key
- 删除某个键值对:HDEL key field
- 判断某个键是否存在:HEXISTS key field
发布订阅
- 将消息发送一个指定的频道:publish channel
- 订阅一个频道:subscribe channel
示例:
1 | // 打开一个终端,订阅一个频道 |
存在的问题:消息无法持久化,无法记录仪历史消息
Stream(消息队列)
- 添加一个消息:XDD key (自定义 id 格式:时间搓-序列号) field value(表示自动生成一个消息 id)
- 查看消息的数量:XLEN key
- 查看消息的详细内容:XRANGE key - +
- 删除消息:XDEL key id
- 保留某条消息:XTRIM key MAXLEN 0(作用是删除所有消息)
- 读取一个消息:XREAD count 个数(表示一次读取几个消息)block 时间(表示如果没有消息阻塞多长时间)streams key(就是消息队列的名称)0(表示从哪个位置开始读取)
- 创建消费者组:XGROUP create key 组名 id
- 查看消费者组信息:XINFO groups key
- 添加组的消费者:XGROUP createconsumer key 组名 消费者名
Geospatial(地理空间)
提供了一种存储地理信息的一种数据结构
- 添加地理位置信息:GEOADD city 经度 维度 城市名字
- 获取地理信息的经纬度:GEOPOS city 城市名字
- 返回两个地理位置的距离:GENDIST city 城市名字 1 城市名字 2 距离单位
- 搜索一个地理位置的附近地理信息:GEOSEARCH city FROMMEMBER 城市名字 BYRADIUS 半径 半径单位
HyperLogLog(基数统计算法)
求出集合中不计算重复元素的个数
- 添加一个元素:PFADD key value1 value2 value3
- 查看计数的个数:PFCOUNT key
位图(Bitmap)
bit 数组:数据的下标就是偏移量(offset),值只有 0 和 1 的一个数组,支持位运算
可以将偏移量表示成一个个成员,而 0 和 1 看成两个状态,那么这样我们就可以统计像点赞收藏等等,只有两个状态的抽象了
- 设置某个偏移量的值:SETBIT key offset value
- 获取某个偏移量的值:GETBIT key offset
- 使用 set 来设置:SET key “十六进制数表示”
- 统计某个 key 中含有 1 的个数:BITCOUNT key
- 第一个出现 0 或 1 的位置:BITPOS key 0|1
位域(Bitfield)
将很多小的整数存储到一个较大的位图中。
- 设置值:BITFIELD key:id set u8(数的类型) #0(表示第一个位置) value
- 获取值:BITFIELD key:id get u8 #0
- 修改值:BITFIELD key:id incrby u32 #1 newvalue
事务
在一个事务中,在 redis 的事务中,我们不能保证事务的原子性,即不能保证事务中的命令全部执行成功,在 redis 中,执行 EXEC 命令之前,所有的命令回放入一个队列缓存起来,不会立即执行,在收到 EXEC 命令执行成功后,开启执行缓存队列中的命令。
- 开启一个事务:MUTI,开启后将事务放入一个队列中
- EXEC:提交事务
- DISCARD,取消事务,放弃执行事务块内的所有命令。
- watch key:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
- unwatch:取消 WATCH 命令对所有 key 的监视。
乐观锁(Watch 命令)
作用:监控一个或多个 key,若事务执行前这些 key 被修改,则事务取消
1 | WATCH balance # 监控balance |
分布式锁
用 SET 命令的 NX(不存在才设置)和 PX(过期时间)特性
1 | 获取锁(lock_key为锁名,uuid为唯一标识,避免释放别人的锁) |
持久化
redis 是将缓存存入到内存当中,当我们因为意外事故,而能会导致数据丢失,这是非常致命的,所以我们需要持久化。
方式一:RDB 方式,在指定时间内,将内存中的数据快照写入磁盘,是某一个时间点数据的完整副本,可以通过配置文件的 save 参数来进行配置;还可以通过 save 命令来手动触发快照;
适合来做备份,因为如果在保存前宕机,那么还是会丢失数据,在传输过程中,由于 redis 是单线程的,会使 redis 处于一个阻塞的状态所以这是不合适的。
redis 提供了一个 bgsave 的命令,会单独创建一个子进程来保存数据,但是在创建子进程时,redis 也是处于阻塞的状态。
方式二:AOF 持久化,在执行写命令时,不仅会将内容写到内存当中还会将内容写到一个文件当中,这个文件会以一个日志的形式来记录每一个写操作,当 redis 重启时,redis 会根据这个文件的内容重新来重建内存当中整个数据库的内容。
开启方式,在配置文件中将 appendonly 的值改为 yes。
主从复制
主从复制,是指将一台 redis 服务器的数据来复制到其他 redis 服务器,一个主节点可以有多个从节点,而一个从节点只能有一个主节点。数据的复制是单向的,只能有主节点到从节点,一般来说,主节点进行写操作,而从节点进行读操作,主节点会将自己的数据变化,通过异步的方式来发送到从节点。从节点接收到主节点的数据后,更新自己的数据,从而达到了数据的一致性。
配置:(配置从节点)
- 命令行方式:
- replicaof host port
- 配置文件方式(一般都用这个):
- 进入从节点配置文件
cd /opt/homebrew/etc - 将 redis.conf 复制到根目录
cp redis.conf ~ - 将配置文件复制一个起名为 redis-port(port 为从节点的端口号).conf 作为从节点配置文件
- 将端口号改为自己的从节点端口号
- 将 dbfilename 这个配置项也加上端口号
- 将 replicaof 这个配置项来指定主节点端口号
- 进入从节点配置文件
全量复制(首次复制):
复制过程:
- 从节点向主节点请求复制
- 主节点执行 bgsave 生成 RDB 文件,同时记录此期间的写命令复制到缓冲区
- 主节点发送 RDB 文件给从节点,从节点加载 RDB
- 主节点发送复制缓冲区的写命令,从节点执行(保证数据一致)。
增量复制(后续同步):
- 主节点每执行一个写命令,会记录到复制偏移量(主从节点各自维护,通过对比偏移量判断是否需要同步)。
- 从节点定期发送 REPLCONF ACK
告知主节点自己的偏移量,主节点若发现从节点落后,发送增量命令。
哨兵模式
当我们的主节点发生故障时,那么我们的主从配置就会故障,而我们还需要重新配置,这是一件麻烦且不合理的问题。
我们可以使用哨兵模式来自动监听我们的 redis 集群,哨兵会以一个独立的进程运行在 redis 集群中,来监听我们的 redis 集群,主要功能:
- 监控
- 通知,如果某个节点除了问题,哨兵会通过订阅模式来通知其它节点
- 自动故障转移,当主节点不能正常工作时,它就会开始自动转移工作,将一个从节点自动升级为主节点,然后将其它从节点指向新的主节点。
配置:
- 在 redis 集群中添加一个哨兵节点,使用
redis -sentinel sentinel.conf命令来添加。需要创建一个 sentinel.conf 文件,配置`entinel monitor master 主节点 ip 地址 端口号 1(表示只需要一个哨兵节点同意就可以进行转移)
在实际生产中,通常会使用 3 个哨兵节点来监听 redis 集群,因为每个哨兵也可能发生故障,当哨兵节点发生故障时,哨兵节点之间会通过选举的方式来选举出一个新的哨兵来监听 redis 集群。
过期键删除策略(为什么过期键不会立即消失?)
Redis 中设置过期时间的键(如 EXPIRE key 10)不会在到期时立即删除,而是通过三种策略协同处理:
- 惰性删除:只有当用户主动访问该键时,才检查是否过期,过期则删除(优点:不浪费 CPU;缺点:可能占用大量内存)。
- 定期删除:Redis 每隔一段时间(默认 100ms)随机抽查部分过期键,删除已过期的(平衡 CPU 和内存消耗)。
- 内存淘汰触发:当内存达到 maxmemory 限制时,触发内存淘汰策略,可能删除过期键(见下文)。
内存淘汰策略(内存满了怎么办?)
当 Redis 内存达到配置的 maxmemory 阈值时,会根据 maxmemory-policy 配置删除数据,共 8 种策略:
针对过期键:
- volatile-lru:删除过期键中最近最少使用的(优先保留热点过期数据)。
- volatile-lfu:删除过期键中最不经常使用的。
- volatile-ttl:删除过期键中剩余时间最短的。
- volatile-random:随机删除过期键。
针对所有键:
- allkeys-lru:删除所有键中最近最少使用的(适合缓存场景,不分是否过期)。
- allkeys-lfu:删除所有键中最不经常使用的。
- allkeys-random:随机删除所有键。
特殊策略:
- noeviction:默认策略,不删除任何键,新写入会返回错误(适合不允许丢失数据的场景)。
Redis Cluster(分片集群,解决海量数据存储)
主从和哨兵只能解决高可用,无法解决 “单节点内存上限” 问题,Redis Cluster 通过分片实现水平扩展
核心原理:将数据分散到 16384 个哈希槽(slot) 中,每个节点负责部分槽(如 3 节点集群,每节点负责~ 5461 个槽)。
键的槽位计算:slot = CRC16(key) % 16384。
集群搭建步骤:
- 启动 3 个主节点(端口 6379、6380、6381)和 3 个从节点(端口 6382、6383、6384)。
- 初始化集群:redis-cli —cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 —cluster-replicas 1(每个主节点配 1 个从节点)。
- 检查集群状态:redis-cli -c -p 6379 cluster info(-c 表示集群模式连接)。
- 故障转移:主节点故障后,其从节点自动升级为主节点,接管槽位(无需哨兵,Cluster 内置故障检测)。
缓存穿透(查询不存在的数据,穿透到 DB)
恶意请求查询不存在的 key(如 ID=-1 的用户),缓存未命中,直接访问数据库,导致 DB 压力骤增。
解决方案:
- 缓存空值:对不存在的 key,缓存一个空值(如 SET key “” EX 60),过期时间设短(1-5 分钟),避免长期占用内存。
- 布隆过滤器:将所有可能存在的 key 提前存入布隆过滤器,请求先过过滤器,不存在则直接返回(误判率极低,适合海量数据)
缓存击穿(热点 key 过期瞬间,大量请求穿透到 DB)
某热点 key(如秒杀商品)过期瞬间,大量请求同时命中缓存未命中,全部访问 DB,导致 DB 崩溃。
解决方案
- 互斥锁:缓存未命中时,用 Redis 的 SET lock_key 1 NX PX 5000 获取锁,只有获取锁的请求去 DB 查询并更新缓存,其他请求等待重试(避免并发查 DB)。
- 热点 key 永不过期:不设置过期时间,后台定时任务异步更新缓存(如每 10 分钟更新一次)。
缓存雪崩(大量 key 同时过期或缓存集群宕机)
大量 key 集中过期,或缓存集群故障,所有请求瞬间穿透到 DB,导致 DB 压垮。
解决方案:
- 过期时间加随机值:设置过期时间时加随机数(如 EXPIRE key 3600 + rand(0, 1800)),避免集中过期。
- 多级缓存:结合本地缓存(如 Caffeine)和分布式缓存(Redis),即使 Redis 宕机,本地缓存可临时抗住部分请求。
- 缓存高可用:Redis Cluster 部署(多主多从),避免单节点故障;哨兵模式自动故障转移。
- 服务熔断降级:用 Sentinel/Hystrix 等工具,当 DB 压力过大时,返回默认值
缓存与数据库一致性(如何保证缓存和 DB 数据同步?)
- Cache Aside Pattern(旁路缓存):
读:先查缓存,未命中查 DB,再回写缓存。
写:先更 DB,再删缓存(而非更新缓存,避免并发写导致的不一致)。
👉 优点:简单可靠;缺点:可能存在短暂不一致 - Write Through(写透):写操作先更缓存,缓存再同步更 DB(适合缓存和 DB 必须强一致的场景,性能较低)
- Write Back(写回):写操作只更缓存,缓存异步批量更 DB(性能高,适合非核心数据,有数据丢失风险)。
性能优化
- 避免大 key(单 key 存储过大数据)
大 key 会导致:读写耗时(网络传输慢、内存分配耗时),删除 / 过期时阻塞主线程。
解决:
- 拆分大 key(如将大 Hash 拆分为多个小 Hash,按 ID 范围分片)。
(用一个 hash 函数将 key 转换为一个数字,比如使用 crc32 hash 函数。对 key foobar 执行 crc32(foobar)会输出类似 93024922 的整数。
对这个整数取模,将其转化为 0-3 之间的数字,就可以将这个整数映射到 4 个 Redis 实例中的一个了。93024922 % 4 = 2,就是说 key foobar 应该被存到 R2 实例中。注意:取模操作是取除的余数,通常在多种编程语言中用%操作符实现。) - 用 SCAN 代替 KEYS *(KEYS 会阻塞主线程,SCAN 分批迭代)。
- 批量操作优化
Pipeline(管道):将多个命令打包发送,减少网络往返次数
批量删除:用 UNLINK 代替 DEL(UNLINK 异步删除,不阻塞主线程)