核心组件说明:
数据结构:Hash + Sorted Set
// 分类元数据存储
String categoryKey = "category:meta:" + catId;
jedis.hmset(categoryKey,
"name", "手机",
"parentId", "0",
"level", "1",
"productCount", "5000"
);
// 分类商品关系存储(按时间排序)
String sortedKey = "category:sort:" + catId + ":time";
jedis.zadd(sortedKey, product.getCreateTime(), productId);
// 按不同维度建立多个ZSET
Map<String, String> sortKeys = new HashMap<>();
sortKeys.put("price", "category:sort:1001:price");
sortKeys.put("sales", "category:sort:1001:sales");
sortKeys.put("rating", "category:sort:1001:rating");
// 商品价格更新时
public void updateProductPrice(String productId, double newPrice) {
String key = "category:sort:1001:price";
jedis.zadd(key, newPrice, productId);
// 同时更新Hash中的价格缓存
String productKey = "product:" + productId;
jedis.hset(productKey, "price", String.valueOf(newPrice));
}
public List<Product> getProductsByCategory(String catId, int page, int size, String sortBy) {
String sortKey = "category:sort:" + catId + ":" + sortBy;
long start = (page - 1) * size;
long end = start + size - 1;
// 获取商品ID列表
Set<String> productIds = jedis.zrevrange(sortKey, start, end);
// 批量获取商品详情
Pipeline pipeline = jedis.pipelined();
Map<String, Response<Map<String, String>>> responses = new HashMap<>();
productIds.forEach(id -> {
responses.put(id, pipeline.hgetAll("product:" + id));
});
pipeline.sync();
// 构建结果
return productIds.stream()
.map(id -> parseProduct(responses.get(id).get()))
.collect(Collectors.toList());
}
public String generateCacheKey(SearchParams params) {
String query = String.format("%s-%s-%s-%s-%s",
params.getKeyword(),
params.getMinPrice(),
params.getMaxPrice(),
String.join(",", params.getBrands()),
params.getSortBy()
);
return "search:" + DigestUtils.md5Hex(query);
}
public SearchResult searchWithCache(SearchParams params) {
String cacheKey = generateCacheKey(params);
// 1. 检查本地缓存
SearchResult cached = localCache.getIfPresent(cacheKey);
if (cached != null) return cached;
// 2. 检查Redis缓存
String redisData = jedis.get(cacheKey);
if (redisData != null) {
SearchResult result = deserialize(redisData);
localCache.put(cacheKey, result);
return result;
}
// 3. 回源到Elasticsearch查询
SearchResult result = elasticsearchService.search(params);
// 4. 异步写入缓存
CompletableFuture.runAsync(() -> {
jedis.setex(cacheKey, 300, serialize(result)); // 缓存5分钟
localCache.put(cacheKey, result);
});
return result;
}
// 热门搜索词缓存
public void preloadHotSearches() {
List<String> hotKeywords = elasticsearchService.getHotKeywords();
hotKeywords.forEach(keyword -> {
SearchParams params = new SearchParams(keyword);
searchWithCache(params); // 触发缓存预加载
});
}
// 定时任务每天执行
@Scheduled(cron = "0 0 3 * * ?")
public void refreshCache() {
preloadHotSearches();
refreshCategoryTrees();
}
public Product getProduct(String productId) {
// 1. 检查本地缓存
Product product = localCache.get(productId);
if (product != null) return product;
// 2. 检查Redis缓存
Map<String, String> redisData = jedis.hgetAll("product:" + productId);
if (!redisData.isEmpty()) {
Product p = parseProduct(redisData);
localCache.put(productId, p);
return p;
}
// 3. 回源数据库
Product dbProduct = productDAO.getById(productId);
// 4. 异步更新缓存
CompletableFuture.runAsync(() -> {
Map<String, String> hash = convertToHash(dbProduct);
jedis.hmset("product:" + productId, hash);
jedis.expire("product:" + productId, 3600);
localCache.put(productId, dbProduct);
});
return dbProduct;
}
// 库存状态单独存储
public int getStockWithCache(String productId) {
String key = "stock:" + productId;
String stock = jedis.get(key);
if (stock != null) return Integer.parseInt(stock);
// 数据库查询并设置缓存
int dbStock = stockService.getRealStock(productId);
jedis.setex(key, 30, String.valueOf(dbStock)); // 30秒过期
return dbStock;
}
// 库存变更时更新
public void updateStock(String productId, int delta) {
String key = "stock:" + productId;
jedis.decrBy(key, delta);
stockService.updateDBStock(productId, delta); // 异步更新数据库
}
// 启动时加载Top 1000商品
@PostConstruct
public void warmUpCache() {
List<Product> hotProducts = productDAO.getTop1000();
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
hotProducts.forEach(p -> {
pipeline.hmset("product:" + p.getId(), convertToHash(p));
pipeline.expire("product:" + p.getId(), 86400);
});
pipeline.sync();
}
}
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineManager = new CaffeineCacheManager();
caffeineManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES));
RedisCacheManager redisManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)))
.build();
// 组合缓存:先查本地,再查Redis
return new CompositeCacheManager(caffeineManager, redisManager);
}
}
public Product getProductSafely(String productId) {
String lockKey = "product_lock:" + productId;
RLock lock = redisson.getLock(lockKey);
try {
if (lock.tryLock(1, 5, TimeUnit.SECONDS)) {
// 二次检查缓存
Product cached = localCache.get(productId);
if (cached != null) return cached;
// 数据库查询
Product product = productDAO.getById(productId);
// 更新缓存
updateCache(product);
return product;
}
} finally {
lock.unlock();
}
return null;
}
// 监听数据库变更
@RocketMQMessageListener(topic = "product_update", consumerGroup = "cache_group")
public class ProductUpdateListener implements RocketMQListener<ProductUpdateEvent> {
@Override
public void onMessage(ProductUpdateEvent event) {
String productId = event.getProductId();
// 删除旧缓存
jedis.del("product:" + productId);
localCache.invalidate(productId);
// 异步重建缓存
executorService.submit(() -> {
Product product = productDAO.getById(productId);
updateCache(product);
});
}
}
指标 | 监控方式 | 告警阈值 |
---|---|---|
缓存命中率 | info stats keyspace_hits |
< 90% |
内存使用率 | info memory used_memory |
> 80% |
网络流量 | info stats total_net_input_bytes |
> 100MB/s |
慢查询数量 | slowlog get |
> 100/分钟 |
# redis.conf 关键配置
maxmemory 16gb
maxmemory-policy allkeys-lfu
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
client-output-buffer-limit normal 0 0 0
测试环境:
性能指标:
操作类型 | 平均延迟 | P99延迟 | 吞吐量 |
---|---|---|---|
分类查询 | 8ms | 25ms | 12,000/s |
复杂搜索 | 35ms | 120ms | 6,500/s |
商品详情获取 | 2ms | 5ms | 25,000/s |
库存查询 | 1ms | 3ms | 45,000/s |
通过以上方案,可实现:
建议配合APM工具(SkyWalking、Pinpoint)进行全链路监控,持续优化热点数据处理逻辑。
http://sj.ysok.net/jydoraemon 访问码:JYAM