Go Redis实现排行榜

news2024/12/28 15:42:42

文章目录

  • 第一章. 前言
    • 1.1. 排行榜的应用场景
    • 1.2. Redis在排行榜功能中的优势
  • 第二章. 排行榜系统的功能接口设计
    • 2.1. 接口设计的重要性
    • 2.2. 核心功能接口
      • 2.2.1. 排行榜系统总接口
      • 2.2.2. 排行榜数据源接口
      • 2.2.3. 排行榜打分器接口
      • 2.2.4. 排行榜数据存储接口
      • 2.2.5. 排行榜重建机制接口
  • 第三章. 排行榜功能接口的具体实现
    • 3.1. Redis客户端的初始化
    • 3.2. 核心接口的实现
      • 3.2.1. 总接口实现
      • 3.2.2. 数据存储实现
      • 3.2.3. 数据源获取实现
      • 3.2.4. 重建机制实现
      • 3.2.5. 打分器实现
  • 第四章. 排行榜功能的使用示例
    • 4.1. 具体使用场景
    • 4.2. 积分更新
    • 4.3. 获取排行榜数据
    • 4.4. 刷新数据到数据库
    • 4.5. 排行榜功能的扩展
  • 第五章. 深入分析与对比
    • 5.1. 使用 Redis 实现排行榜的优缺点
    • 5.2. Redis 实现与其他方式的对比
      • 5.2.1. 基于数据库的实现
      • 5.2.2. 内存排序算法实现
      • 5.2.3. 对比
  • 第六章. 总结与最佳实践
    • 6.1. 排行榜功能的核心要点
    • 6.2. 排行榜系统的设计原则
    • 6.3. 最佳实践清单
  • 附录


第一章. 前言

1.1. 排行榜的应用场景

排行榜是一种广泛应用于各类系统中的功能,能够直观展示数据的对比和竞争关系。以下是一些典型的应用场景:

1. 游戏排行
在游戏场景中,排行榜常被用来展示玩家的分数、等级或竞技表现。例如:

  • 单局排名:展示玩家在某一局游戏中的分数排名。
  • 全服排名:统计全服玩家的积分,显示排名前列的玩家。

2. 电商排行
电商平台利用排行榜来吸引用户关注热门商品和高口碑商家。常见的排行包括:

  • 销量排行榜:基于商品销量进行排名。
  • 好评率排行榜:按用户评价分数排序,突出优质商品。

3. 数据分析
在数据驱动的分析场景中,排行榜提供实时的数据对比,支持用户快速决策。例如:

  • 访问量排行:显示某一时间段内访问量最高的网页。
  • 点赞排行:按点赞数展示内容受欢迎程度。

1.2. Redis在排行榜功能中的优势

在实现排行榜的技术选型中,Redis以其高性能、高可用性成为广泛应用的工具。以下是它的几大优势:

1. 数据操作高效
Redis提供的有序集合(Sorted Set)数据结构,支持按照分数对元素排序,并提供高效的插入、查询操作。常用命令包括:

  • ZADD:向集合中添加成员并设置分数。
  • ZRANGE:按分数范围获取成员列表。
  • ZRANK:获取指定成员的排名。

2. 原子性操作
Redis的命令是原子性的,可以确保分数更新和排名获取等操作在高并发环境下的正确性。

3. 高可扩展性
Redis支持分布式部署,能够轻松处理大规模并发访问。此外,通过分片或数据归档,可以突破单节点内存限制。


第二章. 排行榜系统的功能接口设计

2.1. 接口设计的重要性

在构建复杂的系统如排行榜功能时,接口设计起到了规范功能模块的关键作用。良好的接口设计可以带来以下优势:

  1. 代码易读性:接口定义清晰,开发者可以快速理解功能。
  2. 扩展性:接口设计解耦了具体实现与业务逻辑,便于后续功能扩展。
  3. 维护性:通过接口隔离变更,可以在不影响外部调用者的情况下优化内部实现。

2.2. 核心功能接口

在排行榜系统中,我们将功能模块化,并为每个模块定义了对应的接口。

2.2.1. 排行榜系统总接口

总接口 RankSystemIFace 通过组合其他接口,定义了系统的整体功能:

type RankSystemIFace interface {
	RankSourceIFace
	RankScoreIFace
	RankStorageIFace
	RankRebuildIFace
}
  • 组合接口:将排行榜功能拆分为多个独立的功能模块,并通过组合实现总接口。
  • 便于扩展:未来若新增功能,只需添加新的接口,而无需修改已有代码。

2.2.2. 排行榜数据源接口

RankSourceIFace 用于管理排行榜的数据来源,例如从数据库或外部系统拉取数据。其方法定义如下:

type RankSourceIFace interface {
	// RankSourceItem 获取某个排行榜单项的数据
	RankSourceItem(ctx context.Context, rankListId RankListID, rankItemid RankItemID) (*RankZItem, error)
	// RankSourceRankList 获取排行榜列表
	RankSourceRankList(ctx context.Context, offset, limit int64) ([]RankListID, error)
	// RankSourceList 获取某个排行榜真实数据源
	RankSourceList(ctx context.Context, rankListId RankListID, offset, limit int64) ([]*RankZItem, error)
}
  • 方法解析
    • RankSourceItem:查询单个排行榜项。
    • RankSourceRankList:获取排行榜列表。
    • RankSourceList:获取某个排行榜真实数据源。

2.2.3. 排行榜打分器接口

RankScoreIFace 提供了分数的计算与反计算逻辑,支持灵活的分数管理:

type RankScoreIFace interface {
	// RankItemReScore 排行项重算分数
	RankItemReScore(ctx context.Context, item *RankZItem) (*RankZItem, error)
	// RankItemDeScore 排行项反算分数
	RankItemDeScore(ctx context.Context, item *RankZItem) (*RankZItem, error)
	// ReScore 重算分数
	ReScore(ctx context.Context, score int64, createTime int64) int64
	// DeScore 反算分数
	DeScore(ctx context.Context, encScore int64) (int64, int64)
}

  • 典型场景
    • 重算分数:玩家积分更新时重新计算分数。
    • 反算分数:排行榜中解码分数,便于显示原始信息。

2.2.4. 排行榜数据存储接口

RankStorageIFace 负责数据的增删改查,是排行榜功能的核心接口之一:

type RankStorageIFace interface {
	RankWriter
	RankReader
	ScoreRegister
}
  • 写操作接口
type RankWriter interface {
	StoreRankItem(ctx context.Context, rankListID RankListID, item *RankZItem, scope string) error // 单个存储
	BulkStoreRankItem(ctx context.Context, rankListID RankListID, items []*RankZItem, scope string) (int64, error) // 批量存储
	RemRankByItemId(ctx context.Context, rankListID RankListID, id RankItemID, scope string) error // 删除单个项
}
  • 读操作接口
type RankReader interface {
	GetRankItemById(ctx context.Context, rankListID RankListID, id RankItemID, scope string) (*RankZItem, error) // 查询单个成员
	RankList(ctx context.Context, rankListID RankListID, order RankOrder, offset, limit int64, scope string) ([]*RankZItem, error) // 查询排行榜
}
  • 注册打分器接口
type ScoreRegister interface {
	Register(Score RankScoreIFace) error
}

2.2.5. 排行榜重建机制接口

RankRebuildIFace 提供了数据的重建逻辑,用于修复或更新排行榜:

type RankRebuildIFace interface {
	Rebuild(ctx context.Context, RankListID RankListID) (int, error)
	RebuildAll(ctx context.Context) (int, error)
}
  • 典型场景:排行榜数据不一致或大规模更新时重建数据。

第三章. 排行榜功能接口的具体实现

在实现排行榜功能接口时,我们将重点放在如何利用 Redis 高效存储和操作数据,结合接口设计实现高度可扩展的功能模块。


3.1. Redis客户端的初始化

Redis 是排行榜系统的核心存储工具,其初始化需要考虑连接池配置和安全性。

初始化代码
以下代码展示了 Redis 客户端池的初始化:

redisPool := &redis.Pool{
    MaxIdle:     3,
    IdleTimeout: 240 * time.Second,
    Dial: func() (redis.Conn, error) {
        return redis.Dial("tcp", 
            "redis host", 
            redis.DialPassword("redis password"),
        )
    },
}
  • 连接池参数

    • MaxIdle:最大空闲连接数,确保在高并发情况下有足够的连接可用。
    • IdleTimeout:空闲连接的超时时间,避免长时间未使用的连接占用资源。
  • 安全性

    • 使用 redis.DialPassword 配置 Redis 密码,防止未授权访问。

3.2. 核心接口的实现

3.2.1. 总接口实现

RankSystem 是排行榜系统的总接口,其实现将所有子模块组合到一起:

type RankSystem struct {
    rank_kit.RankSourceIFace
    rank_kit.RankStorageIFace
    rank_kit.RankRebuildIFace
    rank_kit.RankScoreIFace
}
  • 组合接口:通过组合,RankSystem 封装了排行榜的所有核心功能,便于外部调用。

初始化代码

func NewRankSystem() (rank_kit.RankSystemIFace, error) {
    ranksource := &RankSource{}
    rankScorer := NewZSetRankScorer(DescTimeOrder)
    redisPool := &redis.Pool{/* 配置省略 */}
    storage := NewRedisStorage(redisPool, rankScorer)
    locker := NewLock()
    rankBuilder, err := NewRankRebuilder(ranksource, storage, 1, 4000, locker)
    if err != nil {
        return nil, err
    }
    return &RankSystem{
        RankSourceIFace:    ranksource,
        RankStorageIFace:   storage,
        RankRebuilderIFace: rankBuilder,
        RankScorerIFace:    rankScorer,
    }, nil
}

逻辑解析

  1. 初始化RankSource

  2. 初始化RankStorage

    • 初始化存储
    • 注入存储依赖
    • 注入打分器
  3. 初始化RankRebuild

    • 并发处理:利用 ants 工作池实现高效的并发处理,提升重建速度。
    • 错误处理:通过 multierr 累积所有发生的错误,确保重建过程中所有问题都能被记录。
    • 任务终止机制:一旦发生错误或读取到末尾,通过 finish 通道及时终止任务提交,避免不必要的资源浪费。
    • 结果汇总:通过专门的收集协程,安全地汇总处理数量和错误信息,确保数据一致性。
    • 锁机制:确保同一时间只有一个重建操作在进行,防止数据竞争和不一致问题。
  4. 初始化RankScore

    • 初始化打分器
    • 指定排序规则

3.2.2. 数据存储实现

数据存储是排行榜功能的核心模块,通过 Redis 的有序集合(Sorted Set)和哈希表实现高效的增删查改操作。

单个存储排行成员项
StoreRankItem 方法通过 ZADDHSET 存储数据:

func (r *RedisStorage) StoreRankItem(ctx context.Context, rankListID rank_kit.RankListID, item *rank_kit.RankZItem, scope string) error {
    conn, _ := r.redisPool.GetContext(ctx)
    defer conn.Close()

    // 重新计算分数
    newItem, err := r.RankScorer.RankItemReScore(ctx, item)
    if err != nil {
        return err
    }

    // 存储到 ZSet 和 Hash
    zk := r.zsetPrefix(rankListID, scope)
    zm := r.itemKey(string(newItem.RankItemID))
    ctxKey := r.hashPrefix(rankListID, scope)
    context, _ := json.Marshal(newItem.Context)

    _, err = conn.Do("ZADD", zk, newItem.Score, zm)
    if err != nil {
        return rank_kit.ErrRankStorageStoreRankItem
    }

    _, err = conn.Do("HSET", ctxKey, zm, context)
    return err
}
  • ZADD:存储排行榜项的分数和标识,支持按分数排序。
  • HSET:将上下文信息存储在哈希表中,用于查询详细数据。

查询排行榜
RankList 方法支持分页查询排行榜:

func (r *RedisStorage) RankList(ctx context.Context, rankListID rank_kit.RankListID, order rank_kit.RankOrder, offset int64, limit int64, scope string) ([]*rank_kit.RankZItem, error) {
    conn, _ := r.redisPool.GetContext(ctx)
    defer conn.Close()

    zk := r.zsetPrefix(rankListID, scope)
    var reply []string
    if order == rank_kit.AscRankOrder {
        reply, _ = redis.Strings(conn.Do("ZRANGE", zk, offset, offset+limit-1, "WITHSCORES"))
    } else {
        reply, _ = redis.Strings(conn.Do("ZREVRANGE", zk, offset, offset+limit-1, "WITHSCORES"))
    }

    return formatRankItemFromReplyStrings(reply)
}
  • 分页实现:通过 offsetlimit 参数实现结果分页,支持正序和倒序排序。

3.2.3. 数据源获取实现

RankSource 提供数据源接口的具体实现,支持从外部系统拉取排行榜数据。

func (f *RankSource) RankSourceList(ctx context.Context, rankListId rank_kit.RankListID, offset int64, limit int64) ([]*rank_kit.RankZItem, error) {
    // 示例实现:从数据库或其他存储获取数据
    return nil, nil
}

3.2.4. 重建机制实现

RankRebuild 模块实现排行榜数据的重建逻辑,支持高效批量处理。

重建单个排行榜

func (r *RankRebuild) Rebuild(ctx context.Context, RankListID rank_kit.RankListID) (int, error) {
  if locker, err := r.rebuildLock.Lock(ctx, string(RankListID)); err!= nil {
    return 0, err
  } else {
    defer func() {
      locker.Release(ctx)
    }()
  }

  // rebuild task start
  var result error
  var num int

  err := make(chan error)
  nums := make(chan int)
  done := make(chan struct{}, 1)
  finish := make(chan struct{}, 1)
  collectorFinish := make(chan struct{}, 1)

  wg := new(sync.WaitGroup)

  // 开启一个协程负责收集反馈的信息
  go func() {
    for {
      select {
      case <-done:
        close(collectorFinish)
        return
      case e := <-err:
        // 发生错误 终止任务
        result = multierr.Append(result, e)
        select {
        case <-finish:
        default:
          close(finish)
        }
      case n := <-nums:
        num = num + n
        // 如果收到0长度,证明已经循环到尾了
        if n == 0 {
          select {
          case <-finish:
          default:
            close(finish)
          }
        }
      }
    }
  }()

  // 循环提交读取数据的任务
ReadLoop:
  for i := 0; ; i++ {
    select {
    case <-finish:
      break ReadLoop
    default:
      wg.Add(1)
      offset := int64(i) * r.limit
      _ = r.pool.Invoke(newReadTask(ctx, RankListID, offset, r.limit, wg, nums, err, done))
    }
  }

  // 等待所有任务处理完成
  wg.Wait()
  close(done)
  <-collectorFinish

  return num, result
}

核心逻辑

  • 1.加锁
    • 锁定排行榜重建操作
  • 2.初始化变量和通道
    • result:用于累积所有可能发生的错误。
    • num:用于统计总处理的排行榜项数量。
    • err:用于接收各个任务中的错误。
    • nums:用于接收各个任务处理的数量。
    • done:用于通知任务终止。
    • finish:用于通知任务提交完成或发生错误需要终止。
    • collectorFinish:用于标识收集协程的完成。
  • 3.启动收集协程
    • 负责收集各个任务的错误和处理数量
    • 错误处理:一旦接收到错误,将其累积到 result 中,并触发 finish 通道以终止后续任务的提交。
    • 数量统计:累加每个任务处理的数量,如果接收到的数量为 0,说明已经处理完所有数据,触发finish通道。
    • 完成信号:当done通道被关闭时,关闭collectorFinish通道并结束协程。
  • 4.任务提交循环
    • 循环提交读取数据的任务,直到接收到finish信号。
  • 5.等待所有任务完成
    • 等待所有提交的任务完成,并确保收集协程也已结束。
  • 6.返回结果
    • num:总共处理的排行榜项数量。
    • result:可能发生的所有错误,使用multierr进行累积。

3.2.5. 打分器实现

打分器 ZSetRankScore 实现了复杂的分数计算规则,支持时间和分数的综合排序。

分数编码规则

func (z *ZSetRankScore) RankItemReScore(ctx context.Context, item *rank_kit2.RankZItem) (*rank_kit2.RankZItem, error) {

	tmp := *(item)

	tmp.Score = genScore(item.Score, genTimeScore(z.TimeOrder, int64(item.Time)))

	return &tmp, nil
}

func (z *ZSetRankScore) RankItemDeScore(ctx context.Context, item *rank_kit2.RankZItem) (*rank_kit2.RankZItem, error) {

	tmp := *(item)
	score := tmp.Score
	tmp.Score = deScore(score)
	tmp.Time = deCreateTime(score, z.TimeOrder)

	return &tmp, nil
}

func (z *ZSetRankScore) ReScore(ctx context.Context, score int64, createTime int64) int64 {
	return genScore(score, createTime)
}

func (z *ZSetRankScore) DeScore(ctx context.Context, score int64) (int64, int64) {
	return deScore(score), deCreateTime(score, z.TimeOrder)
}

核心逻辑

    1. 首位标志位不用,高31位存储分数,低32位存储时间;
    1. 如果时间倒序,则直接存储时间;
    1. 如果时间正序,则直接MAX_TIME-时间。

第四章. 排行榜功能的使用示例

在实现了排行榜的核心功能后,接下来展示如何在实际场景中调用这些功能。通过应用场景示例,说明如何使用排行榜系统进行积分更新、排行榜查询和数据管理。


4.1. 具体使用场景

本章以积分排行榜为例,演示如何通过 RankSystem 提供的功能接口,实现以下需求:

  1. 积分更新:用户的积分变动时更新排行榜。
  2. 排行榜查询:按月获取排行榜数据,包括当前用户的排名。
  3. 数据管理:定期刷新排行榜数据到数据库。

4.2. 积分更新

IntegralChangeHandle 方法处理用户积分的变更,将其写入 Redis 中的排行榜。

代码实现

type IntegralRankService struct {
  RankSystem rank_kit.RankSystemIFace
}

var singleGroup *singleflight.Group

func NewIntegralRankService() *IntegralRankService {
  integralRankSystem, _ := NewRankSystem()
  return &IntegralRankService{
    RankSystem: integralRankSystem,
  }
}

const MAX_RANK = 100

func (s *IntegralRankService) IntegralChangeHandle(ctx context.Context, opt *IntegralChange) error {
  updateTime := time.Unix(opt.UpdateTime, 0)

  yearMonth := getYearMonth(uint32(updateTime.Year()), uint32(updateTime.Month()))

  item, err := s.RankSystem.GetRankItemById(ctx, rank_kit.RankListID(strconv.Itoa(int(opt.ListId))), rank_kit.RankItemID(strconv.Itoa(int(opt.UserId))), yearMonth)
  if err!= nil {
    // 未找到排行榜
    if err == rank_kit.ErrRankStorageNotFoundItem {
      //从db 获取数据,重建
      item = &rank_kit.RankZItem{
        ItemContext: rank_kit.ItemContext{
          RankItemID: rank_kit.RankItemID(strconv.Itoa(int(opt.UserId))),
          Context: map[string]string{
            "业务key": "业务数据",
          },
        },
        Score: 0,
        Time:  time.Now().Unix(),
      }
    } else {
      return err
    }
  }

  item.Score = item.Score + opt.Integral

  // 写入当前配置
  if err = s.RankSystem.StoreRankItem(ctx, rank_kit.RankListID(strconv.Itoa(int(opt.ListId))), item, yearMonth); err!= nil {
    return err
  }

  return nil
}

逻辑解析

  1. 获取用户积分项

    • 调用 GetRankItemById 查询用户当前的积分信息。
    • 若未找到对应记录,则初始化用户积分项。
  2. 更新积分

    • 累加积分变动值到 item.Score
  3. 写入 Redis

    • 使用 StoreRankItem 更新排行榜。

4.3. 获取排行榜数据

IntegralRankMonthList 方法获取指定月份的积分排行榜,并返回当前用户的排名信息。

代码实现

func (s *IntegralRankService) IntegralRankMonthList(ctx context.Context, req *IntegralRankListRequest) (*IntegralRankListResponse, error) {
    yearMonth := getYearMonth(req.Year, req.Month)
    var RankSystem rank_kit.RankSystemIFace

    // 获取排行榜
    items, err := RankSystem.RankList(ctx, 
        rank_kit.RankListID(strconv.Itoa(int(req.IncentiveSchemeId))),
        rank_kit.DescRankOrder,
        0,
        MAX_RANK,
        yearMonth,
    )
    if err != nil {
        return nil, err
    }

    // 构建排行榜返回值
    var list []*RankItem
    for rank, item := range items {
        userId, _ := strconv.Atoi(string(item.RankItemID))
        list = append(list, &RankItem{
            UserId:             int64(userId),
            AddIntegralByMonth: item.Score,
            Rank:               uint32(rank + 1),
            Status:             1,
        })
    }

    // 查询当前用户排名
    self := &RankItem{UserId: int64(req.UserId)}
    if selfItem, err := RankSystem.GetRankItemById(ctx,
        rank_kit.RankListID(strconv.Itoa(int(req.IncentiveSchemeId))),
        rank_kit.RankItemID(strconv.Itoa(int(req.UserId))),
        yearMonth,
    ); err == nil {
        self.AddIntegralByMonth = selfItem.Score
        self.Status = 1
    }

    return &IntegralRankListResponse{
        Self:              self,
        List:              list,
        IncentiveSchemeId: req.IncentiveSchemeId,
        UpdatedTime:       time.Now().Unix(),
    }, nil
}

逻辑解析

  1. 获取排行榜数据

    • 通过 RankList 查询 Redis 中的排行榜数据。
  2. 构建返回数据

    • 根据 Redis 返回的用户数据,构造每个用户的排行榜项(RankItem)。
  3. 获取当前用户排名

    • 调用 GetRankItemById 查询当前用户在排行榜中的位置及积分。

4.4. 刷新数据到数据库

定期将排行榜数据写入数据库,便于后续数据分析和持久化存储。

代码实现

func (s *IntegralRankService) Flush(ctx context.Context, yearMonth string, force bool, incentiveSchemeID uint64) error {
    var RankSystem rank_kit.RankSystemIFace

    if force {
        _, err := RankSystem.Rebuild(ctx, rank_kit.RankListID(strconv.Itoa(int(incentiveSchemeID))))
        if err != nil {
            return err
        }
    }

    // 获取 Redis 数据
    items, err := RankSystem.RankList(ctx, 
        rank_kit.RankListID(strconv.Itoa(int(incentiveSchemeID))),
        rank_kit.DescRankOrder,
        0,
        MAX_RANK,
        yearMonth,
    )
    if err != nil {
        return err
    }

    // 写入数据库
    for _, item := range items {
        integralRank, _ := IntegralRankFromZItem(yearMonth, incentiveSchemeID, item)
        fmt.Println("写入数据库:", integralRank)
        // 执行数据库写入逻辑
    }

    return nil
}

逻辑解析

  1. 强制刷新排行榜

    • 调用 Rebuild 强制重建 Redis 中的排行榜数据。
  2. 获取 Redis 数据

    • 使用 RankList 查询 Redis 中存储的排行榜数据。
  3. 写入数据库

    • 将 Redis 中的排行榜数据逐条持久化到数据库。

4.5. 排行榜功能的扩展

支持多维度排行榜
通过 SetScope 方法,可以在不同的时间范围或分类下管理排行榜,例如:

  • 每日排行榜。
  • 每周排行榜。

数据归档与清理

  • 定期将 Redis 数据写入数据库后,删除 Redis 中的历史数据,释放内存。

第五章. 深入分析与对比

在实现了基于 Redis 的排行榜功能后,我们需要深入分析其优缺点,并与其他实现方式进行对比,以便更好地选择和优化。


5.1. 使用 Redis 实现排行榜的优缺点

优势

  1. 性能高效

    • Redis 的有序集合(Sorted Set)能够以 O(logN) 的时间复杂度完成插入、删除、查询操作。
    • 支持大规模并发访问,非常适合实时性要求高的排行榜场景。
  2. 操作便捷

    • 通过简单的命令(如 ZADDZRANGE),即可完成复杂的排序、分页查询。
  3. 高可用性与扩展性

    • Redis 支持主从复制和分片存储,能够在高并发场景下保持稳定性能。

局限

  1. 内存瓶颈

    • Redis 是内存型数据库,当数据量过大时,内存占用将成为瓶颈。
  2. 数据持久化成本

    • Redis 的快照(RDB)和日志(AOF)功能虽然提供了持久化支持,但会增加系统的复杂性和存储开销。
  3. 单点存储限制

    • 即使使用分片存储,单个 Redis 实例的存储能力仍有限制,需要仔细规划数据分布。

5.2. Redis 实现与其他方式的对比

5.2.1. 基于数据库的实现

  • 特点
    • 使用关系型数据库(如 MySQL)存储排行榜,依赖 SQL 查询实现排名计算。
  • 优点
    • 数据持久化能力强,适合长时间存储。
  • 缺点
    • SQL 排序操作效率较低,尤其在高并发场景中性能不佳。

5.2.2. 内存排序算法实现

  • 特点
    • 在应用层使用内存排序算法(如快速排序、堆排序)实时更新排行榜。
  • 优点
    • 不依赖外部存储,处理小规模数据时效率极高。
  • 缺点
    • 内存占用大,且无法持久化数据。

5.2.3. 对比

实现方式优势劣势
Redis高性能、高并发支持,实时性强内存消耗大,持久化复杂
数据库数据安全性强,适合长期存储性能不适合实时高并发场景
内存排序算法快速、高效,适合小规模数据数据无法持久化,不适合大规模排行榜场景

第六章. 总结与最佳实践

6.1. 排行榜功能的核心要点

  1. 数据结构选择

    • Redis 的有序集合是高效实现排行榜的核心,支持按分数排序、范围查询等操作。
  2. 系统解耦

    • 通过模块化接口设计,实现业务逻辑与数据存储的分离,便于扩展和维护。
  3. 实时性与持久化的权衡

    • 利用 Redis 提供的实时性能,并结合数据库完成持久化和归档。

6.2. 排行榜系统的设计原则

  1. 易维护性

    • 接口清晰,模块职责分明,便于开发和排查问题。
  2. 高性能

    • 减少 Redis 操作次数,采用批量操作和延迟加载等优化策略。
  3. 可扩展性

    • 支持多维度、多场景排行榜,灵活适应业务需求。

6.3. 最佳实践清单

  1. 定期数据清理

    • 定期将 Redis 数据归档到数据库,清理过期数据释放内存。
  2. 优化分数更新逻辑

    • 合并频繁的分数变更,减少 Redis 操作次数。
  3. 合理设计存储 Key

    • 根据业务场景设计有序集合的 Key,例如按时间范围或分类进行分组存储。
  4. 分布式扩展

    • 对于大规模排行榜,可使用 Redis Cluster 或分片技术进行水平扩展。

附录

附录A:完整代码示例
go-rank

附录B:常见问题与解答

  • :如何避免 Redis 内存占用过高?
    :定期归档数据到数据库,并设置过期时间清理历史数据。

  • :排行榜如何支持多维度?
    :通过 Scope 或 Key 分组,存储不同维度的排行榜数据。

附录C:Redis 常用命令参考

命令功能示例
ZADD添加元素并设置分数ZADD key score member
ZRANGE获取指定范围内的成员(正序)ZRANGE key start stop
ZREVRANGE获取指定范围内的成员(倒序)ZREVRANGE key start stop
ZSCORE获取成员的分数ZSCORE key member

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2266990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络攻防实践

1. 学习总结 黛蛇蠕虫案例&#xff1a; 原理&#xff1a;利用系统漏洞&#xff0c;并集成攻击代码。其中&#xff0c;通过蜜罐技术并进行数据分析所获取的攻击场景如下&#xff1a; 外部感染源攻陷蜜罐主机 执行Shellcode后获取主机权限后连接控制命令服务器&#xff0c;获取F…

寒假准备找实习复习java基础-day1

CMD常用命令&#xff1a; java跨平台原理&#xff1a; JRE和JVM Java基本数据类型

MacOS安装Xcode(非App Store)

文章目录 访问官网资源页面 访问官网资源页面 直接访问官网的历史版本下载资源页面地址&#xff1a;https://developer.apple.com/download/more/完成APP ID的登陆&#xff0c;直接找到需要的软件下载即可 解压后&#xff0c;安装将xcode.app移动到应用程序文件夹。

Docker 安装mysql ,redis,nacos

一、Mysql 一、Docker安装Mysql 1、启动Docker 启动&#xff1a;sudo systemctl start dockerservice docker start 停止&#xff1a;systemctl stop docker 重启&#xff1a;systemctl restart docker 2、查询mysql docker search mysql 3、安装mysql 3.1.默认拉取最新版…

gitlab克隆仓库报错fatal: unable to access ‘仓库地址xxxxxxxx‘

首次克隆仓库&#xff0c;失效了&#xff0c;上网查方法&#xff0c;都说是网络代理的问题&#xff0c;各种清理网络代理后都无效&#xff0c;去问同事&#xff1a; 先前都是直接复制的网页url当做远端url&#xff0c;或者点击按钮‘使用http克隆’ 这次对于我来说有效的远端u…

RK356x bsp 7 - PCF8563 RTC调试记录

文章目录 1、环境介绍2、目标3、PCF85634、dts配置5、内核配置6、测试验证 1、环境介绍 硬件&#xff1a;飞凌ok3568-c开发板 软件&#xff1a;原厂rk356x sdk 2、目标 开发板断电后仍正常计时。 3、PCF8563 PCF8563 是由 NXP Semiconductors 公司生产的低功耗 CMOS 实时…

图研院 | 掌握前沿图技术,从 “Graph XAI” 课程起航

在如今数字化转型加速的时代浪潮下&#xff0c; 图数据库技术正成为众多领域突破创新的关键力量&#xff01; 你是否也渴望深入了解其背后的核心知识&#xff0c; 开启自己的图技术进阶之旅&#xff1f; 由机工社精心打造的系列大师课&#xff0c; 特邀国际级专家/学者师资…

Oracle考试多少分算通过?

OCP和OCM认证的考试及格分数并不是固定的&#xff0c;而是根据考试的难度和考生的整体表现来确定。对于OCP认证&#xff0c;考生需要全面掌握考试要求的知识和技能&#xff0c;并在考试中表现出色才有可能通过。而对于OCM认证&#xff0c;考生则需要在每个模块中都达到一定的水…

01.HTTPS的实现原理-HTTPS的概念

01.HTTPS的实现原理-HTTPS的概念 简介1. HTTPS的概念和安全性2. HTTPS的实现原理3. HTTPS和HTTP的区别4. OSI七层协议模型5. SSL和TLS的区别 简介 该系列文章主要讲述了HTTPS协议与HTTP协议的区别&#xff0c;以及HTTPS如何实现安全传输。内容分为三部分&#xff1a;HTTPS的实…

教师管理系统

大概功能&#xff1a; 1.显示所有教师 2.按姓名查找教师 3.按工号查找教师 4.增加教师 5.删除教师 6.退出 数据会保存到 txt 文件里面 姓名&#xff1a;必须是中文 手机号码&#xff1a;必须是11位&#xff0c;必须是数字 效果展示&#xff1a; 代码展示&#xff1a; Teache…

CSES-1687 Company Queries I(倍增法)

题目传送门https://vjudge.net/problem/CSES-1687#authorGPT_zh 解题思路 其实和倍增法求 LCA 是一样的…… 首先设 表示 号点的上面的第 个祖先是谁。 同倍增法&#xff1a; 然后&#xff0c;题目要求我们向上跳 个点。 枚举 &#xff08;从大到小&#xff0c;想想为…

自动驾驶控制算法-横向误差微分方程LQR前馈控制

本文是学习自动驾驶控制算法第六讲 前馈控制与航向误差以及前两节的学习笔记。 1 横向误差微分方程 以规划的轨迹作为自然坐标系&#xff0c;计算自车在轨迹上的投影点&#xff0c;进而计算误差&#xff1a; 如图所示&#xff0c;横向误差为 d d d&#xff0c;航向误差为 θ…

后端开发如何高效使用 Apifox?

Apifox 是一个 API 协作开发平台&#xff0c;后端、前端、测试都可以使用 Apifox 来提升团队的工作效率。对于后端开发者而言&#xff0c;Apifox 的核心功能主要包括四个模块&#xff1a;调用 API、定义 API、开发与调试 API 以及生成 API 文档。本文将详细介绍后端开发人员如何…

【Unity3D】ECS入门学习(六)状态组件 ISystemStateComponentData

当需要获知组件是否被销毁时&#xff0c;ECS是没有回调告知的&#xff0c;因此可以将组件继承于ISystemStateComponentData接口&#xff0c;这样即使组件的实体被销毁了&#xff0c;该组件本身是不会消失的&#xff0c;所以可以通过在组件实体销毁后&#xff0c;去设置状态组件…

LeetCode 19:删除链表的倒数第N 个结点

题目&#xff1a; 地址&#xff1a;https://leetcode.cn/problems/remove-nth-node-from-end-of-list/ 方法一&#xff1a; 方法二&#xff1a; 代码&#xff1a; package com.zy.leetcode.LeetCode_19;/*** Author: zy* Date: 2024-12-25-13:01* Description: 删除链表…

中学数学:一个函数值计算题

在数学的领域中&#xff0c;函数是一种描述变量之间关系的桥梁&#xff0c;它能够揭示出看似复杂现象背后的简洁规律。通过函数&#xff0c;我们可以预测、分析并解决实际问题。在这张图片中&#xff0c;我们看到了一位数学爱好者手写的解题过程&#xff0c;它展示了如何巧妙地…

kipotix4靶机实战

信息收集 1.判断靶机ip 原理&#xff1a;开靶机之前nmap扫一次网段&#xff0c;再开靶机之后扫一次&#xff0c;查看多出来的ip就是靶机ip ip192.168.98.1742.判断端口服务&#xff0c;系统版本 a.确定端口 b.-p指定端口进一步收集 c.信息筛选 1.端口&#xff1a;22,80,139,…

Xilinx FPGA的Bitstream比特流加密设置方法

关于Xilinx FPGA的Bitstream比特流加密设置方法更多信息可参阅应用笔记xapp1084。 使用加密bitstream分两个步骤&#xff1a; 将bitstream的AES密钥存储在FPGA芯片内将使用AES密钥加密的Bitstream通过SPI Flash或JTAG加载至FPGA芯片 AES密钥可以存储在两个存储区之一&#x…

菜鸟带新鸟——基于EPlan2022的部件库制作(3D)

设备逻辑的概念&#xff1a; 可在布局空间 中和其它对象上放置对象。可将其它对象放置在 3D 对象上。已放置的对象分到组件的逻辑结构中。 将此属性的整体标识为设备逻辑。可使用不同的功能创建和编辑设备逻辑。 设备的逻辑定义 定义 / 旋转 / 移动 / 翻转&#xff1a;组…

理解有放回和无放回抽样 (Python)

理解有放回和无放回抽样 (Python) 文章目录 一、说明二、放回抽样模型概念2.1 如何实现放回抽样2.2 使用 NumPy 进行替换抽样2.3 使用 Pandas 进行替换抽样 三、基本统计原理四、什么是无放回抽样4.1 使用 NumPy 进行无放回抽样4.1 数据科学中不重复抽样的例子 五、结论 一、说…