一、Redis 是什么
- 开源的内存键值数据库,数据存在内存中,读写速度极快(10万+ QPS)
- 支持数据持久化,可将内存数据保存到磁盘
- 单线程模型(6.0 后 I/O 多线程,命令执行仍单线程),避免了线程安全问题
- 常用场景:缓存、会话管理、排行榜、消息队列、分布式锁
二、数据类型
| 类型 |
说明 |
典型场景 |
| String |
最基本类型,可存字符串/数字/二进制 |
缓存、计数器、分布式锁 |
| Hash |
字段-值映射,类似 HashMap |
存储对象(用户信息) |
| List |
有序列表,支持头尾操作 |
消息队列、最新列表 |
| Set |
无序不重复集合 |
标签、共同好友、去重 |
| Sorted Set (ZSet) |
有序集合,每个元素带分数 |
排行榜、延迟队列 |
| Bitmap |
位操作 |
签到、在线状态 |
| HyperLogLog |
基数统计(近似值) |
UV 统计 |
| Stream |
消息流(5.0+) |
消息队列 |
三、常用命令
String
SET key value # 设置值
GET key # 获取值
SET key value EX 60 # 设置值,60秒过期
SETNX key value # 不存在时才设置(分布式锁基础)
INCR key # 值+1(原子操作)
DECR key # 值-1
MSET k1 v1 k2 v2 # 批量设置
MGET k1 k2 # 批量获取
List
LPUSH list a b c # 从左侧插入
RPUSH list x y z # 从右侧插入
LPOP list # 从左侧弹出
RPOP list # 从右侧弹出
LRANGE list 0 -1 # 获取所有元素
LLEN list # 获取长度
LREM list 2 a # 删除2个值为a的元素(0=全部,正=从左,负=从右)
LTRIM list 0 2 # 只保留索引0~2的元素,其余删除
Set
SADD set a b c # 添加元素
SMEMBERS set # 获取所有元素
SISMEMBER set a # 判断元素是否存在
SREM set a # 删除元素
SINTER set1 set2 # 交集
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集
Sorted Set
ZADD rank 100 "张三" # 添加元素(分数100)
ZRANGE rank 0 -1 WITHSCORES # 按分数升序获取
ZREVRANGE rank 0 9 # 按分数降序取前10(排行榜)
ZSCORE rank "张三" # 获取分数
ZRANK rank "张三" # 获取排名
ZREM rank "张三" # 删除元素
Hash
HSET user:1 name "张三" # 设置字段
HGET user:1 name # 获取字段
HMSET user:1 name "张三" age 25 # 批量设置字段
HGETALL user:1 # 获取所有字段和值
HDEL user:1 age # 删除字段
通用命令
DEL key # 删除
EXISTS key # 判断是否存在
EXPIRE key 60 # 设置过期时间(秒)
TTL key # 查看剩余过期时间
TYPE key # 查看数据类型
KEYS pattern # 查找key(生产慎用,会阻塞)
SCAN 0 MATCH user:* COUNT 100 # 渐进式遍历(推荐替代KEYS)
FLUSHDB # 清空当前库
四、发布订阅
SUBSCRIBE channel1 # 订阅频道(会阻塞等待消息)
PUBLISH channel1 "hello" # 向频道发送消息
PSUBSCRIBE news.* # 模式订阅(匹配 news.xxx 的所有频道)
UNSUBSCRIBE channel1 # 取消订阅
- 发布订阅不持久化,消息发出后如果没有订阅者则丢失
- 需要持久化消息队列建议使用 Stream 类型
五、Stream(消息队列)
XADD mystream * name "张三" age 25 # 添加消息(*表示自动生成ID)
↑ ↑ ↑ ↑ ↑
命令 Stream名 ID 字段名 字段值
XLEN mystream # 消息数量
XRANGE mystream - + # 获取所有消息
XREAD COUNT 2 BLOCK 0 STREAMS mystream 0 # 阻塞读取消息
XGROUP CREATE mystream mygroup 0 # 创建消费者组
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream > # 消费者读取
XACK mystream mygroup 消息ID # 确认消费
六、Geospatial(地理空间)
GEOADD city 116.40 39.90 "北京" # 添加地理位置(经度 纬度 名称)
GEOADD city 121.47 31.23 "上海"
GEOPOS city "北京" # 获取坐标
GEODIST city "北京" "上海" km # 计算两地距离(单位:km)
GEOSEARCH city FROMMEMBER "北京" BYRADIUS 1500 km # 搜索半径内的城市
七、HyperLogLog
PFADD uv:page1 user1 user2 user3 # 添加元素
PFCOUNT uv:page1 # 统计基数(近似UV数)
PFMERGE uv:total uv:page1 uv:page2 # 合并多个HyperLogLog
八、Bitmap(位图)
SETBIT sign:user1:202603 0 1 # 第0天签到(位偏移量)
GETBIT sign:user1:202603 0 # 查询第0天是否签到
BITCOUNT sign:user1:202603 # 统计当月签到天数
九、Bitfield(位域)
BITFIELD mykey SET u8 0 200 # 在偏移0处设置8位无符号整数值200
BITFIELD mykey GET u8 0 # 获取偏移0处的8位无符号整数
BITFIELD mykey INCRBY u8 0 1 # 自增1
十、事务
Redis 事务通过 MULTI/EXEC 实现,将多条命令打包一次性执行:
MULTI # 开启事务
SET k1 v1 # 命令入队
SET k2 v2 # 命令入队
EXEC # 执行所有入队命令
# 或
DISCARD # 放弃事务
- Redis 事务不支持回滚:如果某条命令执行失败,其他命令仍会执行
WATCH 实现乐观锁:如果被监视的 key 在事务执行前被修改,事务自动取消
WATCH key # 监视key
MULTI
SET key newvalue
EXEC # 如果key被其他客户端修改,EXEC返回nil
十一、持久化
RDB(快照)
- 按时间间隔将内存数据快照写入磁盘(dump.rdb)
- 优点:文件紧凑,恢复速度快
- 缺点:可能丢失最后一次快照后的数据
# redis.conf 配置
save 900 1 # 900秒内至少1个key变化则触发
save 300 10 # 300秒内至少10个key变化则触发
AOF(追加日志)
- 记录每次写操作到日志文件(appendonly.aof)
- 优点:数据更安全,最多丢1秒数据
- 缺点:文件比RDB大,恢复较慢
# redis.conf 配置
appendonly yes
appendfsync everysec # 每秒同步一次(推荐)
# appendfsync always # 每次写都同步(最安全但最慢)
如何选择
- 对数据安全要求高 → AOF(或 RDB + AOF 混合)
- 对性能要求高,可容忍少量丢失 → RDB
十二、缓存常见问题
| 问题 |
说明 |
解决方案 |
| 缓存穿透 |
查询不存在的数据,每次都打到数据库 |
布隆过滤器;缓存空值 |
| 缓存击穿 |
热点key过期瞬间,大量请求打到数据库 |
互斥锁;热点key永不过期 |
| 缓存雪崩 |
大量key同时过期,数据库压力骤增 |
过期时间加随机值;多级缓存 |
十三、过期与淘汰策略
过期删除策略
- 惰性删除:访问key时才检查是否过期
- 定期删除:每隔一段时间随机抽查一批key删除过期的
内存淘汰策略(内存满时)
| 策略 |
说明 |
noeviction |
不淘汰,写入报错(默认) |
allkeys-lru |
所有key中淘汰最近最少使用的(最常用) |
volatile-lru |
有过期时间的key中淘汰LRU |
allkeys-random |
随机淘汰 |
volatile-ttl |
淘汰即将过期的key |
十四、常用架构
主从复制
- 主节点写,从节点读,实现读写分离
- 从节点异步复制主节点数据
# 从节点配置
replicaof 主节点IP 6379
哨兵模式(Sentinel)
- 监控主从节点状态
- 主节点宕机时自动故障转移,选举新主节点
集群模式(Cluster)
- 数据分片存储在多个节点(16384个槽位)
- 支持水平扩展,突破单机内存限制
十五、Spring Boot 集成 Redis
// 依赖:spring-boot-starter-data-redis
@Autowired
private StringRedisTemplate redisTemplate;
// String 操作
redisTemplate.opsForValue().set("key", "value", 60, TimeUnit.SECONDS);
String val = redisTemplate.opsForValue().get("key");
// Hash 操作
redisTemplate.opsForHash().put("user:1", "name", "张三");
// 删除
redisTemplate.delete("key");