Redis在电商搜索与分类缓存中的最佳实践全面详解
一、电商搜索与分类的挑战
- 海量数据:百万级商品数据实时检索
- 复杂查询:多条件组合过滤(价格、品牌、评分等)
- 动态排序:按销量、价格、新品等多维度排序
- 实时性要求:库存状态、价格变动需及时反映
- 高并发访问:大促期间每秒数万次查询
二、整体架构设计
核心组件说明:
- Redis Cluster:存储分类结构、热点搜索数据、商品基础信息
- Elasticsearch:处理复杂搜索查询
- 本地缓存:使用Caffeine缓存极热点数据
- 数据同步:通过Canal监听MySQL Binlog
三、分类系统缓存设计
1. 分类树存储方案
数据结构: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);
2. 多维度排序实现
// 按不同维度建立多个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));
}
3. 分页查询优化
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());
}
四、搜索系统缓存设计
1. 搜索条件指纹生成
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);
}
2. 搜索结果缓存策略
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;
}
3. 关联数据预加载
// 热门搜索词缓存
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();
}
五、商品详情缓存设计
1. 多级存储策略
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;
}
2. 库存状态缓存
// 库存状态单独存储
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); // 异步更新数据库
}
六、高级优化技巧
1. 缓存预热策略
// 启动时加载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();}
}
2. 二级缓存配置
@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic 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();// 组合缓存:先查本地,再查Redisreturn new CompositeCacheManager(caffeineManager, redisManager);}
}
3. 分布式锁防击穿
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;
}
七、数据一致性保障
1. 数据变更同步流程
2. 最终一致性实现
// 监听数据库变更
@RocketMQMessageListener(topic = "product_update", consumerGroup = "cache_group")
public class ProductUpdateListener implements RocketMQListener<ProductUpdateEvent> {@Overridepublic void onMessage(ProductUpdateEvent event) {String productId = event.getProductId();// 删除旧缓存jedis.del("product:" + productId);localCache.invalidate(productId);// 异步重建缓存executorService.submit(() -> {Product product = productDAO.getById(productId);updateCache(product);});}
}
八、监控与调优
1. 关键监控指标
指标 | 监控方式 | 告警阈值 |
---|---|---|
缓存命中率 | info stats keyspace_hits | < 90% |
内存使用率 | info memory used_memory | > 80% |
网络流量 | info stats total_net_input_bytes | > 100MB/s |
慢查询数量 | slowlog get | > 100/分钟 |
2. 性能调优参数
# 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
九、压测数据参考
测试环境:
- Redis Cluster(6节点,32核/64GB)
- 商品数据量:1000万
- 搜索QPS:5万+
性能指标:
操作类型 | 平均延迟 | P99延迟 | 吞吐量 |
---|---|---|---|
分类查询 | 8ms | 25ms | 12,000/s |
复杂搜索 | 35ms | 120ms | 6,500/s |
商品详情获取 | 2ms | 5ms | 25,000/s |
库存查询 | 1ms | 3ms | 45,000/s |
十、生产环境Checklist
- 容量规划:预留30%内存缓冲
- 安全配置:启用ACL访问控制
- 备份策略:每日RDB + 实时AOF
- 故障演练:定期模拟节点宕机
- 慢查询分析:每周检查slowlog
- 版本升级:保持Redis 6.2+版本
通过以上方案,可实现:
- 毫秒级响应:核心操作<10ms
- 99.99%可用性:全年故障时间<1小时
- 线性扩展:轻松应对流量十倍增长
- 实时数据:秒级数据一致性保障
建议配合APM工具(SkyWalking、Pinpoint)进行全链路监控,持续优化热点数据处理逻辑。
更多资源:
http://sj.ysok.net/jydoraemon 访问码:JYAM