Redis 学习笔记(一):基础与数据类型

写在前面

本文是 Redis 学习笔记系列的第一篇,介绍 Redis 的核心概念、5 种基本数据类型及其应用场景,以及 .NET 客户端的使用。基于 Redis 7.x 版本。


一、Redis 是什么

1.1 定义

Redis(Remote Dictionary Server)是一个基于内存的键值存储系统,可以用作数据库、缓存和消息中间件。

1
2
3
4
5
6
核心特征:
1. 基于内存    — 读写极快(10万+ QPS)
2. 单线程      — 无并发竞争问题(Redis 6.0+ 网络 I/O 多线程)
3. 持久化      — 支持RDB和AOF两种方式,数据不丢
4. 丰富数据类型 — String、Hash、List、Set、Sorted Set 等
5. 高可用      — 主从复制、哨兵、集群

1.2 为什么 Redis 这么快

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
1. 基于内存
   内存读取速度是纳秒级,磁盘是毫秒级
   差距 10万 倍

2. 单线程 + IO 多路复用
   单线程避免了锁竞争和上下文切换
   epoll/kqueue 实现高效网络 IO
   命令排队串行执行,没有并发问题

3. 高效的数据结构
   底层用 C 实现了 SDS、跳表、压缩列表等高效结构

4. Redis 6.0+ 的多线程
   网络读写用多线程(IO 多路复用仍然是单线程)
   命令执行仍然是单线程
   只提升网络 IO 性能,不改变数据一致性模型

1.3 典型应用场景

1
2
3
4
5
6
7
8
缓存              — 最常见的用法,热点数据缓存
分布式锁           — setnx 实现互斥
计数器             — 文章阅读数、点赞数
排行榜             — Sorted Set 实现
会话存储           — 用户登录状态(Session)
限流               — 滑动窗口限流
地理位置           — 附近的人、店铺
发布订阅           — 消息通知

二、安装

2.1 Docker 安装

1
2
3
4
5
docker run -d \
  --name redis \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:7.4 redis-server --appendonly yes
1
2
3
4
--appendonly yes    — 开启 AOF 持久化

连接:
  redis-cli -h localhost -p 6379

2.2 Windows 安装

1
2
3
4
5
# 方式一:winget
winget install Redis.Redis

# 方式二:下载安装包
# https://github.com/tporadowski/redis/releases

2.3 验证

1
2
3
4
5
redis-cli ping
# 返回 PONG 表示连接成功

# 查看版本
redis-server --version

三、数据类型详解

3.1 String(字符串)

最基础的类型,可以存储字符串、数字、JSON、二进制数据(最大 512MB)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 基本操作
SET name "张三"
GET name                          # "张三"

# SET 选项
SET lock "1" EX 30 NX             # EX 过期时间(秒),NX 不存在才设置(分布式锁)
SET user:1 '{"name":"张三","age":25}'  # 存储 JSON

# 批量操作
MSET key1 "v1" key2 "v2" key3 "v3"
MGET key1 key2 key3

# 数值操作
SET counter 100
INCR counter                      # 101(原子自增)
INCRBY counter 10                 # 111
DECR counter                      # 110
INCRBYFLOAT price 0.5             # 浮点数增减

# 过期时间
SET session:token "user_data" EX 1800    # 30 分钟后过期
EXPIRE name 60                           # 设置已存在的 key 过期时间
TTL name                                 # 查看剩余过期时间(-1 永不过期,-2 已过期)

# 其他
STRLEN name                       # 字符串长度
APPEND name "先生"                 # 追加
SETEX cache:key 300 "data"        # 设置值 + 过期时间(原子操作)
1
2
3
4
5
6
应用场景:
  - 缓存(存 JSON)
  - 计数器(INCR 原子操作)
  - 分布式锁(SET NX EX)
  - Session 存储
  - 限流(INCR + EXPIRE)

3.2 Hash(哈希)

键值对集合,类似 C# 的 Dictionary<string, string>。适合存储对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 基本操作
HSET user:1 name "张三" age 25 email "zhang@test.com"
HGET user:1 name                  # "张三"
HGET user:1 age                   # "25"

# 批量操作
HMSET user:2 name "李四" age 30 role "admin"

# 获取所有字段
HGETALL user:1
# name: "张三", age: "25", email: "zhang@test.com"

# 获取指定字段
HMGET user:1 name age

# 字段操作
HDEL user:1 email                 # 删除字段
HEXISTS user:1 name               # 字段是否存在(1/0)
HLEN user:1                       # 字段数量

# 数值操作
HINCRBY user:1 age 1              # age + 1

# 批量设置字段值(Redis 7.4+)
HSET user:3 name "王五" age 28 role "user"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
应用场景:
  - 存储对象(用户信息、商品信息)
  - 比用 String 存 JSON 更灵活(可以只读/写某个字段)
  - 购物车(用户ID为key,商品ID为field,数量为value)

String 存对象 vs Hash 存对象:
  String: SET user:1 '{"name":"张三","age":25}'
          → 修改 age 要读出来改完再写回去

  Hash:   HSET user:1 name "张三" age 25
          → HINCRBY user:1 age 1(直接修改某个字段)

3.3 List(列表)

有序的字符串列表,按插入顺序排序,支持从两端操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 从左/右推入
LPUSH tasks "task3"
LPUSH tasks "task2"
LPUSH tasks "task1"               # list: [task1, task2, task3]
RPUSH tasks "task4"               # list: [task1, task2, task3, task4]

# 从左/右弹出
LPOP tasks                        # "task1"
RPOP tasks                        # "task4"

# 阻塞弹出(阻塞直到有数据或超时)
BLPOP queue:email 30              # 30 秒内等数据

# 查询
LRANGE tasks 0 -1                 # 查看所有元素
LRANGE tasks 0 2                  # 前3个元素
LLEN tasks                        # 列表长度

# 按索引操作
LINDEX tasks 0                    # 第一个元素
LSET tasks 0 "updated"            # 修改指定位置

# 裁剪(保留指定范围)
LTRIM tasks 0 99                  # 只保留前100个
1
2
3
4
5
应用场景:
  - 消息队列(LPUSH + BRPOP)
  - 最新列表(最新文章、最新动态,LTRIM 保留最新N条)
  - 栈(LPUSH + LPOP)
  - 队列(LPUSH + RPOP)

3.4 Set(集合)

无序、不重复的字符串集合。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 基本操作
SADD tags "Java" "Python" "Go"
SADD tags "Rust"

# 查询
SMEMBERS tags                     # 所有成员
SCARD tags                        # 成员数量
SISMEMBER tags "Java"             # 是否存在(1/0)

# 删除
SREM tags "Rust"
SPOP tags                         # 随机弹出一个

# 集合运算
SADD set-a "a" "b" "c" "d"
SADD set-b "c" "d" "e" "f"

SINTER set-a set-b                # 交集:c, d
SUNION set-a set-b                # 并集:a, b, c, d, e, f
SDIFF set-a set-b                 # 差集(a有b没有):a, b

# 随机获取
SRANDMEMBER tags 2                # 随机取2个(不删除)
1
2
3
4
5
应用场景:
  - 标签系统(文章标签、用户标签)
  - 去重(点赞用户列表、签到用户)
  - 交集运算(共同好友、共同关注)
  - 抽奖(SRANDMEMBER / SPOP 随机抽取)

3.5 Sorted Set(有序集合)

每个成员关联一个分数(score),按分数排序。Redis 中最实用的数据结构之一。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 添加(score + member)
ZADD leaderboard 100 "张三" 95 "李四" 88 "王五" 92 "赵六"

# 按分数排名(升序,0开始)
ZRANGE leaderboard 0 -1 WITHSCORES
# 王五:88, 赵六:92, 李四:95, 张三:100

# 降序排名
ZREVRANGE leaderboard 0 -1 WITHSCORES
# 张三:100, 李四:95, 赵六:92, 王五:88

# 取 TOP N
ZREVRANGE leaderboard 0 2 WITHSCORES    # 前三名

# 查询成员信息
ZSCORE leaderboard "张三"                # 100(查分数)
ZRANK leaderboard "张三"                 # 排名(升序,从0开始)
ZREVRANK leaderboard "张三"              # 排名(降序,从0开始)

# 增减分数
ZINCRBY leaderboard 5 "李四"            # 李四分数 +5

# 按分数范围查询
ZRANGEBYSCORE leaderboard 90 100 WITHSCORES    # 90-100 分的

# 按排名范围删除
ZREMRANGEBYRANK leaderboard 0 2         # 删除排名最低的3个

# 数量
ZCARD leaderboard                        # 总成员数
ZCOUNT leaderboard 90 100                # 分数在 90-100 之间的数量
1
2
3
4
5
应用场景:
  - 排行榜(积分榜、销量榜、热度榜)
  - 延迟队列(score = 执行时间戳)
  - 滑动窗口限流(score = 时间戳)
  - 带权重的数据(按分数排序取 TOP N)

四、其他数据类型

4.1 Bitmap(位图)

1
2
3
4
SETBIT sign:202605 0 1            # 第1天签到
SETBIT sign:202605 4 1            # 第5天签到
GETBIT sign:202605 0              # 第1天是否签到
BITCOUNT sign:202605              # 本月签到次数
1
应用场景:签到打卡、在线状态、布隆过滤器

4.2 HyperLogLog(基数统计)

1
2
3
PFADD uv:20260501 "user1" "user2" "user3"
PFADD uv:20260501 "user2" "user4"
PFCOUNT uv:20260501               # 近似去重数量:4
1
2
应用场景:UV 统计、去重计数(占用极小,12KB 可统计 2^64 个不同元素)
注意:结果有 0.81% 的误差

4.3 Stream(流)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 添加消息
XADD orders * user_id 1 product "手机" amount 3999
# 返回消息 ID:1740000000000-0

# 读取消息
XREAD COUNT 10 BLOCK 5000 STREAMS orders $
# 从最新位置读取,阻塞等待5秒

# 消费者组
XGROUP CREATE orders order-group $ MKSTREAM
XREADGROUP GROUP order-group consumer1 COUNT 1 STREAMS orders >
1
应用场景:消息队列(比 List 更强大,支持消费组、ACK、持久化)

五、通用命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 键操作
KEYS *                            # 查看所有 key(生产环境禁用,用 SCAN)
SCAN 0 MATCH user:* COUNT 100    # 安全遍历
EXISTS key1                       # key 是否存在
DEL key1 key2                     # 删除 key
UNLINK key1                       # 异步删除(大 key 用这个)
TYPE key1                         # 查看 key 类型
RENAME key1 key2                  # 重命名

# 过期时间
EXPIRE key1 60                    # 60秒后过期
PEXPIRE key1 60000                # 毫秒
TTL key1                          # 剩余秒数
PERSIST key1                      # 移除过期时间

# 数据库
SELECT 0                          # 切换数据库(0-15,默认0)
DBSIZE                            # 当前数据库 key 数量
FLUSHDB                           # 清空当前数据库
FLUSHALL                          # 清空所有数据库

# 信息查看
INFO memory                       # 内存使用情况
INFO keyspace                     # 各数据库 key 统计

六、.NET 客户端

6.1 安装

1
dotnet add package StackExchange.Redis

6.2 基本连接

1
2
3
4
5
6
7
8
9
// 连接 Redis
var connection = ConnectionMultiplexer.Connect("localhost:6379");

// 获取数据库(默认 db0)
var db = connection.GetDatabase();

// 也可以用连接字符串
var connection = ConnectionMultiplexer.Connect(
    "localhost:6379,abortConnect=false,connectTimeout=5000,syncTimeout=5000");

6.3 注册为服务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Program.cs
builder.Services.AddSingleton<IConnectionMultiplexer>(
    ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("Redis")));

// appsettings.json
{
  "ConnectionStrings": {
    "Redis": "localhost:6379,abortConnect=false"
  }
}

6.4 String 操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class RedisCacheService
{
    private readonly IDatabase _db;

    public RedisCacheService(IConnectionMultiplexer redis)
    {
        _db = redis.GetDatabase();
    }

    // 设置值
    public async Task SetAsync(string key, string value, TimeSpan? expiry = null)
    {
        await _db.StringSetAsync(key, value, expiry);
    }

    // 获取值
    public async Task<string?> GetAsync(string key)
    {
        return await _db.StringGetAsync(key);
    }

    // 设置对象(序列化为 JSON)
    public async Task SetObjectAsync<T>(string key, T obj, TimeSpan? expiry = null)
    {
        var json = JsonSerializer.Serialize(obj);
        await _db.StringSetAsync(key, json, expiry);
    }

    // 获取对象
    public async Task<T?> GetObjectAsync<T>(string key)
    {
        var value = await _db.StringGetAsync(key);
        if (value.IsNull) return default;
        return JsonSerializer.Deserialize<T>(value!);
    }

    // 删除
    public async Task DeleteAsync(string key)
    {
        await _db.KeyDeleteAsync(key);
    }

    // 是否存在
    public async Task<bool> ExistsAsync(string key)
    {
        return await _db.KeyExistsAsync(key);
    }

    // 设置过期时间
    public async Task ExpireAsync(string key, TimeSpan expiry)
    {
        await _db.KeyExpireAsync(key, expiry);
    }
}

6.5 Hash 操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 存储用户信息
public class UserRedisService
{
    private readonly IDatabase _db;

    public UserRedisService(IConnectionMultiplexer redis) => _db = redis.GetDatabase();

    public async Task SetUserAsync(int userId, string name, int age, string email)
    {
        var key = $"user:{userId}";
        await _db.HashSetAsync(key, new HashEntry[]
        {
            new("name", name),
            new("age", age),
            new("email", email)
        });
        await _db.KeyExpireAsync(key, TimeSpan.FromHours(24));
    }

    public async Task<Dictionary<string, string>> GetUserAsync(int userId)
    {
        var key = $"user:{userId}";
        var entries = await _db.HashGetAllAsync(key);
        return entries.ToDictionary(
            e => e.Name.ToString(),
            e => e.Value.ToString());
    }

    public async Task<string?> GetUserFieldAsync(int userId, string field)
    {
        return await _db.HashGetAsync($"user:{userId}", field);
    }

    public async Task IncrementUserAgeAsync(int userId)
    {
        await _db.HashIncrementAsync($"user:{userId}", "age");
    }
}

6.6 Sorted Set 操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class LeaderboardService
{
    private readonly IDatabase _db;

    public LeaderboardService(IConnectionMultiplexer redis) => _db = redis.GetDatabase();

    // 更新分数
    public async Task UpdateScoreAsync(string userId, double score)
    {
        await _db.SortedSetAddAsync("leaderboard", userId, score);
    }

    // 增加分数
    public async Task AddScoreAsync(string userId, double increment)
    {
        await _db.SortedSetIncrementAsync("leaderboard", userId, increment);
    }

    // 获取 TOP N(降序)
    public async Task<List<LeaderboardEntry>> GetTopNAsync(int count)
    {
        var entries = await _db.SortedSetRangeByRankWithScoresAsync(
            "leaderboard", 0, count - 1, Order.Descending);

        return entries.Select((e, i) => new LeaderboardEntry
        {
            Rank = i + 1,
            UserId = e.Element.ToString(),
            Score = e.Score
        }).ToList();
    }

    // 获取用户排名
    public async Task<long> GetRankAsync(string userId)
    {
        return await _db.SortedSetRankAsync("leaderboard", userId, Order.Descending) + 1;
    }
}

七、Key 命名规范

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
格式:业务:实体:标识

示例:
  user:1                     — 用户信息
  user:1:profile             — 用户详细资料
  cache:product:100          — 商品缓存
  session:token:abc123       — Session
  lock:order:12345           — 分布式锁
  leaderboard:daily:20260531 — 排行榜
  queue:email                — 邮件队列

原则:
  - 用冒号分隔层级
  - 简短有意义
  - 避免特殊字符
  - 统一前缀方便管理

八、小结

本文学习了 Redis 的基础:

  • Redis 是什么和为什么快
  • 5 种核心数据类型(String、Hash、List、Set、Sorted Set)
  • 其他类型(Bitmap、HyperLogLog、Stream)
  • 通用命令
  • .NET 客户端 StackExchange.Redis
  • Key 命名规范

下一篇将学习缓存实战:缓存模式、过期策略和三大经典问题。