🌟 引言

Redis作为当今最流行的内存数据库和缓存解决方案,在现代Web应用中扮演着至关重要的角色。从简单的键值存储到复杂的分布式缓存架构,Redis以其卓越的性能和丰富的数据结构赢得了开发者的青睐。本文将带你深入了解Redis的实战应用,从基础配置到高级优化策略,构建高性能缓存系统。

🚀 Redis基础架构与配置

1. 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// RedisTemplate配置类
@Configuration
@EnableCaching
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);

// 设置序列化器
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LazyPropertyAccessor.Impl.PROPERTY, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);

// 设置key和value的序列化规则
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jackson2JsonRedisSerializer);

template.afterPropertiesSet();
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)))
.disableCachingNullValues();

return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}

// Redis基础操作服务
@Service
@Slf4j
public class RedisService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

// String操作
public void setString(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}

public Object getString(String key) {
return redisTemplate.opsForValue().get(key);
}

public Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}

// Hash操作
public void setHash(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}

public Object getHash(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}

public Map<Object, Object> getAllHash(String key) {
return redisTemplate.opsForHash().entries(key);
}

// List操作
public Long leftPush(String key, Object value) {
return redisTemplate.opsForList().leftPush(key, value);
}

public Object rightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}

public List<Object> getList(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}

// Set操作
public Long addSet(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}

public Set<Object> getSet(String key) {
return redisTemplate.opsForSet().members(key);
}

public Boolean isMemberSet(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}

// ZSet操作
public Boolean addZSet(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}

public Set<Object> rangeZSet(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}

public Set<ZSetOperations.TypedTuple<Object>> rangeWithScores(String key, long start, long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}

// 通用操作
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}

public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}

public Boolean delete(String key) {
return redisTemplate.delete(key);
}

public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}

public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
}

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# application.yml - Redis配置
spring:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 3000ms

# 连接池配置
lettuce:
pool:
max-active: 20 # 最大连接数
max-wait: -1ms # 最大等待时间
max-idle: 10 # 最大空闲连接
min-idle: 5 # 最小空闲连接
shutdown-timeout: 100ms

# 集群配置(如果使用集群)
cluster:
nodes:
- 192.168.1.100:7000
- 192.168.1.100:7001
- 192.168.1.100:7002
- 192.168.1.101:7000
- 192.168.1.101:7001
- 192.168.1.101:7002
max-redirects: 3

# 哨兵配置(如果使用哨兵)
sentinel:
master: mymaster
nodes:
- 192.168.1.100:26379
- 192.168.1.101:26379
- 192.168.1.102:26379

# redis.conf 关键配置项
# maxmemory 2gb # 最大内存限制
# maxmemory-policy allkeys-lru # 内存淘汰策略
# save 900 1 # 持久化配置
# save 300 10
# save 60 10000
# appendonly yes # AOF持久化
# appendfsync everysec # AOF同步策略

2. 缓存策略与模式

缓存穿透、击穿、雪崩解决方案

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// 缓存管理服务
@Service
@Slf4j
public class CacheManager {

@Autowired
private RedisService redisService;

@Autowired
private UserService userService;

private static final String USER_CACHE_PREFIX = "user:";
private static final String LOCK_PREFIX = "lock:";
private static final String NULL_VALUE = "NULL";
private static final long CACHE_TTL = 30 * 60; // 30分钟
private static final long LOCK_TTL = 10; // 10秒

/**
* 解决缓存穿透 - 布隆过滤器 + 空值缓存
*/
public User getUserById(Long userId) {
String key = USER_CACHE_PREFIX + userId;

// 1. 从缓存获取
Object cached = redisService.getString(key);
if (cached != null) {
if (NULL_VALUE.equals(cached)) {
return null; // 空值缓存
}
return (User) cached;
}

// 2. 布隆过滤器检查(可选)
if (!bloomFilter.mightContain(userId)) {
// 缓存空值,防止缓存穿透
redisService.setString(key, NULL_VALUE, 5 * 60, TimeUnit.SECONDS);
return null;
}

// 3. 查询数据库
User user = userService.findById(userId);

// 4. 更新缓存
if (user != null) {
redisService.setString(key, user, CACHE_TTL, TimeUnit.SECONDS);
} else {
// 缓存空值,较短过期时间
redisService.setString(key, NULL_VALUE, 5 * 60, TimeUnit.SECONDS);
}

return user;
}

/**
* 解决缓存击穿 - 分布式锁
*/
public User getUserByIdWithLock(Long userId) {
String key = USER_CACHE_PREFIX + userId;
String lockKey = LOCK_PREFIX + userId;

// 1. 从缓存获取
Object cached = redisService.getString(key);
if (cached != null && !NULL_VALUE.equals(cached)) {
return (User) cached;
}

// 2. 获取分布式锁
String lockValue = UUID.randomUUID().toString();
boolean lockAcquired = redisService.setIfAbsent(lockKey, lockValue, LOCK_TTL, TimeUnit.SECONDS);

if (lockAcquired) {
try {
// 双重检查
cached = redisService.getString(key);
if (cached != null && !NULL_VALUE.equals(cached)) {
return (User) cached;
}

// 查询数据库
User user = userService.findById(userId);

// 更新缓存
if (user != null) {
redisService.setString(key, user, CACHE_TTL, TimeUnit.SECONDS);
} else {
redisService.setString(key, NULL_VALUE, 5 * 60, TimeUnit.SECONDS);
}

return user;

} finally {
// 释放锁
releaseLock(lockKey, lockValue);
}
} else {
// 等待并重试
try {
Thread.sleep(100);
return getUserByIdWithLock(userId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
}

/**
* 解决缓存雪崩 - 随机过期时间
*/
public void setUserCacheWithRandomTTL(Long userId, User user) {
String key = USER_CACHE_PREFIX + userId;

// 基础TTL + 随机时间(0-300秒)
long randomTTL = CACHE_TTL + new Random().nextInt(300);

redisService.setString(key, user, randomTTL, TimeUnit.SECONDS);
}

/**
* 缓存预热
*/
@PostConstruct
public void warmUpCache() {
log.info("开始缓存预热...");

// 预热热点数据
List<Long> hotUserIds = userService.getHotUserIds();
for (Long userId : hotUserIds) {
User user = userService.findById(userId);
if (user != null) {
setUserCacheWithRandomTTL(userId, user);
}
}

log.info("缓存预热完成,预热用户数量: {}", hotUserIds.size());
}

/**
* 释放分布式锁
*/
private void releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";

redisService.execute(script, Collections.singletonList(lockKey), lockValue);
}
}

// 布隆过滤器配置
@Configuration
public class BloomFilterConfig {

@Bean
public BloomFilter<Long> userBloomFilter() {
// 预期插入100万个元素,误判率0.01%
return BloomFilter.create(Funnels.longFunnel(), 1000000, 0.0001);
}

@EventListener
public void initBloomFilter(ContextRefreshedEvent event) {
BloomFilter<Long> bloomFilter = userBloomFilter();

// 初始化布隆过滤器
List<Long> allUserIds = userService.getAllUserIds();
for (Long userId : allUserIds) {
bloomFilter.put(userId);
}

log.info("布隆过滤器初始化完成,用户数量: {}", allUserIds.size());
}
}

🔧 高级缓存模式与实践

1. 多级缓存架构

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 多级缓存管理器
@Component
@Slf4j
public class MultiLevelCacheManager {

@Autowired
private RedisService redisService;

// 本地缓存(Caffeine)
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build();

// 分布式缓存(Redis)
private static final String CACHE_PREFIX = "multi:";
private static final long REDIS_TTL = 30 * 60; // 30分钟

/**
* 多级缓存获取
*/
public <T> T get(String key, Class<T> type, Supplier<T> dataLoader) {
// L1: 本地缓存
Object cached = localCache.getIfPresent(key);
if (cached != null) {
log.debug("命中本地缓存: {}", key);
return type.cast(cached);
}

// L2: Redis缓存
String redisKey = CACHE_PREFIX + key;
cached = redisService.getString(redisKey);
if (cached != null) {
log.debug("命中Redis缓存: {}", key);
// 回写本地缓存
localCache.put(key, cached);
return type.cast(cached);
}

// L3: 数据源
log.debug("从数据源加载: {}", key);
T data = dataLoader.get();
if (data != null) {
// 写入Redis缓存
redisService.setString(redisKey, data, REDIS_TTL, TimeUnit.SECONDS);
// 写入本地缓存
localCache.put(key, data);
}

return data;
}

/**
* 缓存更新
*/
public void put(String key, Object value) {
String redisKey = CACHE_PREFIX + key;

// 更新Redis缓存
redisService.setString(redisKey, value, REDIS_TTL, TimeUnit.SECONDS);

// 更新本地缓存
localCache.put(key, value);

// 通知其他节点清除本地缓存
notifyOtherNodes(key);
}

/**
* 缓存删除
*/
public void evict(String key) {
String redisKey = CACHE_PREFIX + key;

// 删除Redis缓存
redisService.delete(redisKey);

// 删除本地缓存
localCache.invalidate(key);

// 通知其他节点清除本地缓存
notifyOtherNodes(key);
}

/**
* 通知其他节点清除本地缓存
*/
private void notifyOtherNodes(String key) {
String channel = "cache:invalidate";
CacheInvalidateMessage message = new CacheInvalidateMessage(key, System.currentTimeMillis());

redisService.convertAndSend(channel, message);
}

/**
* 监听缓存失效消息
*/
@EventListener
public void handleCacheInvalidate(CacheInvalidateMessage message) {
// 清除本地缓存
localCache.invalidate(message.getKey());
log.debug("收到缓存失效通知: {}", message.getKey());
}

/**
* 获取缓存统计信息
*/
public CacheStats getLocalCacheStats() {
return localCache.stats();
}
}

// 缓存失效消息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CacheInvalidateMessage {
private String key;
private long timestamp;
}

// Redis消息监听配置
@Configuration
public class RedisMessageConfig {

@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory,
MultiLevelCacheManager cacheManager) {

RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);

// 监听缓存失效消息
container.addMessageListener(new MessageListener() {
@Override
public void onMessage(Message message, byte[] pattern) {
try {
String body = new String(message.getBody());
CacheInvalidateMessage msg = JSON.parseObject(body, CacheInvalidateMessage.class);
cacheManager.handleCacheInvalidate(msg);
} catch (Exception e) {
log.error("处理缓存失效消息失败", e);
}
}
}, new ChannelTopic("cache:invalidate"));

return container;
}
}

2. 缓存一致性保证

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// 缓存一致性管理
@Service
@Slf4j
public class CacheConsistencyManager {

@Autowired
private RedisService redisService;

@Autowired
private UserRepository userRepository;

@Autowired
private MultiLevelCacheManager cacheManager;

/**
* Cache-Aside模式 - 更新数据
*/
@Transactional
public User updateUser(User user) {
try {
// 1. 更新数据库
User updatedUser = userRepository.save(user);

// 2. 删除缓存
String cacheKey = "user:" + user.getId();
cacheManager.evict(cacheKey);

log.info("用户更新成功,已清除缓存: {}", cacheKey);
return updatedUser;

} catch (Exception e) {
log.error("用户更新失败", e);
throw e;
}
}

/**
* Write-Through模式 - 同时更新缓存和数据库
*/
@Transactional
public User updateUserWriteThrough(User user) {
try {
// 1. 更新数据库
User updatedUser = userRepository.save(user);

// 2. 更新缓存
String cacheKey = "user:" + user.getId();
cacheManager.put(cacheKey, updatedUser);

log.info("用户更新成功,已更新缓存: {}", cacheKey);
return updatedUser;

} catch (Exception e) {
log.error("用户更新失败", e);
// 回滚缓存
String cacheKey = "user:" + user.getId();
cacheManager.evict(cacheKey);
throw e;
}
}

/**
* 延时双删策略
*/
@Transactional
public User updateUserDelayedDoubleDelete(User user) {
String cacheKey = "user:" + user.getId();

try {
// 1. 删除缓存
cacheManager.evict(cacheKey);

// 2. 更新数据库
User updatedUser = userRepository.save(user);

// 3. 延时删除缓存
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000); // 延时1秒
cacheManager.evict(cacheKey);
log.info("延时删除缓存完成: {}", cacheKey);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("延时删除缓存被中断", e);
}
});

return updatedUser;

} catch (Exception e) {
log.error("用户更新失败", e);
throw e;
}
}

/**
* 基于版本号的乐观锁更新
*/
@Transactional
public User updateUserWithVersion(User user) {
String cacheKey = "user:" + user.getId();
String lockKey = "lock:user:" + user.getId();
String lockValue = UUID.randomUUID().toString();

// 获取分布式锁
boolean lockAcquired = redisService.setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
if (!lockAcquired) {
throw new RuntimeException("获取锁失败,请稍后重试");
}

try {
// 1. 检查版本号
User currentUser = userRepository.findById(user.getId()).orElse(null);
if (currentUser == null) {
throw new RuntimeException("用户不存在");
}

if (!currentUser.getVersion().equals(user.getVersion())) {
throw new RuntimeException("数据已被其他用户修改,请刷新后重试");
}

// 2. 更新版本号
user.setVersion(user.getVersion() + 1);

// 3. 更新数据库
User updatedUser = userRepository.save(user);

// 4. 删除缓存
cacheManager.evict(cacheKey);

return updatedUser;

} finally {
// 释放锁
releaseLock(lockKey, lockValue);
}
}

/**
* 基于Canal的数据同步
*/
@Component
public static class CanalDataSyncHandler {

@Autowired
private MultiLevelCacheManager cacheManager;

/**
* 监听数据库变更事件
*/
@EventListener
public void handleDatabaseChange(DatabaseChangeEvent event) {
if ("user".equals(event.getTableName())) {
String cacheKey = "user:" + event.getPrimaryKey();

switch (event.getEventType()) {
case INSERT:
case UPDATE:
// 删除缓存,让下次查询时重新加载
cacheManager.evict(cacheKey);
break;
case DELETE:
// 删除缓存
cacheManager.evict(cacheKey);
break;
}

log.info("处理数据库变更事件: {} - {}", event.getEventType(), cacheKey);
}
}
}

private void releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";

redisService.execute(script, Collections.singletonList(lockKey), lockValue);
}
}

📊 性能监控与优化

1. 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// Redis性能监控服务
@Service
@Slf4j
public class RedisMonitorService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private MeterRegistry meterRegistry;

// 性能指标
private final Counter cacheHitCounter;
private final Counter cacheMissCounter;
private final Timer cacheOperationTimer;

public RedisMonitorService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.cacheHitCounter = Counter.builder("redis.cache.hit")
.description("Redis缓存命中次数")
.register(meterRegistry);
this.cacheMissCounter = Counter.builder("redis.cache.miss")
.description("Redis缓存未命中次数")
.register(meterRegistry);
this.cacheOperationTimer = Timer.builder("redis.operation.duration")
.description("Redis操作耗时")
.register(meterRegistry);
}

/**
* 监控缓存操作
*/
public <T> T monitorCacheOperation(String operation, String key, Supplier<T> supplier) {
Timer.Sample sample = Timer.start(meterRegistry);

try {
T result = supplier.get();

// 记录缓存命中/未命中
if (result != null) {
cacheHitCounter.increment(Tags.of("operation", operation, "key_pattern", getKeyPattern(key)));
} else {
cacheMissCounter.increment(Tags.of("operation", operation, "key_pattern", getKeyPattern(key)));
}

return result;

} finally {
sample.stop(cacheOperationTimer.tag("operation", operation));
}
}

/**
* 获取Redis信息
*/
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void collectRedisInfo() {
try {
Properties info = redisTemplate.getConnectionFactory()
.getConnection()
.info();

// 内存使用情况
String usedMemory = info.getProperty("used_memory");
String maxMemory = info.getProperty("maxmemory");
if (usedMemory != null && maxMemory != null) {
double memoryUsageRatio = Double.parseDouble(usedMemory) / Double.parseDouble(maxMemory);
Gauge.builder("redis.memory.usage.ratio")
.description("Redis内存使用率")
.register(meterRegistry, () -> memoryUsageRatio);
}

// 连接数
String connectedClients = info.getProperty("connected_clients");
if (connectedClients != null) {
Gauge.builder("redis.connections.current")
.description("Redis当前连接数")
.register(meterRegistry, () -> Double.parseDouble(connectedClients));
}

// 命令执行统计
String totalCommandsProcessed = info.getProperty("total_commands_processed");
if (totalCommandsProcessed != null) {
Counter.builder("redis.commands.total")
.description("Redis命令执行总数")
.register(meterRegistry)
.increment(Double.parseDouble(totalCommandsProcessed));
}

// 键空间统计
for (String key : info.stringPropertyNames()) {
if (key.startsWith("db")) {
String dbInfo = info.getProperty(key);
// 解析db0:keys=1000,expires=100,avg_ttl=3600000
String[] parts = dbInfo.split(",");
for (String part : parts) {
String[] kv = part.split("=");
if (kv.length == 2) {
String metricName = "redis.keyspace." + kv[0];
double value = Double.parseDouble(kv[1]);
Gauge.builder(metricName)
.tag("database", key)
.register(meterRegistry, () -> value);
}
}
}
}

} catch (Exception e) {
log.error("收集Redis信息失败", e);
}
}

/**
* 慢查询监控
*/
@Scheduled(fixedRate = 30000) // 每30秒执行一次
public void monitorSlowQueries() {
try {
List<Object> slowLogs = redisTemplate.getConnectionFactory()
.getConnection()
.slowLogGet(10); // 获取最近10条慢查询

for (Object logEntry : slowLogs) {
// 处理慢查询日志
log.warn("Redis慢查询: {}", logEntry);

// 记录慢查询指标
Counter.builder("redis.slow.queries")
.description("Redis慢查询次数")
.register(meterRegistry)
.increment();
}

} catch (Exception e) {
log.error("监控Redis慢查询失败", e);
}
}

/**
* 连接池监控
*/
public void monitorConnectionPool() {
// 这里需要根据具体的连接池实现来获取指标
// 例如:Lettuce连接池
try {
// 获取连接池统计信息
// GenericObjectPoolConfig poolConfig = ...;
// 记录连接池指标

} catch (Exception e) {
log.error("监控连接池失败", e);
}
}

private String getKeyPattern(String key) {
// 提取键的模式,用于分组统计
if (key.startsWith("user:")) {
return "user:*";
} else if (key.startsWith("product:")) {
return "product:*";
} else if (key.startsWith("session:")) {
return "session:*";
}
return "other";
}
}

// 缓存性能分析器
@Component
@Slf4j
public class CachePerformanceAnalyzer {

@Autowired
private RedisService redisService;

/**
* 分析热点键
*/
public List<HotKeyInfo> analyzeHotKeys(int topN) {
List<HotKeyInfo> hotKeys = new ArrayList<>();

try {
// 使用MONITOR命令分析(生产环境慎用)
// 或者通过应用层统计

// 这里使用应用层统计的方式
Map<String, AtomicLong> keyAccessCount = new ConcurrentHashMap<>();

// 统计一段时间内的键访问情况
// 实际实现中可以使用AOP或者在RedisService中埋点

return hotKeys.stream()
.sorted((a, b) -> Long.compare(b.getAccessCount(), a.getAccessCount()))
.limit(topN)
.collect(Collectors.toList());

} catch (Exception e) {
log.error("分析热点键失败", e);
return Collections.emptyList();
}
}

/**
* 分析大键
*/
public List<BigKeyInfo> analyzeBigKeys() {
List<BigKeyInfo> bigKeys = new ArrayList<>();

try {
// 扫描所有键并分析大小
Set<String> keys = redisService.scan("*", 1000);

for (String key : keys) {
try {
long size = getKeySize(key);
if (size > 1024 * 1024) { // 大于1MB的键
BigKeyInfo bigKey = new BigKeyInfo();
bigKey.setKey(key);
bigKey.setSize(size);
bigKey.setType(getKeyType(key));
bigKeys.add(bigKey);
}
} catch (Exception e) {
log.warn("分析键大小失败: {}", key, e);
}
}

return bigKeys.stream()
.sorted((a, b) -> Long.compare(b.getSize(), a.getSize()))
.collect(Collectors.toList());

} catch (Exception e) {
log.error("分析大键失败", e);
return Collections.emptyList();
}
}

private long getKeySize(String key) {
// 根据键类型计算大小
String type = getKeyType(key);
switch (type) {
case "string":
return redisService.strlen(key);
case "list":
return redisService.llen(key);
case "set":
return redisService.scard(key);
case "zset":
return redisService.zcard(key);
case "hash":
return redisService.hlen(key);
default:
return 0;
}
}

private String getKeyType(String key) {
return redisService.type(key);
}
}

// 热点键信息
@Data
public class HotKeyInfo {
private String key;
private long accessCount;
private String pattern;
private long avgResponseTime;
}

// 大键信息
@Data
public class BigKeyInfo {
private String key;
private long size;
private String type;
private String sizeFormatted;
}

2. 性能优化策略

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Redis性能优化配置
@Configuration
public class RedisOptimizationConfig {

/**
* 优化的RedisTemplate配置
*/
@Bean
@Primary
public RedisTemplate<String, Object> optimizedRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);

// 使用更高效的序列化器
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringSerializer = new StringRedisSerializer();

template.setKeySerializer(stringSerializer);
template.setValueSerializer(jsonSerializer);
template.setHashKeySerializer(stringSerializer);
template.setHashValueSerializer(jsonSerializer);

// 启用事务支持
template.setEnableTransactionSupport(true);

template.afterPropertiesSet();
return template;
}

/**
* 连接池优化配置
*/
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
// 连接池配置
GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfig =
new GenericObjectPoolConfig<>();

poolConfig.setMaxTotal(50); // 最大连接数
poolConfig.setMaxIdle(20); // 最大空闲连接
poolConfig.setMinIdle(10); // 最小空闲连接
poolConfig.setMaxWaitMillis(3000); // 最大等待时间
poolConfig.setTestOnBorrow(true); // 借用时测试连接
poolConfig.setTestOnReturn(true); // 归还时测试连接
poolConfig.setTestWhileIdle(true); // 空闲时测试连接

// 客户端配置
ClientOptions clientOptions = ClientOptions.builder()
.autoReconnect(true) // 自动重连
.pingBeforeActivateConnection(true) // 激活前ping
.build();

LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.poolConfig(poolConfig)
.clientOptions(clientOptions)
.commandTimeout(Duration.ofSeconds(3))
.build();

// Redis配置
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName("localhost");
redisConfig.setPort(6379);
redisConfig.setDatabase(0);

return new LettuceConnectionFactory(redisConfig, clientConfig);
}
}

// 批量操作优化
@Service
@Slf4j
public class RedisBatchService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 批量设置键值
*/
public void batchSet(Map<String, Object> keyValues, long timeout, TimeUnit unit) {
redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection) connection;

for (Map.Entry<String, Object> entry : keyValues.entrySet()) {
String serializedValue = serialize(entry.getValue());
stringRedisConn.setEx(entry.getKey(), timeout, serializedValue);
}

return null;
}
});
}

/**
* 批量获取键值
*/
public List<Object> batchGet(List<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}

/**
* 批量删除键
*/
public Long batchDelete(Collection<String> keys) {
return redisTemplate.delete(keys);
}

/**
* 使用Lua脚本进行原子操作
*/
public Object executeLuaScript(String script, List<String> keys, Object... args) {
DefaultRedisScript<Object> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Object.class);

return redisTemplate.execute(redisScript, keys, args);
}

/**
* 分布式限流(令牌桶算法)
*/
public boolean rateLimitWithTokenBucket(String key, int capacity, int refillRate, int tokens) {
String script =
"local key = KEYS[1] " +
"local capacity = tonumber(ARGV[1]) " +
"local refillRate = tonumber(ARGV[2]) " +
"local tokens = tonumber(ARGV[3]) " +
"local now = tonumber(ARGV[4]) " +

"local bucket = redis.call('HMGET', key, 'tokens', 'lastRefill') " +
"local currentTokens = tonumber(bucket[1]) or capacity " +
"local lastRefill = tonumber(bucket[2]) or now " +

"local elapsed = now - lastRefill " +
"local tokensToAdd = math.floor(elapsed * refillRate / 1000) " +
"currentTokens = math.min(capacity, currentTokens + tokensToAdd) " +

"if currentTokens >= tokens then " +
" currentTokens = currentTokens - tokens " +
" redis.call('HMSET', key, 'tokens', currentTokens, 'lastRefill', now) " +
" redis.call('EXPIRE', key, 3600) " +
" return 1 " +
"else " +
" redis.call('HMSET', key, 'tokens', currentTokens, 'lastRefill', now) " +
" redis.call('EXPIRE', key, 3600) " +
" return 0 " +
"end";

Long result = (Long) executeLuaScript(script,
Collections.singletonList(key),
capacity, refillRate, tokens, System.currentTimeMillis());

return result != null && result == 1;
}

private String serialize(Object value) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(value);
} catch (Exception e) {
throw new RuntimeException("序列化失败", e);
}
}
}

🛡️ Redis安全与运维

1. 安全配置

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
# redis.conf 安全配置
# 1. 密码认证
requirepass your_strong_password_here

# 2. 绑定IP地址
bind 127.0.0.1 192.168.1.100

# 3. 修改默认端口
port 16379

# 4. 禁用危险命令
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command KEYS ""
rename-command CONFIG "CONFIG_9f2e8a1b3c4d5e6f"
rename-command SHUTDOWN "SHUTDOWN_a1b2c3d4e5f6"
rename-command DEBUG ""
rename-command EVAL ""

# 5. 设置最大客户端连接数
maxclients 10000

# 6. 启用保护模式
protected-mode yes

# 7. 设置超时时间
timeout 300

# 8. 限制内存使用
maxmemory 2gb
maxmemory-policy allkeys-lru

# 9. 日志配置
loglevel notice
logfile /var/log/redis/redis-server.log

# 10. 持久化安全
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis/

# AOF配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

2. 监控告警系统

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Redis健康检查服务
@Service
@Slf4j
public class RedisHealthCheckService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private NotificationService notificationService;

/**
* 健康检查
*/
@Scheduled(fixedRate = 30000) // 每30秒检查一次
public void healthCheck() {
try {
// 1. 连接性检查
checkConnectivity();

// 2. 内存使用检查
checkMemoryUsage();

// 3. 响应时间检查
checkResponseTime();

// 4. 键空间检查
checkKeyspace();

// 5. 持久化检查
checkPersistence();

} catch (Exception e) {
log.error("Redis健康检查失败", e);
notificationService.sendAlert("Redis健康检查失败: " + e.getMessage());
}
}

private void checkConnectivity() {
try {
String pong = redisTemplate.getConnectionFactory()
.getConnection()
.ping();

if (!"PONG".equals(pong)) {
throw new RuntimeException("Redis连接异常");
}

} catch (Exception e) {
log.error("Redis连接检查失败", e);
notificationService.sendAlert("Redis连接失败: " + e.getMessage());
throw e;
}
}

private void checkMemoryUsage() {
try {
Properties info = redisTemplate.getConnectionFactory()
.getConnection()
.info("memory");

long usedMemory = Long.parseLong(info.getProperty("used_memory"));
long maxMemory = Long.parseLong(info.getProperty("maxmemory"));

if (maxMemory > 0) {
double usageRatio = (double) usedMemory / maxMemory;

if (usageRatio > 0.9) {
String message = String.format("Redis内存使用率过高: %.2f%%", usageRatio * 100);
log.warn(message);
notificationService.sendAlert(message);
} else if (usageRatio > 0.8) {
String message = String.format("Redis内存使用率警告: %.2f%%", usageRatio * 100);
log.warn(message);
}
}

} catch (Exception e) {
log.error("Redis内存检查失败", e);
}
}

private void checkResponseTime() {
long startTime = System.currentTimeMillis();

try {
// 执行简单的SET/GET操作
String testKey = "health_check:" + System.currentTimeMillis();
redisTemplate.opsForValue().set(testKey, "test", 10, TimeUnit.SECONDS);
String value = (String) redisTemplate.opsForValue().get(testKey);
redisTemplate.delete(testKey);

long responseTime = System.currentTimeMillis() - startTime;

if (responseTime > 1000) { // 响应时间超过1秒
String message = String.format("Redis响应时间过长: %dms", responseTime);
log.warn(message);
notificationService.sendAlert(message);
}

} catch (Exception e) {
long responseTime = System.currentTimeMillis() - startTime;
log.error("Redis响应时间检查失败,耗时: {}ms", responseTime, e);
}
}

private void checkKeyspace() {
try {
Properties info = redisTemplate.getConnectionFactory()
.getConnection()
.info("keyspace");

// 检查键数量是否异常
for (String key : info.stringPropertyNames()) {
if (key.startsWith("db")) {
String dbInfo = info.getProperty(key);
// 解析键数量
String[] parts = dbInfo.split(",");
for (String part : parts) {
if (part.startsWith("keys=")) {
long keyCount = Long.parseLong(part.substring(5));

if (keyCount > 1000000) { // 键数量超过100万
String message = String.format("Redis键数量过多 %s: %d", key, keyCount);
log.warn(message);
notificationService.sendAlert(message);
}
}
}
}
}

} catch (Exception e) {
log.error("Redis键空间检查失败", e);
}
}

private void checkPersistence() {
try {
Properties info = redisTemplate.getConnectionFactory()
.getConnection()
.info("persistence");

// 检查RDB持久化
String rdbLastSaveTime = info.getProperty("rdb_last_save_time");
if (rdbLastSaveTime != null) {
long lastSaveTime = Long.parseLong(rdbLastSaveTime) * 1000;
long timeSinceLastSave = System.currentTimeMillis() - lastSaveTime;

if (timeSinceLastSave > 24 * 60 * 60 * 1000) { // 超过24小时未保存
String message = "Redis RDB持久化时间过长: " + (timeSinceLastSave / 1000 / 60 / 60) + "小时";
log.warn(message);
notificationService.sendAlert(message);
}
}

// 检查AOF持久化
String aofEnabled = info.getProperty("aof_enabled");
if ("1".equals(aofEnabled)) {
String aofLastRewriteTime = info.getProperty("aof_last_rewrite_time_sec");
if (aofLastRewriteTime != null && !"-1".equals(aofLastRewriteTime)) {
long lastRewriteTime = Long.parseLong(aofLastRewriteTime) * 1000;
long timeSinceLastRewrite = System.currentTimeMillis() - lastRewriteTime;

if (timeSinceLastRewrite > 7 * 24 * 60 * 60 * 1000) { // 超过7天未重写
String message = "Redis AOF重写时间过长: " + (timeSinceLastRewrite / 1000 / 60 / 60 / 24) + "天";
log.warn(message);
}
}
}

} catch (Exception e) {
log.error("Redis持久化检查失败", e);
}
}
}

// 通知服务
@Service
public class NotificationService {

public void sendAlert(String message) {
// 发送邮件、短信、钉钉等告警通知
log.error("Redis告警: {}", message);

// 这里可以集成具体的通知渠道
// 例如:邮件、短信、Slack、钉钉等
}
}

📈 最佳实践总结

1. 架构设计原则

  • 合理选择数据结构:根据业务场景选择最适合的Redis数据类型
  • 设计良好的键命名规范:使用有意义的前缀和分隔符
  • 控制键的生命周期:合理设置过期时间,避免内存泄漏
  • 避免大键:单个键的大小不应超过1MB

2. 性能优化策略

  • 使用连接池:避免频繁创建和销毁连接
  • 批量操作:使用Pipeline和批量命令减少网络开销
  • 合理使用Lua脚本:保证操作的原子性
  • 监控关键指标:内存使用率、命中率、响应时间等

3. 安全运维要点

  • 启用密码认证:设置强密码并定期更换
  • 网络安全:绑定内网IP,修改默认端口
  • 命令安全:禁用或重命名危险命令
  • 定期备份:配置RDB和AOF持久化

4. 故障处理指南

  • 内存不足:优化数据结构,清理过期键,调整淘汰策略
  • 响应缓慢:检查慢查询,优化命令,增加实例
  • 连接异常:检查网络连接,调整连接池配置
  • 数据丢失:检查持久化配置,恢复备份数据

🎯 总结

Redis作为现代应用架构中不可或缺的组件,其正确使用和优化对系统性能至关重要。通过本文的实战指南,我们深入了解了Redis的核心特性、缓存策略、性能优化和安全运维等方面的最佳实践。

掌握Redis不仅仅是学会基本的命令操作,更重要的是理解其内部机制,能够根据业务场景设计合适的缓存架构,并在生产环境中保证系统的稳定性和高性能。

随着业务的发展和技术的演进,Redis的应用场景还在不断扩展。持续学习和实践,才能在这个快速变化的技术领域中保持竞争力。让我们一起在Redis的世界中探索更多可能!