103 lines
3.7 KiB
Markdown
103 lines
3.7 KiB
Markdown
---
|
||
id: backend.redis.data-structures
|
||
title: 数据结构和常用命令:String、Hash、List、Set、ZSet、Stream
|
||
type: lesson
|
||
domain: backend
|
||
stack: [redis]
|
||
tags: [String, Hash, ZSet, Stream, 过期时间]
|
||
level: basic
|
||
est_hours: 2
|
||
related: [backend.redis.cache-patterns, backend.redis.interview]
|
||
week: [3]
|
||
order: 1
|
||
---
|
||
|
||
# 数据结构和常用命令:String、Hash、List、Set、ZSet、Stream
|
||
|
||
## 解释说明
|
||
|
||
| 类型 | 是什么 | 常见用途 | 常用命令 |
|
||
| --- | --- | --- | --- |
|
||
| String | 字符串 / 数字 | 缓存 JSON、计数器、验证码 | `SET k v EX 60 NX`、`GET`、`INCR`、`INCRBY` |
|
||
| Hash | 字段 → 值 | 对象(用户资料、购物车) | `HSET`、`HGET`、`HGETALL`、`HINCRBY` |
|
||
| List | 双端链表 | 最新列表、简单队列 | `LPUSH`、`RPOP`、`LRANGE`、`BRPOP` |
|
||
| Set | 无序去重集合 | 标签、点赞用户、共同好友 | `SADD`、`SISMEMBER`、`SINTER` |
|
||
| ZSet | 按分数排序的集合 | 排行榜、延迟队列(分数=时间) | `ZADD`、`ZINCRBY`、`ZREVRANGE`、`ZRANGEBYSCORE` |
|
||
| Stream | 追加式日志,支持消费组 | 轻量消息队列 | `XADD`、`XREADGROUP`、`XACK` |
|
||
|
||
还有 Bitmap(签到)、HyperLogLog(UV 近似计数)、Geo(附近的人)。
|
||
|
||
**关键概念**
|
||
|
||
- **单线程执行命令**:每条命令是原子的;多条命令要原子执行用 Lua 脚本或 `MULTI/EXEC`。
|
||
- **过期时间**:`EX` 秒 / `PX` 毫秒;`TTL` 查看剩余时间。过期删除 = 惰性删除(访问时检查)+ 定期抽样删除。
|
||
- **内存淘汰策略**:内存满时按 `maxmemory-policy` 淘汰,缓存场景常用 `allkeys-lru` 或 `allkeys-lfu`。
|
||
- **持久化**:RDB(定时快照)和 AOF(追加命令日志),可以同时开启。
|
||
|
||
## 案例
|
||
|
||
- 文章阅读数:`INCR article:42:views`,定时批量写回数据库。
|
||
- 积分排行榜:`ZINCRBY rank:2026-10 10 user:7`,`ZREVRANGE rank:2026-10 0 9 WITHSCORES` 取前 10。
|
||
- 登录验证码:`SET sms:13800000000 123456 EX 300 NX`,NX 保证 5 分钟内不重复发送。
|
||
|
||
## 代码示例
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
)
|
||
|
||
func main() {
|
||
url := os.Getenv("REDIS_URL") // 例如 redis://localhost:6379/0
|
||
if url == "" {
|
||
fmt.Println("未设置 REDIS_URL,跳过")
|
||
return
|
||
}
|
||
opt, err := redis.ParseURL(url)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
rdb := redis.NewClient(opt)
|
||
defer rdb.Close()
|
||
ctx := context.Background()
|
||
|
||
// 计数器
|
||
n, _ := rdb.Incr(ctx, "demo:views:42").Result()
|
||
fmt.Println("阅读数", n)
|
||
|
||
// 排行榜
|
||
rdb.ZIncrBy(ctx, "demo:rank", 30, "alice")
|
||
rdb.ZIncrBy(ctx, "demo:rank", 50, "bob")
|
||
rdb.ZIncrBy(ctx, "demo:rank", 10, "alice")
|
||
top, _ := rdb.ZRevRangeWithScores(ctx, "demo:rank", 0, 9).Result()
|
||
for i, z := range top {
|
||
fmt.Printf("第 %d 名 %v %.0f 分\n", i+1, z.Member, z.Score)
|
||
}
|
||
|
||
// 对象用 Hash
|
||
rdb.HSet(ctx, "demo:user:7", map[string]any{"name": "张三", "level": 3})
|
||
u, _ := rdb.HGetAll(ctx, "demo:user:7").Result()
|
||
fmt.Println(u["name"], u["level"])
|
||
|
||
rdb.Del(ctx, "demo:views:42", "demo:rank", "demo:user:7")
|
||
}
|
||
```
|
||
|
||
## 易错点
|
||
|
||
- **大 key**:一个 Hash / ZSet 里放几百万元素,`HGETALL`、`DEL` 会阻塞 Redis。拆分 key,删除用 `UNLINK`。
|
||
- **`KEYS *`**:线上会阻塞,用 `SCAN` 分批遍历。
|
||
- **忘记设过期时间**:缓存类 key 一律带 TTL,否则内存只增不减。
|
||
- **用 List 做可靠队列**:`RPOP` 后消费者崩溃,消息就丢了;需要可靠性用 Stream(消费组 + ACK)或专业 MQ。
|
||
- **把 Redis 当主存储**:持久化有丢数据窗口,重要数据以数据库为准。
|
||
|
||
## 面试常问
|
||
|
||
见 [Redis 面试题](interview.md):Redis 为什么快、有哪些数据结构和场景、过期和淘汰策略、持久化方式。
|