Spring Boot基于Redis的电商PriceInfo多条件查询与Key设计
适配双查询需求的Redis Key结构设计
你当前实现的两个硬伤
- 全局Hash的Field直接用ProductId标识,同个商品在不同供应商的PriceInfo存储时会互相覆盖,根本保留不下多渠道的同商品价格数据
- 没有建立供应商维度的索引,要查某个供应商的全量商品数据必须遍历全库所有Hash条目,数据量稍大就会出现性能瓶颈
核心设计思路
采用独立实体存储+双维度冗余索引的拆分结构,完全匹配两类查询的性能要求,同时尽量降低冗余存储的内存占用:
1. 单条价格数据的实体存储
每条PriceInfo的唯一标识是ProductId + Provider的组合,实体Key的统一命名规范为:pricing:info:{productId}:{provider}
- Key的Value直接存序列化完成的PriceInfo POJO,不管是JSON序列化、Spring Redis默认的对象序列化格式都兼容
- 举个例子:Ebay渠道的iphone价格,对应的实体Key就是
pricing:info:iphone:Ebay,Value就是这条价格记录的完整POJO
2. 单商品全渠道查询的索引结构
为了支持「查询单个商品在所有供应商的价格」的需求,给每个商品建独立的Hash索引,命名规范为:pricing:index:product:{productId}
- Hash的Field是具体的供应商ID(对应PriceInfo的Provider字段值)
- Hash的Value是对应实体存储的Key引用(只存上面
pricing:info:开头的实体Key路径,不存完整POJO,节省内存) - 举个例子:iphone的商品索引Key是
pricing:index:product:iphone,Hash内会有3个Field:Amazon、Ebay、Alibaba,每个Field的值分别对应三个渠道的实体Key
3. 单供应商全量商品查询的索引结构
为了支持「查询指定供应商的全量商品价格」的需求,给每个供应商建独立的Hash索引,命名规范为:pricing:index:provider:{providerId}
- Hash的Field是具体的商品ID(对应PriceInfo的ProductId字段值)
- Hash的Value同样是对应实体存储的Key引用,不存完整POJO
- 举个例子:Ebay的供应商索引Key是
pricing:index:provider:Ebay,Hash内会有2个Field:iphone、GalaxyX2,每个Field的值分别对应两个商品在Ebay的实体Key
落地代码逻辑
改写save方法
原有的save逻辑需要同步写入三个位置,保证数据一致性:
public void save(PricingInfo pricingInfo) { // 生成当前数据对应的实体Key String entityKey = String.format("pricing:info:%s:%s", pricingInfo.getProductId(), pricingInfo.getProvider()); // 存储实体POJO redisTemplate.opsForValue().set(entityKey, pricingInfo); // 写入商品维度的索引 String productIndexKey = String.format("pricing:index:product:%s", pricingInfo.getProductId()); redisTemplate.opsForHash().put(productIndexKey, pricingInfo.getProvider(), entityKey); // 写入供应商维度的索引 String providerIndexKey = String.format("pricing:index:provider:%s", pricingInfo.getProvider()); redisTemplate.opsForHash().put(providerIndexKey, pricingInfo.getProductId(), entityKey); }
重复save同个ProductId+Provider的组合时,直接覆盖实体和对应的两个索引条目即可,不会产生脏数据。
对应查询实现
- 查询单商品全渠道价格:比如查iphone的价格,直接对Key
pricing:index:product:iphone执行hgetall拿到所有实体Key列表,再用mget批量取出对应的PriceInfo实体,直接组装返回结果即可,全程不需要扫描全库,耗时仅和返回的数据量正相关 - 查询单供应商全量商品价格:比如查Ebay的商品价格,直接对Key
pricing:index:provider:Ebay执行hgetall拿到所有实体Key列表,同样用mget批量取实体返回即可
落地注意细节
- 所有Key统一带
pricing:前缀,后续做批量删除、过期时间配置、线上Key排查时会省很多事 - 给实体和对应的索引设置统一的过期时间(比如价格数据只保留30天),避免长期残留无效脏Key
- 删除单条价格数据时,必须同步清理三个位置:实体Key、对应商品索引下的Field、对应供应商索引下的Field,防止出现索引指向不存在实体的异常
- 不要在索引Hash里存完整的PriceInfo对象,只存实体Key的引用,商品量、供应商规模越大,这种设计的内存节约效果越明显
内容的提问来源于stack exchange,提问作者Koret
相关产品推荐
相关产品推荐

