在电商平台中,商家经常关注自己的热销商品排名,以便进行策略优化、库存管理或重点推广。这种需求既要求高效查询,也要求数据能动态更新或实时统计。基于 Redis 的有序集合(Sorted Set),我们可以设计一个高效的商家热销商品 TOP50 查询系统。

本文将从目标场景出发,结合 Redis 的特性阐述方案,并用 Golang 实现完整代码。


1. 场景分析

商家希望查询自己售卖的最热销商品 TOP50,即商品销量最高的前 50 名。这个需求的具体要求如下:

  1. 高效存储和查询:商品销量每天变化,需要提供近乎实时的热销商品排名。

  2. 支持动态更新:销量随交易实时变化,排行必须即时更新。

  3. 响应速度快:需要支持毫秒级查询,适应高并发环境。

  4. 扩展性强:数据量随着时间和业务增长,设计需支持大规模数据查询。

Redis 的有序集合(Sorted Set)可以很好地满足这一需求,通过 ZINCRBYZREVRANGE 等命令,我们可以高效维护商品销量与排名列表。


2. 方案设计

2.1 Redis 数据结构设计

利用 Redis 的有序集合,我们为每个商家单独维护一个商品销量排行榜,具体结构如下:

  1. 键名设计

  • 每个商家的排行榜存储在一个独立的 Redis Key,下标以商家 ID 区分,格式为 seller:rank:{seller_id}

  • 商品 ID 作为集合的成员,销量作为分值(score)。

  1. 示例

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 系统功能设计

核心功能包括以下几部分:

  1. 销量动态更新
    当用户购买商品时,立即更新该商品的销量数据到 Redis。

  2. 排行榜查询
    商家可以根据 ID 查询自己的 TOP50 热销商品。

  3. 数据删除
    如果某个商品下架,需要从排行榜中移除该商品。


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 数据膨胀,我们可以为排行榜设置过期时间或按时间维度分区:

  1. 键设置过期时间

rdb.Expire(ctx, "seller:rank:123", 7*24*time.Hour)
  • 使用 EXPIRE 命令控制排行榜存储时间。

  • 示例:将商家排行榜设置为 7 天有效期。

  1. 按时间分区
    为支持每日、每周榜单查询,可以在键名中加入时间维度:

Key: seller:rank:123:20231001

4.2 Redis Cluster 扩展

当数据量剧增时,单个 Redis 节点可能面临存储压力或性能瓶颈。可通过 Redis Cluster 实现水平扩展:

  1. 使用 hash_slot 分片存储商家排行榜。

  2. 商家 ID 用作分片键,这样可以均匀分布排行榜数据。

示例:

seller:rank:123  -> 节点 A
seller:rank:456  -> 节点 B
seller:rank:789  -> 节点 C

5. 总结

使用 Redis 有序集合设计商家热销商品 TOP50 查询系统可以高效满足动态更新与实时查询需求:

  1. 高性能更新ZINCRBY 实现销量动态更新,时间复杂度为 O(log(N))。

  2. 快速查询ZREVRANGE 快速获取降序排列的 TOP50 热销商品。

  3. 良好扩展性:支持基于 Redis Cluster 的水平扩展,适配大规模数据。