Redis 核心知识笔记 - 第一部分:基础知识
Redis 核心知识笔记 - 第一部分:基础知识
一、Redis 概述
1.1 什么是 Redis
Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能键值存储数据库。
核心特性:
- 高性能:基于内存操作,读写速度极快(读 110000 次/s,写 81000 次/s)
- 丰富的数据类型:String、Hash、List、Set、ZSet、BitMap、HyperLogLog、GEO、Stream
- 原子性操作:所有操作都是原子性的,支持事务
- 持久化:支持 RDB 和 AOF 两种持久化机制
- 高可用:支持主从复制、哨兵、集群模式
1.2 Redis 架构图
1.3 Redis 为什么这么快?
二、五大基础数据类型
2.1 String(字符串)
底层实现:SDS(Simple Dynamic String)
特点:
- 二进制安全,可存储任何数据(文本、图片、序列化对象等)
- 最大存储 512MB
- 支持自增/自减操作
常用命令
# 基本操作
SET key value # 设置值
GET key # 获取值
SETNX key value # key不存在时设置(原子操作)
SETEX key seconds value # 设置值并指定过期时间
MSET k1 v1 k2 v2 # 批量设置
MGET k1 k2 # 批量获取
# 数值操作
INCR key # 自增1
INCRBY key increment # 自增指定值
DECR key # 自减1
DECRBY key decrement # 自减指定值
INCRBYFLOAT key increment # 浮点数自增
# 字符串操作
APPEND key value # 追加字符串
STRLEN key # 获取长度
GETRANGE key start end # 获取子串
SETRANGE key offset value # 覆盖子串
Java 代码示例
@Service
public class RedisStringService {
@Autowired
private StringRedisTemplate redisTemplate;
// 基本操作
public void basicOperations() {
// 设置值
redisTemplate.opsForValue().set("user:1001:name", "张三");
// 设置值并指定过期时间
redisTemplate.opsForValue().set("session:abc123", "userId:1001",
30, TimeUnit.MINUTES);
// 获取值
String name = redisTemplate.opsForValue().get("user:1001:name");
// SETNX(如果不存在则设置)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent("lock:order:1001", "locked", 10, TimeUnit.SECONDS);
// 批量操作
Map<String, String> map = new HashMap<>();
map.put("user:1001:age", "25");
map.put("user:1001:city", "北京");
redisTemplate.opsForValue().multiSet(map);
List<String> values = redisTemplate.opsForValue()
.multiGet(Arrays.asList("user:1001:name", "user:1001:age"));
}
// 计数器示例
public Long incrementPageView(String articleId) {
String key = "article:pv:" + articleId;
return redisTemplate.opsForValue().increment(key);
}
// 分布式自增ID
public Long generateOrderId() {
String key = "order:id:generator";
return redisTemplate.opsForValue().increment(key);
}
}
适用场景
| 场景 | 说明 | Key 设计 |
|---|---|---|
| 缓存对象 | 缓存用户信息、商品信息 | user:{id}:info |
| 计数器 | 文章阅读数、点赞数 | article:{id}:pv |
| 分布式锁 | SETNX 实现互斥 | lock:{resource} |
| 分布式 Session | 存储会话信息 | session:{sessionId} |
| 限流 | 配合 INCR 实现 | rate:{ip}:{minute} |
2.2 Hash(哈希)
底层实现:ziplist(压缩列表)或 hashtable
特点:
- 适合存储对象
- 可单独修改某个字段,避免全量序列化
- 字段数量小于 512 且值小于 64 字节时使用 ziplist
常用命令
HSET key field value # 设置单个字段
HMSET key f1 v1 f2 v2 # 设置多个字段
HGET key field # 获取单个字段
HMGET key f1 f2 # 获取多个字段
HGETALL key # 获取所有字段和值
HDEL key field # 删除字段
HINCRBY key field increment # 字段自增
HKEYS key # 获取所有字段名
HVALS key # 获取所有值
HLEN key # 获取字段数量
HEXISTS key field # 判断字段是否存在
HSETNX key field value # 字段不存在时设置
Java 代码示例
@Service
public class RedisHashService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 存储用户对象
public void saveUser(User user) {
String key = "user:" + user.getId();
Map<String, Object> userMap = new HashMap<>();
userMap.put("id", user.getId().toString());
userMap.put("name", user.getName());
userMap.put("age", user.getAge().toString());
userMap.put("email", user.getEmail());
redisTemplate.opsForHash().putAll(key, userMap);
}
// 获取用户对象
public User getUser(Long userId) {
String key = "user:" + userId;
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
if (entries.isEmpty()) {
return null;
}
User user = new User();
user.setId(Long.parseLong((String) entries.get("id")));
user.setName((String) entries.get("name"));
user.setAge(Integer.parseInt((String) entries.get("age")));
user.setEmail((String) entries.get("email"));
return user;
}
// 更新单个字段
public void updateUserAge(Long userId, Integer newAge) {
String key = "user:" + userId;
redisTemplate.opsForHash().put(key, "age", newAge.toString());
}
// 购物车示例
public void addToCart(Long userId, Long productId, Integer quantity) {
String key = "cart:" + userId;
redisTemplate.opsForHash().put(key, productId.toString(), quantity);
}
public void incrementCartItem(Long userId, Long productId, Integer delta) {
String key = "cart:" + userId;
redisTemplate.opsForHash().increment(key, productId.toString(), delta);
}
public Map<Object, Object> getCart(Long userId) {
String key = "cart:" + userId;
return redisTemplate.opsForHash().entries(key);
}
}
适用场景
| 场景 | 说明 | Key 设计 |
|---|---|---|
| 对象存储 | 用户信息、商品详情 | user:{id} product:{id} |
| 购物车 | field=商品ID, value=数量 | cart:{userId} |
| 计数统计 | 多维度计数 | stat:{date} |
2.3 List(列表)
底层实现:quicklist(快速列表,由多个 ziplist 组成的双向链表)
特点:
- 有序、可重复
- 支持双端操作
- 可实现栈、队列、阻塞队列
常用命令
# 插入操作
LPUSH key v1 v2 v3 # 左侧插入
RPUSH key v1 v2 v3 # 右侧插入
LINSERT key BEFORE|AFTER pivot value # 指定位置插入
# 弹出操作
LPOP key # 左侧弹出
RPOP key # 右侧弹出
BLPOP key timeout # 阻塞式左侧弹出
BRPOP key timeout # 阻塞式右侧弹出
RPOPLPUSH src dst # 右侧弹出左侧插入(原子操作)
# 查询操作
LRANGE key start stop # 范围查询
LINDEX key index # 按索引查询
LLEN key # 获取长度
# 修改操作
LSET key index value # 按索引设置
LTRIM key start stop # 保留指定范围
LREM key count value # 移除指定值
Java 代码示例
@Service
public class RedisListService {
@Autowired
private StringRedisTemplate redisTemplate;
// 消息队列 - 生产者
public void sendMessage(String queue, String message) {
redisTemplate.opsForList().rightPush(queue, message);
}
// 消息队列 - 消费者(阻塞式)
public String receiveMessage(String queue, long timeout) {
return redisTemplate.opsForList()
.leftPop(queue, timeout, TimeUnit.SECONDS);
}
// 最新消息列表(如微博 Timeline)
public void addTimeline(Long userId, Long messageId) {
String key = "timeline:" + userId;
// 左侧插入,保证最新的在前面
redisTemplate.opsForList().leftPush(key, messageId.toString());
// 只保留最近1000条
redisTemplate.opsForList().trim(key, 0, 999);
}
public List<String> getTimeline(Long userId, int start, int end) {
String key = "timeline:" + userId;
return redisTemplate.opsForList().range(key, start, end);
}
// 实现栈(LIFO)
public void push(String stackName, String value) {
redisTemplate.opsForList().leftPush(stackName, value);
}
public String pop(String stackName) {
return redisTemplate.opsForList().leftPop(stackName);
}
// 实现队列(FIFO)
public void enqueue(String queueName, String value) {
redisTemplate.opsForList().rightPush(queueName, value);
}
public String dequeue(String queueName) {
return redisTemplate.opsForList().leftPop(queueName);
}
}
适用场景
| 场景 | 说明 | 实现方式 |
|---|---|---|
| 消息队列 | 简单的生产者消费者模型 | LPUSH + BRPOP |
| 最新列表 | 最新文章、最新消息 | LPUSH + LTRIM + LRANGE |
| 栈 | LIFO 结构 | LPUSH + LPOP |
| 队列 | FIFO 结构 | RPUSH + LPOP |
2.4 Set(集合)
底层实现:intset(整数集合)或 hashtable
特点:
- 无序、唯一
- 支持集合运算(交、并、差)
- 元素都是整数且数量小于 512 时使用 intset
常用命令
# 基本操作
SADD key member [member ...] # 添加成员
SREM key member [member ...] # 移除成员
SMEMBERS key # 获取所有成员
SISMEMBER key member # 判断是否是成员
SCARD key # 获取成员数量
SRANDMEMBER key [count] # 随机获取成员
SPOP key [count] # 随机弹出成员
# 集合运算
SINTER key1 key2 # 交集
SUNION key1 key2 # 并集
SDIFF key1 key2 # 差集(key1中有,key2中没有)
SINTERSTORE dst key1 key2 # 交集并存储
SUNIONSTORE dst key1 key2 # 并集并存储
SDIFFSTORE dst key1 key2 # 差集并存储
Java 代码示例
@Service
public class RedisSetService {
@Autowired
private StringRedisTemplate redisTemplate;
// 标签系统
public void addTags(Long articleId, String... tags) {
String key = "article:tags:" + articleId;
redisTemplate.opsForSet().add(key, tags);
}
public Set<String> getTags(Long articleId) {
String key = "article:tags:" + articleId;
return redisTemplate.opsForSet().members(key);
}
// 共同关注(交集)
public Set<String> getCommonFollowing(Long userId1, Long userId2) {
String key1 = "user:following:" + userId1;
String key2 = "user:following:" + userId2;
return redisTemplate.opsForSet().intersect(key1, key2);
}
// 可能认识的人(差集)
public Set<String> getRecommendFollows(Long userId, Long friendId) {
String myFollowing = "user:following:" + userId;
String friendFollowing = "user:following:" + friendId;
// 朋友关注的人中,我没有关注的
return redisTemplate.opsForSet().difference(friendFollowing, myFollowing);
}
// 抽奖系统
public void joinLottery(Long activityId, Long userId) {
String key = "lottery:" + activityId;
redisTemplate.opsForSet().add(key, userId.toString());
}
public List<String> drawLottery(Long activityId, int winnerCount) {
String key = "lottery:" + activityId;
// 随机弹出,中奖者不再参与后续抽奖
return redisTemplate.opsForSet().pop(key, winnerCount);
}
// 点赞功能
public void like(Long articleId, Long userId) {
String key = "article:likes:" + articleId;
redisTemplate.opsForSet().add(key, userId.toString());
}
public void unlike(Long articleId, Long userId) {
String key = "article:likes:" + articleId;
redisTemplate.opsForSet().remove(key, userId.toString());
}
public Boolean isLiked(Long articleId, Long userId) {
String key = "article:likes:" + articleId;
return redisTemplate.opsForSet().isMember(key, userId.toString());
}
public Long getLikeCount(Long articleId) {
String key = "article:likes:" + articleId;
return redisTemplate.opsForSet().size(key);
}
}
适用场景
| 场景 | 说明 | 实现方式 |
|---|---|---|
| 标签系统 | 文章标签、用户标签 | SADD / SMEMBERS |
| 共同好友 | 社交网络 | SINTER |
| 抽奖活动 | 随机抽取 | SRANDMEMBER / SPOP |
| 点赞/收藏 | 唯一性保证 | SADD / SREM / SISMEMBER |
| 黑名单 | 快速判断 | SISMEMBER |
2.5 ZSet(有序集合)
底层实现:ziplist 或 skiplist(跳表)+ hashtable
特点:
- 有序、唯一
- 每个元素关联一个 score(分数)用于排序
- 适合排行榜场景
常用命令
# 基本操作
ZADD key score member [score member ...] # 添加成员
ZREM key member [member ...] # 移除成员
ZSCORE key member # 获取分数
ZCARD key # 获取成员数量
ZINCRBY key increment member # 增加分数
# 范围查询
ZRANGE key start stop [WITHSCORES] # 按索引范围(升序)
ZREVRANGE key start stop [WITHSCORES] # 按索引范围(降序)
ZRANGEBYSCORE key min max [WITHSCORES] # 按分数范围(升序)
ZREVRANGEBYSCORE key max min [WITHSCORES] # 按分数范围(降序)
# 排名查询
ZRANK key member # 获取排名(升序)
ZREVRANK key member # 获取排名(降序)
# 其他操作
ZCOUNT key min max # 分数范围内的成员数量
ZREMRANGEBYRANK key start stop # 按排名范围移除
ZREMRANGEBYSCORE key min max # 按分数范围移除
Java 代码示例
@Service
public class RedisZSetService {
@Autowired
private StringRedisTemplate redisTemplate;
// 排行榜系统
public void addScore(String leaderboard, String userId, double score) {
redisTemplate.opsForZSet().add(leaderboard, userId, score);
}
public void incrementScore(String leaderboard, String userId, double delta) {
redisTemplate.opsForZSet().incrementScore(leaderboard, userId, delta);
}
// 获取 Top N
public Set<ZSetOperations.TypedTuple<String>> getTopN(String leaderboard, int n) {
return redisTemplate.opsForZSet()
.reverseRangeWithScores(leaderboard, 0, n - 1);
}
// 获取用户排名
public Long getUserRank(String leaderboard, String userId) {
Long rank = redisTemplate.opsForZSet().reverseRank(leaderboard, userId);
return rank != null ? rank + 1 : null; // 转为1开始的排名
}
// 获取用户分数
public Double getUserScore(String leaderboard, String userId) {
return redisTemplate.opsForZSet().score(leaderboard, userId);
}
// 延迟队列实现
public void addDelayTask(String queue, String taskId, long executeTime) {
// score 为执行时间戳
redisTemplate.opsForZSet().add(queue, taskId, executeTime);
}
public Set<String> getExpiredTasks(String queue) {
long now = System.currentTimeMillis();
// 获取所有已到期的任务
return redisTemplate.opsForZSet().rangeByScore(queue, 0, now);
}
public void removeTask(String queue, String taskId) {
redisTemplate.opsForZSet().remove(queue, taskId);
}
// 滑动窗口限流
public boolean isAllowed(String userId, String action, int maxRequests, int windowSeconds) {
String key = "rate:" + action + ":" + userId;
long now = System.currentTimeMillis();
long windowStart = now - windowSeconds * 1000L;
// 使用 Lua 脚本保证原子性
String script =
"redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1]) " +
"local count = redis.call('ZCARD', KEYS[1]) " +
"if count < tonumber(ARGV[2]) then " +
" redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3]) " +
" redis.call('EXPIRE', KEYS[1], ARGV[4]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
String.valueOf(windowStart),
String.valueOf(maxRequests),
String.valueOf(now),
String.valueOf(windowSeconds)
);
return result != null && result == 1;
}
}
适用场景
| 场景 | 说明 | score 含义 |
|---|---|---|
| 排行榜 | 游戏积分榜、热搜榜 | 分数/热度 |
| 延迟队列 | 定时任务调度 | 执行时间戳 |
| 滑动窗口限流 | API 限流 | 请求时间戳 |
| 权重队列 | 带优先级的任务队列 | 优先级 |
| 时间线 | 按时间排序的动态 | 发布时间 |
三、数据类型选择决策图
四、Key 设计规范
4.1 命名规范
业务模块:对象类型:对象ID:属性
示例:
user:info:1001 # 用户1001的信息
user:session:abc123 # 会话abc123
order:detail:202401001 # 订单详情
article:pv:1001 # 文章1001的阅读量
cart:user:1001 # 用户1001的购物车
rate:api:getUserInfo:127.0.0.1 # API限流
4.2 设计原则
| 原则 | 说明 |
|---|---|
| 可读性 | 使用冒号分隔,便于理解和管理 |
| 简洁性 | 在保证可读的前提下尽量简短 |
| 唯一性 | Key 必须能唯一标识数据 |
| 避免特殊字符 | 不使用空格、换行等特殊字符 |
| 设置过期时间 | 尽量为每个 Key 设置 TTL |
Caution
避免 Big Key:单个 String 不超过 10KB,集合类型元素不超过 10000 个