在电商平台中,商家经常关注自己的热销商品排名,以便进行策略优化、库存管理或重点推广。这种需求既要求高效查询,也要求数据能动态更新或实时统计。基于 Redis 的有序集合(Sorted Set),我们可以设计一个高效的商家热销商品 TOP50 查询系统。
本文将从目标场景出发,结合 Redis 的特性阐述方案,并用 Golang 实现完整代码。
1. 场景分析
商家希望查询自己售卖的最热销商品 TOP50,即商品销量最高的前 50 名。这个需求的具体要求如下:
高效存储和查询:商品销量每天变化,需要提供近乎实时的热销商品排名。
支持动态更新:销量随交易实时变化,排行必须即时更新。
响应速度快:需要支持毫秒级查询,适应高并发环境。
扩展性强:数据量随着时间和业务增长,设计需支持大规模数据查询。
Redis 的有序集合(Sorted Set)可以很好地满足这一需求,通过 ZINCRBY
和 ZREVRANGE
等命令,我们可以高效维护商品销量与排名列表。
2. 方案设计
2.1 Redis 数据结构设计
利用 Redis 的有序集合,我们为每个商家单独维护一个商品销量排行榜,具体结构如下:
键名设计:
每个商家的排行榜存储在一个独立的 Redis Key,下标以商家 ID 区分,格式为
seller:rank:{seller_id}
。商品 ID 作为集合的成员,销量作为分值(score)。
示例:
Key: seller:rank:123
Member: product_1 Score: 300
Member: product_2 Score: 500
Member: product_3 Score: 200
商家 ID 为
123
的排行榜:
product_2
拥有最高销量(500),排名第一。Redis 会根据销量动态维护商品的降序排列。
2.2 系统功能设计
核心功能包括以下几部分:
销量动态更新:
当用户购买商品时,立即更新该商品的销量数据到 Redis。排行榜查询:
商家可以根据 ID 查询自己的 TOP50 热销商品。数据删除:
如果某个商品下架,需要从排行榜中移除该商品。
3. Golang 实现
下面我们将基于 Golang 实现上述功能,使用 Redis 作为数据存储。
3.1 Redis 连接初始化
我们使用 Redis 的 Go 客户端 go-redis/v8 来操作 Redis。
package main
import (
"context"
"github.com/redis/go-redis/v8"
"log"
)
// 创建 Redis 客户端
var ctx = context.Background()
var rdb *redis.Client
func initRedis() {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 地址
Password: "", // Redis 无密码
DB: 0, // 使用默认数据库
})
// 测试连接
_, err := rdb.Ping(ctx).Result()
if err != nil {
log.Fatalf("连接 Redis 失败: %v", err)
}
log.Println("Redis 连接成功!")
}
3.2 更新商品销量
当用户购买商品时,我们需要动态更新销量。Redis 提供 ZINCRBY
命令,可以有效增加指定商品的分值(即销量)。
代码实现:
func updateSales(sellerID int, productID string, quantity int) error {
// 构建 Redis Key
redisKey := buildSellerRankKey(sellerID)
// ZINCRBY 命令:增加销量分值
err := rdb.ZIncrBy(ctx, redisKey, float64(quantity), productID).Err()
if err != nil {
return err
}
log.Printf("销量更新成功:商家 %d,商品 %s,增加销量 %d\n", sellerID, productID, quantity)
return nil
}
func buildSellerRankKey(sellerID int) string {
return "seller:rank:" + strconv.Itoa(sellerID)
}
示例调用:
updateSales(123, "product_1", 10) // 为商家 123 的商品 product_1 增加 10 的销量
3.3 查询商家热销商品 TOP50
Redis 提供 ZREVRANGE
命令,可以获取指定集合按分值降序排列的前 N 个成员,这非常适合实现排行榜功能。
代码实现:
func getTopProducts(sellerID int, limit int) ([]map[string]interface{}, error) {
// 构建 Redis Key
redisKey := buildSellerRankKey(sellerID)
// 查询 Redis:ZREVRANGE 按销量降序查询
result, err := rdb.ZRevRangeWithScores(ctx, redisKey, 0, int64(limit-1)).Result()
if err != nil {
return nil, err
}
// 格式化输出
var topProducts []map[string]interface{}
for _, item := range result {
topProducts = append(topProducts, map[string]interface{}{
"product_id": item.Member,
"sales": item.Score,
})
}
return topProducts, nil
}
示例调用:
topProducts, err := getTopProducts(123, 50) // 查询商家 123 的热销商品 TOP50
if err != nil {
log.Fatalf("获取排行榜失败: %v", err)
}
log.Println("商家 123 的热销商品 TOP50:", topProducts)
返回示例:
假如查询结果为:
1) "product_2" 2) "500"
3) "product_1" 4) "300"
5) "product_3" 6) "200"
则 Golang 返回值格式如下:
[
{"product_id": "product_2", "sales": 500},
{"product_id": "product_1", "sales": 300},
{"product_id": "product_3", "sales": 200}
]
3.4 删除商品数据
当某商品下架时,需要将该商品从排行榜中移除。
代码实现:
func removeProduct(sellerID int, productID string) error {
// 构建 Redis Key
redisKey := buildSellerRankKey(sellerID)
// ZREM 命令:删除指定商品
err := rdb.ZRem(ctx, redisKey, productID).Err()
if err != nil {
return err
}
log.Printf("商品删除成功:商家 %d,商品 %s\n", sellerID, productID)
return nil
}
示例调用:
removeProduct(123, "product_1") // 删除商家 123 的商品 product_1
4. 性能扩展与优化
4.1 数据过期和分区策略
为了防止 Redis 数据膨胀,我们可以为排行榜设置过期时间或按时间维度分区:
键设置过期时间:
rdb.Expire(ctx, "seller:rank:123", 7*24*time.Hour)
使用
EXPIRE
命令控制排行榜存储时间。示例:将商家排行榜设置为 7 天有效期。
按时间分区:
为支持每日、每周榜单查询,可以在键名中加入时间维度:
Key: seller:rank:123:20231001
4.2 Redis Cluster 扩展
当数据量剧增时,单个 Redis 节点可能面临存储压力或性能瓶颈。可通过 Redis Cluster 实现水平扩展:
使用
hash_slot
分片存储商家排行榜。商家 ID 用作分片键,这样可以均匀分布排行榜数据。
示例:
seller:rank:123 -> 节点 A
seller:rank:456 -> 节点 B
seller:rank:789 -> 节点 C
5. 总结
使用 Redis 有序集合设计商家热销商品 TOP50 查询系统可以高效满足动态更新与实时查询需求:
高性能更新:
ZINCRBY
实现销量动态更新,时间复杂度为 O(log(N))。快速查询:
ZREVRANGE
快速获取降序排列的 TOP50 热销商品。良好扩展性:支持基于 Redis Cluster 的水平扩展,适配大规模数据。
评论