写在前面
本文是 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 # 本月签到次数
|
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 命名规范
下一篇将学习缓存实战:缓存模式、过期策略和三大经典问题。