在现代分布式系统中,Redis 作为高性能的内存数据库,常用于缓存、消息队列和实时数据处理。合理使用 Redis 数据结构(如 String、Set、Hash、List 等)可以极大提升系统性能。本文将通过一个实际案例,介绍如何将 Redis 存储结构从 Set 迁移到 Hash,并实现定时任务监控数据变化,确保数据一致性。
我们有一个 LogMediaAdIdCache 类,用于缓存广告位 ID,并定期从 Redis 刷新数据。原始实现使用 Set 存储数据,结构如下:
SET logscraping:mediaAdId [id1, id2, id3, ...]但随着业务发展,我们需要存储更多元信息(如广告位名称、状态、更新时间等),仅用 Set 已无法满足需求。
name, status, updateTime)。因此,我们决定将数据结构从 Set 改为 Hash:
HASH logscraping:mediaAdId
id1 -> { "name": "广告位1", "status": "active" }
id2 -> { "name": "广告位2", "status": "inactive" }特性 | Set | Hash |
|---|---|---|
存储方式 | 无序唯一集合 | 键值对存储(field-value) |
适用场景 | 去重、集合运算(交集、并集) | 结构化数据,需存储额外属性 |
查询效率 | O(1) 判断元素是否存在 | O(1) 按 field 查询 value |
扩展性 | 只能存储单一值 | 可存储复杂对象(JSON、Map) |
结论:
LogMediaAdIdCache@Component
@Slf4j
@RequiredArgsConstructor
public class LogMediaAdIdCache {
private final RedisTemplate<String, String> redisTemplate;
private volatile Set<Long> cachedLogMediaAdIds = Collections.emptySet();
public static final String LOG_REDIS_KEY = "logscraping:mediaAdId";
@PostConstruct
public void init() {
refreshCache();
}
@Scheduled(fixedRate = 5 * 1000) // 每5秒刷新一次
public void refreshCache() {
try {
Map<Object, Object> idMap = redisTemplate.opsForHash().entries(LOG_REDIS_KEY);
if (idMap != null) {
Set<Long> newCache = idMap.keySet().stream()
.map(key -> Long.valueOf(key.toString()))
.collect(Collectors.toSet());
this.cachedLogMediaAdIds = newCache;
log.info("广告位ID缓存刷新成功,数量: {}", newCache.size());
}
} catch (Exception e) {
log.error("刷新广告位ID缓存失败", e);
}
}
public Set<Long> getLogMediaAdIds() {
return Collections.unmodifiableSet(cachedLogMediaAdIds);
}
public boolean contains(Long mediaAdId) {
return cachedLogMediaAdIds.contains(mediaAdId);
}
// 新增方法:获取广告位详情
public String getAdInfo(Long mediaAdId) {
return redisTemplate.<String, String>opsForHash().get(LOG_REDIS_KEY, mediaAdId.toString());
}
// 新增方法:更新广告位信息
public void updateAdInfo(Long mediaAdId, String info) {
redisTemplate.opsForHash().put(LOG_REDIS_KEY, mediaAdId.toString(), info);
refreshCache(); // 立即刷新缓存
}
}opsForHash() 操作 Redis Hash,支持结构化存储。getAdInfo() 方法,按广告位 ID 查询详情。updateAdInfo() 方法,支持动态更新数据。LogStatsMonitorJob@Component
@Slf4j
@RequiredArgsConstructor
public class LogStatsMonitorJob {
private final RedisTemplate<String, String> redisTemplate;
private static final String LOG_STATS_KEY = "log:stats:1635474646";
@Scheduled(fixedRate = 10 * 1000) // 每10秒执行一次
public void monitorLogStats() {
String currentTimeKey = getFormattedTime();
String fullKey = LOG_STATS_KEY + ":" + currentTimeKey;
try {
Map<Object, Object> stats = redisTemplate.opsForHash().entries(fullKey);
if (stats == null || stats.isEmpty()) {
log.warn("未找到统计信息: {}", fullKey);
return;
}
log.info("----- 统计信息监控(Key: {})-----", fullKey);
stats.forEach((field, value) ->
log.info("{}: {}", field, value));
// 业务处理示例
int total = Integer.parseInt(stats.getOrDefault("total", "0").toString());
if (total > 1000) {
log.warn("警告:数据量过大({}条)", total);
}
} catch (Exception e) {
log.error("监控统计信息失败", e);
}
}
private String getFormattedTime() {
// 生成格式化的时间戳,如 20250609175050
return LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
}log:stats:1635474646:20250609175050)。LogMediaAdIdCache(改造后)(见上文 3.1 节)
LogStatsMonitorJob(新增)(见上文 4.1 节)
@Configuration
@EnableScheduling
public class SchedulingConfig {
// 启用定时任务
}本文通过一个实际案例,演示了如何将 Redis 数据结构从 Set 迁移到 Hash,并实现高效定时监控。合理的数据结构选择 + 定时任务优化,可以显著提升系统性能和可维护性。希望本文对你有所帮助! 🚀