Redis最佳实践——搜索与分类缓存详解

Redis在电商搜索与分类缓存中的最佳实践全面详解


一、电商搜索与分类的挑战
  1. 海量数据:百万级商品数据实时检索
  2. 复杂查询:多条件组合过滤(价格、品牌、评分等)
  3. 动态排序:按销量、价格、新品等多维度排序
  4. 实时性要求:库存状态、价格变动需及时反映
  5. 高并发访问:大促期间每秒数万次查询

二、整体架构设计

客户端 API网关 搜索服务 Redis集群 Elasticsearch MySQL 本地缓存

核心组件说明

  • Redis Cluster:存储分类结构、热点搜索数据、商品基础信息
  • Elasticsearch:处理复杂搜索查询
  • 本地缓存:使用Caffeine缓存极热点数据
  • 数据同步:通过Canal监听MySQL Binlog

三、分类系统缓存设计
1. 分类树存储方案

数据结构:Hash + Sorted Set

java 复制代码
// 分类元数据存储
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. 多维度排序实现
java 复制代码
// 按不同维度建立多个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. 分页查询优化
java 复制代码
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. 搜索条件指纹生成
java 复制代码
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. 搜索结果缓存策略
java 复制代码
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. 关联数据预加载
java 复制代码
// 热门搜索词缓存
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. 多级存储策略
java 复制代码
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. 库存状态缓存
java 复制代码
// 库存状态单独存储
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. 缓存预热策略
java 复制代码
// 启动时加载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. 二级缓存配置
java 复制代码
@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);
    }
}
3. 分布式锁防击穿
java 复制代码
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. 数据变更同步流程

MySQL Canal RocketMQ Redis App Binlog 变更事件 通知消息 删除/更新缓存 MySQL Canal RocketMQ Redis App

2. 最终一致性实现
java 复制代码
// 监听数据库变更
@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);
        });
    }
}

八、监控与调优
1. 关键监控指标
指标 监控方式 告警阈值
缓存命中率 info stats keyspace_hits < 90%
内存使用率 info memory used_memory > 80%
网络流量 info stats total_net_input_bytes > 100MB/s
慢查询数量 slowlog get > 100/分钟
2. 性能调优参数
properties 复制代码
# 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
  1. 容量规划:预留30%内存缓冲
  2. 安全配置:启用ACL访问控制
  3. 备份策略:每日RDB + 实时AOF
  4. 故障演练:定期模拟节点宕机
  5. 慢查询分析:每周检查slowlog
  6. 版本升级:保持Redis 6.2+版本

通过以上方案,可实现:

  • 毫秒级响应:核心操作<10ms
  • 99.99%可用性:全年故障时间<1小时
  • 线性扩展:轻松应对流量十倍增长
  • 实时数据:秒级数据一致性保障

建议配合APM工具(SkyWalking、Pinpoint)进行全链路监控,持续优化热点数据处理逻辑。

更多资源:

http://sj.ysok.net/jydoraemon 访问码:JYAM

本文发表于【纪元A梦】,关注我,获取更多免费实用教程/资源!

相关推荐
初听于你16 小时前
缓存技术揭秘
java·运维·服务器·开发语言·spring·缓存
恒悦sunsite18 小时前
Ubuntu之apt安装ClickHouse数据库
数据库·clickhouse·ubuntu·列式存储·8123
奥尔特星云大使19 小时前
MySQL 慢查询日志slow query log
android·数据库·mysql·adb·慢日志·slow query log
来自宇宙的曹先生19 小时前
MySQL 存储引擎 API
数据库·mysql
间彧19 小时前
MySQL Performance Schema详解与实战应用
数据库
间彧19 小时前
MySQL Exporter采集的关键指标有哪些,如何解读这些指标?
数据库
weixin_4462608520 小时前
Django - 让开发变得简单高效的Web框架
前端·数据库·django
mpHH20 小时前
babelfish for postgresql 分析--todo
数据库·postgresql
zizisuo20 小时前
解决在使用Lombok时maven install 找不到符号的问题
java·数据库·maven
程序边界21 小时前
国产之光!金仓数据库KingbaseES Oracle兼容性深度体验大赏
数据库·oracle