Redis 入门教程——从安装到缓存实战

Redis 入门教程——从安装到缓存实战

作者: CaoZH
日期: 2024-04-15
本文为原创教程


Redis 是最流行的内存数据库,2024 年 Stack Overflow 调查显示超过 40% 的开发者在使用 Redis。作为后端开发者,Redis 是缓存、会话管理、消息队列的首选方案。

本文从安装开始,涵盖 Redis 的核心数据结构和典型使用场景。

一、安装 Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Ubuntu/Debian
sudo apt update
sudo apt install -y redis-server

# 启动
sudo systemctl start redis
sudo systemctl enable redis

# 验证
redis-cli ping
# 输出:PONG

# macOS
brew install redis
brew services start redis

配置文件

1
2
# /etc/redis/redis.conf 常用配置
sudo vim /etc/redis/redis.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 绑定地址(生产环境不要用 0.0.0.0)
bind 127.0.0.1

# 端口
port 6379

# 密码(生产环境必须设置)
requirepass yourpassword

# 持久化
save 900 1 # 900秒内至少1个key改变
save 300 10 # 300秒内至少10个key改变
save 60 10000 # 60秒内至少10000个key改变

# 最大内存(设置后需要配置淘汰策略)
maxmemory 256mb
maxmemory-policy allkeys-lru

二、核心数据结构

String(字符串)

1
2
3
4
5
6
7
8
9
# 最基础的类型,存任何字符串
SET name "张三"
GET name # "张三"
SET counter 100
INCR counter # 101
INCRBY counter 5 # 106
DECR counter # 105
SETEX token 3600 "abc123" # 设置带过期时间的 key
TTL token # 查看剩余秒数

使用场景: 缓存 HTML 片段、计数器、分布式锁、Session

Hash(哈希)

1
2
3
4
5
6
7
# 存储对象
HSET user:1001 name "张三" age 28 email "zhangsan@test.com"
HGET user:1001 name # "张三"
HGETALL user:1001 # 获取所有字段
HMSET user:1002 name "李四" age 30
HDEL user:1001 email
HLEN user:1001 # 字段数量

使用场景: 用户信息、商品详情、配置项

List(列表)

1
2
3
4
5
6
# 双向链表
LPUSH logs "error: timeout" # 从左侧插入
RPUSH logs "info: login" # 从右侧插入
LPOP logs # 从左侧弹出
LLEN logs # 列表长度
LRANGE logs 0 -1 # 获取所有元素

使用场景: 消息队列、最新消息列表、操作日志

Set(集合)

1
2
3
4
5
6
7
# 无序不重复集合
SADD tags:1 "java" "python" "vue"
SMEMBERS tags:1 # 所有成员
SISMEMBER tags:1 "java" # 是否包含
SINTER set1 set2 # 交集
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集

使用场景: 标签系统、好友关系、权限管理

Sorted Set(有序集合)

1
2
3
4
5
6
# 带权重的有序集合
ZADD leaderboard 100 "user1" 90 "user2" 80 "user3"
ZINCRBY leaderboard 10 "user2" # 加10分
ZREVRANGE leaderboard 0 2 WITHSCORES # 前三名
ZRANK leaderboard "user2" # 排名
ZSCORE leaderboard "user2" # 分数

使用场景: 排行榜、延迟队列、限流

三、Java 中使用 Redis

Spring Boot 集成

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
# application.yml
spring:
redis:
host: localhost
port: 6379
password: yourpassword
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0

RedisTemplate 使用

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
@Service
public class UserService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 缓存用户信息
public User getUserById(Long id) {
String key = "user:" + id;

// 1. 查缓存
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}

// 2. 查数据库
user = userMapper.selectById(id);
if (user != null) {
// 3. 写入缓存,过期时间 1 小时
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
}

return user;
}

// 更新用户时清除缓存
public void updateUser(User user) {
userMapper.updateById(user);
redisTemplate.delete("user:" + user.getId());
}
}

使用注解(@Cacheable)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class ProductService {

@Cacheable(value = "product", key = "#id", unless = "#result == null")
public Product getById(Long id) {
return productMapper.selectById(id);
}

@CacheEvict(value = "product", key = "#product.id")
public void update(Product product) {
productMapper.updateById(product);
}

@CacheEvict(value = "product", allEntries = true)
public void clearAll() {
// 清理所有商品缓存
}
}

四、实战场景

1. 分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
public boolean tryLock(String key, String value, long expireSeconds) {
return Boolean.TRUE.equals(
redisTemplate.opsForValue()
.setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS)
);
}

public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key), value);
}

2. 接口限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public boolean rateLimit(String key, int maxCount, int windowSeconds) {
String script = """
local key = KEYS[1]
local now = redis.call('TIME')[1]
local window = tonumber(ARGV[1])
local max = tonumber(ARGV[2])

redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)

if count < max then
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('EXPIRE', key, window)
return 1
end
return 0
""";

return Long.valueOf(1).equals(
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key), windowSeconds, maxCount)
);
}

3. 缓存穿透保护

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
public User getUserById(Long id) {
String key = "user:" + id;

// 1. 查缓存
Object cache = redisTemplate.opsForValue().get(key);

// 2. 查到了空值标记,直接返回 null
if (cache instanceof String && "NULL".equals(cache)) {
return null;
}

if (cache != null) {
return (User) cache;
}

// 3. 查数据库
User user = userMapper.selectById(id);

// 4. 缓存结果或空值标记
if (user == null) {
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
} else {
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
}

return user;
}

五、常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
# 通用
KEYS * # 列出所有 key(生产环境慎用)
EXISTS key # 是否存在
TYPE key # 类型
TTL key # 剩余过期时间
DEL key1 key2 # 删除
FLUSHALL # 清空所有(⚠️ 慎用)

# 性能与监控
INFO # 服务器信息
MONITOR # 实时监控命令
SLOWLOG GET 10 # 查看慢查询
CLIENT LIST # 查看连接

六、总结

数据类型 使用场景 示例
✅ String 缓存、计数器、验证码 SET user:1 data
✅ Hash 对象存储 HSET user:1 name "张三"
✅ List 消息队列、最新列表 LPUSH queue task
✅ Set 标签、去重 SADD tags vue
✅ Sorted Set 排行榜、延时队列 ZADD rank 100 user1

首发于 CaoZH 的笔记