码到成功

redis技术分享——(1)

Redis 是一个高性能的内存数据库,常被用作缓存、计数器、排行榜、消息队列、分布式锁和会话存储。

很多人刚开始使用 Redis 时,只把它当作简单的 `key-value` 缓存。实际上,Redis 真正强大的地方在于它提供了多种数据结构。不同的数据结构适合不同的业务场景,选对结构往往比单纯记住命令更重要。

本文主要围绕 Redis 中几种常用数据结构展开:

- String
- Hash
- List
- Set
- Sorted Set
- Bitmap
- HyperLogLog
- Stream

## 一、String:最基础的数据结构

String 是 Redis 中最基础、最常用的数据结构。它可以存储字符串、数字、JSON 字符串、序列化后的对象等。

### 常用命令

```bash
SET user:1:name "Tom"
GET user:1:name

SET count 1
INCR count
DECR count

SETEX token:abc 3600 "user-1"
TTL token:abc
```

### 常见场景

#### 1. 缓存对象

可以把一个对象序列化成 JSON 后存入 Redis:

```bash
SET user:1 '{"id":1,"name":"Tom","age":20}'
GET user:1
```

这种方式简单直接,适合读取整个对象的场景。

#### 2. 计数器

Redis 的 `INCR` 和 `DECR` 是原子操作,非常适合做计数器:

```bash
INCR article:1001:views
INCR video:2001:likes
```

常见用法包括:

- 文章阅读量
- 视频点赞数
- 接口调用次数
- 短信发送次数

#### 3. 临时 Token

登录 token、验证码、短信码等通常都有过期时间,可以使用 `SETEX`:

```bash
SETEX login:token:abc123 7200 "user-1"
SETEX sms:code:13800000000 300 "9527"
```

这样 Redis 会自动删除过期数据,不需要业务代码额外清理。

## 二、Hash:适合存储对象字段

Hash 可以理解为一个 key 对应多个 field-value。它很适合存储对象,尤其是对象字段需要单独读写的场景。

### 常用命令

```bash
HSET user:1 name "Tom" age 20 city "Shanghai"
HGET user:1 name
HMGET user:1 name age
HGETALL user:1
HINCRBY user:1 age 1
HDEL user:1 city
```

### 常见场景

#### 1. 用户信息

```bash
HSET user:1 id 1 name "Tom" age 20 level 3
HGET user:1 name
HINCRBY user:1 level 1
```

如果只需要修改用户的某一个字段,Hash 比整个 JSON 字符串更方便。

#### 2. 商品库存信息

```bash
HSET product:1001 stock 500 price 99 sales 0
HINCRBY product:1001 stock -1
HINCRBY product:1001 sales 1
```

库存、销量、价格等字段可以独立更新。

### String 和 Hash 怎么选

如果业务经常一次性读取整个对象,可以使用 String 存 JSON。

如果业务经常修改或读取对象中的某几个字段,Hash 更合适。

例如:

- 用户详情页一次性展示完整用户信息:String 可以
- 用户积分、等级、状态频繁独立更新:Hash 更合适

## 三、List:有序列表

List 是一个按照插入顺序排列的列表,可以从两端插入或弹出元素。

### 常用命令

```bash
LPUSH queue:email "job-1"
LPUSH queue:email "job-2"
RPOP queue:email

RPUSH messages "msg-1"
RPUSH messages "msg-2"
LRANGE messages 0 -1
```

### 常见场景

#### 1. 简单消息队列

生产者写入任务:

```bash
LPUSH queue:order "order-1001"
LPUSH queue:order "order-1002"
```

消费者取出任务:

```bash
RPOP queue:order
```

也可以使用阻塞式命令:

```bash
BRPOP queue:order 10
```

`BRPOP` 在队列为空时会等待,适合消费者进程轮询任务。

#### 2. 最新消息列表

```bash
LPUSH article:comments:1001 "comment-1"
LPUSH article:comments:1001 "comment-2"
LRANGE article:comments:1001 0 9
```

这种方式适合取最新评论、最新动态等。

### 注意点

List 适合简单队列,但如果业务要求更强的消息确认、失败重试、消费组等能力,应该优先考虑 Redis Stream,或者使用专业消息队列。

## 四、Set:无序且不重复的集合

Set 用来存储不重复的元素,适合去重、标签、关注关系、集合运算等场景。

### 常用命令

```bash
SADD user:1:tags "python" "redis" "mysql"
SMEMBERS user:1:tags
SISMEMBER user:1:tags "redis"
SREM user:1:tags "mysql"
SCARD user:1:tags
```

### 常见场景

#### 1. 标签系统

```bash
SADD article:1001:tags "redis" "database" "cache"
SADD article:1002:tags "redis" "python"
```

Set 天然去重,适合保存标签。

#### 2. 用户关注关系

```bash
SADD user:1:following 2 3 4
SADD user:2:followers 1
```

判断是否关注:

```bash
SISMEMBER user:1:following 2
```

#### 3. 共同关注

```bash
SINTER user:1:following user:2:following
```

集合运算是 Set 的重要能力:

- `SINTER`:交集
- `SUNION`:并集
- `SDIFF`:差集

例如社交系统中的共同好友、共同关注、兴趣匹配,都可以用 Set 来实现。

## 五、Sorted Set:带分数的有序集合

Sorted Set,也叫 ZSet。它和 Set 一样元素不重复,但每个元素都会关联一个分数 `score`,Redis 会根据分数排序。

### 常用命令

```bash
ZADD rank:game 100 "Tom"
ZADD rank:game 200 "Jerry"
ZADD rank:game 150 "Alice"

ZRANGE rank:game 0 -1 WITHSCORES
ZREVRANGE rank:game 0 9 WITHSCORES
ZINCRBY rank:game 50 "Tom"
ZRANK rank:game "Tom"
ZREVRANK rank:game "Tom"
```

### 常见场景

#### 1. 排行榜

```bash
ZADD rank:article 100 "article-1"
ZADD rank:article 300 "article-2"
ZADD rank:article 200 "article-3"
```

获取前 10 名:

```bash
ZREVRANGE rank:article 0 9 WITHSCORES
```

增加文章热度:

```bash
ZINCRBY rank:article 1 "article-1"
```

#### 2. 延迟队列

可以把时间戳作为 score:

```bash
ZADD delay:queue 1730000000 "task-1"
ZADD delay:queue 1730000060 "task-2"
```

消费者查询当前时间之前到期的任务:

```bash
ZRANGEBYSCORE delay:queue 0 1730000000
```

这种方式适合实现轻量级延迟任务。

#### 3. 按时间排序的动态流

把发布时间作为 score:

```bash
ZADD user:1:feed 1730000000 "post-1001"
ZADD user:1:feed 1730000100 "post-1002"
```

按时间倒序取最新内容:

```bash
ZREVRANGE user:1:feed 0 19
```

## 六、Bitmap:适合大量布尔状态

Bitmap 本质上是基于 String 的位操作。它适合存储大量只有两种状态的数据,例如是否签到、是否在线、是否活跃。

### 常用命令

```bash
SETBIT sign:2026-04 0 1
GETBIT sign:2026-04 0
BITCOUNT sign:2026-04
```

### 常见场景

#### 1. 用户签到

假设一个月最多 31 天,可以用每一位表示某一天是否签到:

```bash
SETBIT user:1:sign:2026-04 0 1
SETBIT user:1:sign:2026-04 1 1
SETBIT user:1:sign:2026-04 2 0
```

统计本月签到天数:

```bash
BITCOUNT user:1:sign:2026-04
```

#### 2. 用户活跃状态

如果用用户 ID 作为偏移量,可以记录某天哪些用户活跃:

```bash
SETBIT active:2026-04-24 10001 1
GETBIT active:2026-04-24 10001
```

统计当天活跃用户数:

```bash
BITCOUNT active:2026-04-24
```

Bitmap 的优势是非常节省空间。对于大量布尔值场景,比用 Set 存用户 ID 更省内存。

## 七、HyperLogLog:基数统计

HyperLogLog 用于统计不重复元素数量,也就是基数统计。它的特点是占用空间极小,但结果有一定误差。

### 常用命令

```bash
PFADD uv:article:1001 user-1 user-2 user-3
PFADD uv:article:1001 user-2
PFCOUNT uv:article:1001
```

### 常见场景

#### 1. UV 统计

```bash
PFADD uv:site:2026-04-24 user-1
PFADD uv:site:2026-04-24 user-2
PFADD uv:site:2026-04-24 user-1
PFCOUNT uv:site:2026-04-24
```

即使同一个用户访问多次,也只会统计一次。

### 注意点

HyperLogLog 适合只关心数量、不关心具体成员的场景。

如果你需要知道“哪些用户访问过”,应该使用 Set。

如果你只需要知道“大概有多少独立用户访问过”,HyperLogLog 更节省空间。

## 八、Stream:Redis 的消息流

Stream 是 Redis 5.0 引入的数据结构,适合实现消息队列、事件流、日志流等。

相比 List,Stream 支持消息 ID、消费组、消费确认和消息追踪。

### 常用命令

```bash
XADD order:stream * orderId 1001 status created
XADD order:stream * orderId 1002 status paid

XRANGE order:stream - +
XREAD COUNT 2 STREAMS order:stream 0
```

### 消费组示例

创建消费组:

```bash
XGROUP CREATE order:stream group-1 0 MKSTREAM
```

消费者读取消息:

```bash
XREADGROUP GROUP group-1 consumer-1 COUNT 1 STREAMS order:stream >
```

确认消息:

```bash
XACK order:stream group-1 1730000000000-0
```

### 常见场景

#### 1. 订单事件流

```bash
XADD order:events * orderId 1001 event created
XADD order:events * orderId 1001 event paid
XADD order:events * orderId 1001 event shipped
```

不同消费者可以处理不同逻辑,例如:

- 发送通知
- 更新搜索索引
- 记录审计日志
- 触发积分发放

#### 2. 轻量级消息队列

Stream 比 List 更适合消息队列,因为它支持:

- 消费组
- 消息确认
- 未确认消息查询
- 多消费者协作

如果项目已经使用 Redis,且消息量不算特别大,Stream 是一个实用选择。

## 九、不同数据结构怎么选

下面是一个简单对照表:

| 数据结构 | 适合场景 |
| --- | --- |
| String | 缓存值、计数器、验证码、Token |
| Hash | 用户对象、商品对象、字段独立更新 |
| List | 简单队列、最新列表、评论列表 |
| Set | 去重、标签、关注关系、集合运算 |
| Sorted Set | 排行榜、延迟队列、按分数排序 |
| Bitmap | 签到、活跃状态、布尔标记 |
| HyperLogLog | UV、去重数量统计 |
| Stream | 消息队列、事件流、消费组 |

## 十、Redis Key 设计建议

Redis 的 key 命名建议保持清晰和统一:

```bash
业务名:对象名:对象ID:字段
```

例如:

```bash
user:1
user:1:profile
user:1:following
article:1001:views
rank:article:daily
uv:site:2026-04-24
```

几个建议:

- 使用冒号 `:` 分隔层级
- key 不要太长,但要能表达含义
- 临时数据设置过期时间
- 大 key 要拆分,避免一次读取过多数据
- 不要在生产环境使用 `KEYS *` 扫描全量 key,应该使用 `SCAN`

## 十一、总结

Redis 不是只能做缓存。它的多种数据结构可以覆盖很多常见业务需求。

简单来说:

- 存简单值,用 String
- 存对象字段,用 Hash
- 存队列或列表,用 List
- 存不重复集合,用 Set
- 做排行榜,用 Sorted Set
- 存大量布尔状态,用 Bitmap
- 统计 UV,用 HyperLogLog
- 做消息流,用 Stream

真正用好 Redis 的关键,不是记住所有命令,而是根据业务场景选择合适的数据结构。数据结构选对了,代码会更简单,性能也会更稳定。