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梦】,关注我,获取更多免费实用教程/资源!

相关推荐
程序员三藏23 分钟前
Selenium三大等待
自动化测试·软件测试·数据库·python·selenium·测试工具·测试用例
闪电麦坤9535 分钟前
SQL:Constraint(约束)
数据库·sql
观无1 小时前
.NET-EFCore基础知识
数据库·.net
^_^ 纵歌1 小时前
mongodb和clickhouse比较
数据库·clickhouse·mongodb
旅行的橘子汽水2 小时前
【C语言-全局变量】
c语言·开发语言·数据库
pwzs2 小时前
缓存不只是加速器:深入理解 Redis 的底层机制
数据库·redis·缓存
A尘埃2 小时前
电商中的购物车(redis的hash类型操作)
数据库·redis·哈希算法
程序员学习随笔3 小时前
PostgreSQL技术内幕28:触发器实现原理
数据库·postgresql
在下千玦3 小时前
#关于数据库中的时间存储
数据库
寰宇视讯3 小时前
铼赛智能Edge mini斩获2025法国设计大奖 | 重新定义数字化齿科美学
前端·数据库·edge