Redis

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 个