欢迎来到窥视未来的博客

https://github.com/lwx57280 https://gitee.com/li_VillageHead

老板说“搞个实时排行榜”,我用Redis ZSet + SpringBoot 2小时搞定,性能直接拉满

一、需求来了:百万用户的积分排行榜,要实时!

兄弟们,你们遇到过这种需求吗?产品经理跑过来说:“我们要做一个用户积分排行榜,要实时更新的那种,用户一有积分变动,榜单马上就能看到。”

我当时心里一紧:用户量百万级,积分实时变动,还要毫秒级响应——这不就是排行榜的经典场景吗?

如果直接用MySQL做排序,ORDER BY score DESC LIMIT 10,在百万数据下,即使有索引,查询也要几十毫秒甚至上百毫秒。而且每次用户积分变动都要重新计算排名,数据库根本扛不住。

我当时第一反应就是:用Redis的有序集合(ZSet)。ZSet底层是跳表(skiplist),插入、更新、查询排名的时间复杂度都是O(log N),百万级数据轻松应对

今天就把这个完整方案从零到一复盘出来,包含源码、配置、API和踩坑点。

 

二、技术选型:为什么是Redis ZSet?

 
方案优点缺点适用场景
MySQL ORDER BY 实现简单 百万级数据排序慢,频繁更新压力大 数据量小、非实时
Redis ZSet 读写快(O(log N)),天然支持排序 内存占用 实时排行榜首选
Elasticsearch 功能强大,支持复杂聚合 太重,运维成本高 大数据分析场景

Redis ZSet(有序集合)的每个元素都关联一个double类型的分数,按分数从小到大排序。它天然支持:

  • ZADD:添加/更新元素分数

  • ZREVRANK:获取元素排名(分数从高到低)

  • ZREVRANGE:按分数从高到低获取指定范围的元素

一句话总结:排行榜核心数据放Redis,MySQL只做用户基础信息持久化,不把排序计算放数据库

 

三、项目实战:完整代码实现

spring-boot-redis-rank-learn/
├── src/main/java/com/mate/cloud/redis/rank/
│   ├── constants/
│   │   └── UserRankConstants.java          # Redis Key常量
│   ├── entity/
│   │   └── SysUserScore.java               # 用户积分实体
│   ├── mapper/
│   │   └── SysUserScoreMapper.java         # MyBatis-Plus Mapper
│   ├── service/
│   │   ├── UserScoreRankService.java       # 服务接口
│   │   └── impl/
│   │       └── UserScoreRankServiceImpl.java # 核心实现
│   └── controller/
│       └── UserScoreRankController.java    # REST API
└── src/main/resources/
    ├── application.yml                     # 配置文件
    └── mapper/
        └── SysUserScoreMapper.xml          # MyBatis XML

3.2 Maven依赖

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Boot Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot-starter</artifactId>
        <version>3.5.5</version>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

 

3.3 application.yml 配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${MYSQL_HOST:mate-mysql}:3306/learn?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
    username: root
    password: root
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      database: 0
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5

mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  configuration:
    map-underscore-to-camel-case: true

 

3.4 MySQL建表脚本

MySQL只存储用户基础信息,排行榜实时排序完全交给Redis ZSet,MySQL不做排序计算

CREATE DATABASE IF NOT EXISTS rank_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE rank_demo;

CREATE TABLE `sys_user_score` (
    `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
    `user_id` VARCHAR(64) NOT NULL COMMENT '用户唯一id',
    `user_name` VARCHAR(64) NOT NULL COMMENT '用户名称',
    `score` DECIMAL(18, 2) NOT NULL DEFAULT 0.00 COMMENT '用户积分',
    `is_del` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除 0:否 1:是',
    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` datetime DEFAULT NULL COMMENT '修改时间',
    `create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
    `update_by` varchar(50) DEFAULT NULL COMMENT '修改人',
    UNIQUE KEY uk_user_id (`user_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='用户积分表';

3.5 实体类 SysUserScore

package com.mate.cloud.redis.rank.entity;

import com.mate.cloud.mybatis.bases.BasesModel;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serial;
import java.math.BigDecimal;

/**
 * 用户积分
 *
 * @author: MI
 * @email: 448341911@qq.com
 * @createTime: 2026/8/22 11:36
 * @updateUser: MI
 * @updateTime: 2026/8/22 11:36
 * @updateRemark: 修改内容
 * @version: 1.0
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class SysUserScore extends BasesModel<SysUserScore> {

    @Serial
    private static final long serialVersionUID = 4576620049851639248L;
    /**
     * 用户id
     */
    private String userId;
    /**
     * 用户名
     */
    private String userName;
    /**
     * 用户积分
     */
    private BigDecimal score;
}

3.6 Redis常量

package com.mate.cloud.redis.rank.constants;

public class UserRankConstants {
    /** redis score key:积分排行榜 */
    public static final String RANK_USER_SCORE = "rank:user:score";
}

3.7 Mapper 接口

SysUserScoreMapper.java

package com.mate.cloud.redis.rank.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mate.cloud.redis.rank.entity.SysUserScore;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.math.BigDecimal;
import java.util.List;

/**
 * 用户积分Mapper接口
 *
 * @author: MI
 * @email: 448341911@qq.com
 * @createTime: 2026/8/22 11:51
 * @updateUser: MI
 * @updateTime: 2026/8/22 11:51
 * @updateRemark: 修改内容
 * @version: 1.0
 */
@Mapper
public interface SysUserScoreMapper extends BaseMapper<SysUserScore> {

    /**
     * 批量新增用户积分数据(百万初始化使用)
     *
     * @param list 实体列表
     * @return 影响行数
     */
    int batchInsert(@Param("list") List<SysUserScore> list);


    /**
     * 根据userId更新分数
     */
    int updateScoreByUserId(@Param("userId") String userId, @Param("score") BigDecimal score);

}

  

3.8 Service 接口

package com.mate.cloud.redis.rank.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.mate.cloud.redis.rank.entity.SysUserScore;
import com.mate.cloud.redis.rank.query.SysUserScoreQuery;
import com.mate.cloud.redis.rank.vo.SysUserScoreVO;
import org.springframework.data.redis.core.ZSetOperations;

import java.math.BigDecimal;
import java.util.Set;

/**
 * 用户积分排行榜Service接口
 *
 * @author: MI
 * @email: 448341911@qq.com
 * @createTime: 2026/8/22 11:51
 * @updateUser: MI
 * @updateTime: 2026/8/22 11:51
 * @updateRemark: 修改内容
 * @version: 1.0
 */
public interface UserScoreRankService extends IService<SysUserScore> {

    /**
     * 清空排行榜redis数据
     */
    void clearRank();

    /**
     * 生成测试数据,同时写入mysql + redis zset
     *
     * @param count 数据量
     */
    void generateTestData(int count);

    /**
     * 更新用户积分:覆盖模式
     *
     * @param userId 用户id
     * @param score  分数
     */
    void updateUserScore(String userId, double score);

    /**
     * 用户积分累加
     *
     * @param userId   用户id
     * @param addScore 增加的分数
     */
    void addUserScore(String userId, double addScore);

    /**
     * 获取用户排名 从1开始;reverseRank=true:分数越高排名越靠前
     *
     * @param userId 用户id
     * @return 排名,null代表不在榜单
     */
    Long getUserRank(String userId);

    /**
     * 获取榜单分页数据
     *
     * @param start   起始下标 0
     * @param end     结束下标
     * @param reverse true:降序(高分在前) false升序
     * @return 用户+分数集合
     */
    Set<ZSetOperations.TypedTuple<String>> getTopUsers(long start, long end, boolean reverse);

    /**
     * 根据userId更新分数
     */
    int updateScoreByUserId(String userId, BigDecimal score);

    /**
     * 用户积分排行榜分页查询
     *
     * @param query
     * @return
     */
    IPage<SysUserScoreVO> listPage(SysUserScoreQuery query);
}

  

  

3.9 Service核心实现

package com.mate.cloud.redis.rank.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mate.cloud.core.util.BeanMapper;
import com.mate.cloud.redis.rank.constants.UserRankConstants;
import com.mate.cloud.redis.rank.entity.SysUserScore;
import com.mate.cloud.redis.rank.mapper.SysUserScoreMapper;
import com.mate.cloud.redis.rank.query.SysUserScoreQuery;
import com.mate.cloud.redis.rank.service.UserScoreRankService;
import com.mate.cloud.redis.rank.vo.SysUserScoreVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;

/**
 * 用户积分排行实现
 *
 * @author: MI
 * @email: 448341911@qq.com
 * @createTime: 2026/8/22 11:44
 * @updateUser: MI
 * @updateTime: 2026/8/22 11:44
 * @updateRemark: 修改内容
 * @version: 1.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserScoreRankServiceImpl extends ServiceImpl<SysUserScoreMapper, SysUserScore> implements UserScoreRankService {

    private final RedisTemplate<String, Object> redisTemplate;

    @Override
    public void clearRank() {
        redisTemplate.delete(UserRankConstants.RANK_USER_SCORE);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generateTestData(int count) {
        // 清理缓存
        clearRank();
        // 每批次大小,可调整1000‑2000
        final int BATCH_SIZE = 1000;
        int loop = count / BATCH_SIZE;
        int remain = count % BATCH_SIZE;

        for (int i = 0; i < loop; i++) {
            List<SysUserScore> dbList = new ArrayList<>(BATCH_SIZE);
            Set<ZSetOperations.TypedTuple<Object>> redisTupleSet = new HashSet<>(BATCH_SIZE);

            for (int j = 0; j < BATCH_SIZE; j++) {
                String userId = "u_" + UUID.randomUUID().toString().substring(0, 12);
                String userName = "用户_" + (i * BATCH_SIZE + j);
                double score = Math.random() * 100000;

                SysUserScore userScore = new SysUserScore();
                userScore.setUserId(userId);
                userScore.setUserName(userName);
                userScore.setScore(BigDecimal.valueOf(score));
                dbList.add(userScore);

                //组装redis zset批量元组
                ZSetOperations.TypedTuple<Object> tuple = ZSetOperations.TypedTuple.of(userId, score);
                redisTupleSet.add(tuple);
            }
            //mysql批量插入
            baseMapper.batchInsert(dbList);
            //redis zset批量添加
            redisTemplate.opsForZSet().add(UserRankConstants.RANK_USER_SCORE, redisTupleSet);
        }
    }

    /**
     * 覆盖设置分数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserScore(String userId, double score) {
        //1.更新mysql
        SysUserScore userScore = new SysUserScore();
        userScore.setUserId(userId);
        userScore.setScore(BigDecimal.valueOf(score));
        baseMapper.updateById(userScore);
        //2.覆盖redis zset分数
        redisTemplate.opsForZSet().add(UserRankConstants.RANK_USER_SCORE, userId, score);
    }

    /**
     * 分数累加
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addUserScore(String userId, double addScore) {

//        String redisKey = String.format(UserRankConstants.RANK_USER_SCORE, userId, addScore);

        // redis zset 原子累加
        Double newScore = redisTemplate.opsForZSet().incrementScore(UserRankConstants.RANK_USER_SCORE, userId, addScore);
        BigDecimal scoreVal = BigDecimal.valueOf(newScore);
        log.info("分数累加 score:{}", scoreVal);
        // 更新mysql持久化
        updateUserScore(userId, scoreVal);
    }


    private void updateUserScore(String userId, BigDecimal scoreVal) {
        // 根据userId查询记录
        SysUserScore exist = baseMapper.selectOne(Wrappers.<SysUserScore>lambdaQuery()
                .eq(SysUserScore::getUserId, userId));

        if (exist != null) {
            //存在:更新分数
            exist.setScore(scoreVal);
            baseMapper.updateById(exist);
        } else {
            //不存在:新增
            SysUserScore userScore = new SysUserScore();
            userScore.setUserId(userId);
            userScore.setScore(scoreVal);
            baseMapper.insert(userScore);
        }
    }

    @Override
    public Long getUserRank(String userId) {
        // zrevrank:分数越大,排名数字越小(第一名=0下标,返回值+1转为业务排名从1开始)
        Long rank = redisTemplate.opsForZSet().reverseRank(UserRankConstants.RANK_USER_SCORE, userId);
        if (rank == null) {
            return null;
        }
        return rank + 1;
    }

    @Override
    public Set<ZSetOperations.TypedTuple<String>> getTopUsers(long start, long end, boolean reverse) {
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
        if (reverse) {
            // 降序:高分在前
            return (Set) zSetOps.reverseRangeWithScores(UserRankConstants.RANK_USER_SCORE, start, end);
        } else {
            // 升序:低分在前
            return (Set) zSetOps.rangeWithScores(UserRankConstants.RANK_USER_SCORE, start, end);
        }
    }

    @Override
    public int updateScoreByUserId(String userId, BigDecimal score) {
        return baseMapper.updateScoreByUserId(userId, score);
    }

    @Override
    public IPage<SysUserScoreVO> listPage(SysUserScoreQuery query) {
        LambdaQueryWrapper<SysUserScore> queryWrapper = Wrappers.lambdaQuery();
        Page<SysUserScore> page = new Page<>(query.getCurrent(), query.getSize());
        // 分页查询
        IPage<SysUserScore> sysUserScorePage = baseMapper.selectPage(page, queryWrapper);
        // entity → VO转换
        List<SysUserScore> sysUserScoreList = sysUserScorePage.getRecords();
        List<SysUserScoreVO> userScoreVOList = BeanMapper.convert(sysUserScoreList, SysUserScoreVO.class);

        // 构建VO分页对象,复制分页元信息:当前页、页大小、总条数、总页数,设置转换后的VO集合
        Page<SysUserScoreVO> voPage = new Page<>(query.getCurrent(), query.getSize(), sysUserScorePage.getTotal());
        return voPage.setRecords(userScoreVOList);
    }
}

关键点说明

  1. incrementScore是原子操作,Redis保证并发安全

  2. 先更新Redis再更新MySQL,保证实时性

  3. reverseRank返回的是下标(从0开始),业务排名需要+1

4.0 Controller

package com.mate.cloud.redis.rank.controller;


import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mate.cloud.protocol.response.BaseResponse;
import com.mate.cloud.protocol.web.WebException;
import com.mate.cloud.protocol.web.controller.AdminBaseController;
import com.mate.cloud.redis.rank.query.SysUserScoreQuery;
import com.mate.cloud.redis.rank.service.UserScoreRankService;
import com.mate.cloud.redis.rank.vo.SysUserScoreVO;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * 用户积分排行榜
 *
 * @author astupidcoder
 * @since 2021-06-26
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/rank")
public class UserScoreRankController extends AdminBaseController {

    private final UserScoreRankService userScoreRankService;

    /**
     * 分页查询用户积分排行榜
     *
     * @param page
     * @param sysDict
     * @return
     */
    @PostMapping("/list")
    public BaseResponse<IPage<SysUserScoreVO>> list(@RequestBody SysUserScoreQuery query) {
        return successBodyResponse(userScoreRankService.listPage(query));
    }

    /**
     * 初始化测试数据
     */
    @PostMapping("/init")
    public BaseResponse<String> initData(@RequestParam int count) {
        userScoreRankService.clearRank();
        userScoreRankService.generateTestData(count);
        return successBodyResponse("初始化" + count + "条数据成功");
    }

    /**
     * 覆盖更新用户积分
     */
    @PostMapping("/update")
    public BaseResponse updateScore(@RequestParam String userId, @RequestParam BigDecimal score) {

        return successBodyCondition(userScoreRankService.updateScoreByUserId(userId, score) >= 1);
    }

    /**
     * 积分累加
     */
    @PostMapping("/add")
    public BaseResponse<String> addScore(@RequestParam String userId, @RequestParam double addScore) throws WebException {
        if (userId == null) {
            _throwEx("userId不能为空"); //来自BaseRest
        }

        userScoreRankService.addUserScore(userId, addScore);
        return successCodeResponse(); //响应代理
    }

    /**
     * 查询用户排名
     */
    @GetMapping("/rank")
    public BaseResponse<String> getRank(@RequestParam String userId) {
        Long rank = userScoreRankService.getUserRank(userId);
        return successMsgResponse(rank == null ? "未上榜" : "当前排名:" + rank);
    }

    /**
     * 获取榜单分页,reverse=true降序(高分在前)
     * start=0 end=9 获取top10
     */
    @GetMapping("/top")
    public BaseResponse<List<String>> getTop(@RequestParam(defaultValue = "0") int start,
                                             @RequestParam(defaultValue = "9") int end,
                                             @RequestParam(defaultValue = "true") boolean reverse) {
        Set<ZSetOperations.TypedTuple<String>> topUsers = userScoreRankService.getTopUsers(start, end, reverse);
        Collector<String, ?, List<String>> stringListCollector = Collectors.toList();
        return successBodyResponse(topUsers.stream()
                .map(t -> t.getValue() + "(积分:" + String.format("%.2f", t.getScore()) + ")")
                .collect(stringListCollector));
    }
}

4.1 RedisTemplate配置(解决序列化乱码)

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer jsonSerializer =
            new GenericJackson2JsonRedisSerializer();

        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

四、性能实测:百万数据毫秒级响应

 
操作Redis命令时间复杂度百万数据实测
插入/更新积分 ZADD O(log N) < 1ms
积分累加 ZINCRBY O(log N) < 1ms
查用户排名 ZREVRANK O(log N) < 1ms
查TOP榜单 ZREVRANGE O(log N + M) < 1ms

百万级数据下,ZSet的增删改查基本都能做到10ms以内

内存评估:100万条ZSet,单条userId为短字符串,占用内存大概几十MB级别

五、踩坑与避坑指南

坑1:排名从0开始

Redis ZSet的rank返回的是下标(从0开始),业务展示排名需要+1

// ❌ 错误:直接返回rank
return rank;  // 第一名返回0

// ✅ 正确:+1后返回
return rank + 1;  // 第一名返回1

坑2:分数相同时的排序规则

Redis ZSet分数相同时,按照元素的字典序排序。如果业务需要自定义同分规则(如先达到该分数的排前面),需要额外处理。

解决方案:将分数设计为score + 时间戳/1000000000的组合,保证分数相同时先达到的排前面。

坑3:数据一致性问题

更新时同时写MySQL和Redis,极端场景下Redis宕机会出现不一致

生产解决方案

  • 定时任务:定期将MySQL的score全量刷入ZSet做兜底同步

  • 或者使用Canal监听MySQL binlog,异步更新Redis

坑4:不要一次性获取全部数据

// ❌ 危险:百万数据一次性拉取,内存爆炸
Set<String> all = redisTemplate.opsForZSet().range(key, 0, -1);

// ✅ 正确:分页获取
Set<String> page = redisTemplate.opsForZSet().reverseRange(key, start, end);

坑5:ZSet的Key设计

日榜、周榜、总榜需要不同的ZSet Key

rank:day:2026-08-22    # 日榜
rank:week:2026-34      # 周榜
rank:total             # 总榜

搭配Redis过期时间自动清理历史榜单。

六、生产环境部署建议

6.1 Redis配置建议

# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru   # 内存满时淘汰冷数据

6.2 降级方案

Redis不可用时,降级读取MySQL做排序(性能差,仅作为最后兜底)

public List<SysUserScore> getTopFromMySQL(int limit) {
    return baseMapper.selectList(
        new LambdaQueryWrapper<SysUserScore>()
            .orderByDesc(SysUserScore::getScore)
            .last("LIMIT " + limit)
    );
}

6.3 API接口汇总

接口说明
POST /rank/init?count=1000000 初始化百万测试数据
POST /rank/update?userId=u_xxx&score=8888 覆盖设置用户分数
POST /rank/add?userId=u_xxx&addScore=100 用户积分累加
GET /rank/rank?userId=u_xxx 查询用户排名(1 开始)
POST /rank/list 用户积分排名分页查询
GET /rank/top?start=0&end=9&reverse=true 获取 TOP10 排行榜

 

七、总结

 
核心要点说明
技术选型 Redis ZSet天然支持排序,O(log N)复杂度,百万数据毫秒响应
数据架构 Redis存排行榜实时数据,MySQL存用户基础信息做兜底
核心操作 ZADD更新、ZREVRANK查排名、ZREVRANGE查TOP榜单
原子性 ZINCRBY保证积分累加的原子性,无需额外加锁
避坑要点 rank从0开始需+1;同分按字典序;大榜必须分页;做好Redis-MySQL一致性兜底

一句话总结:Redis ZSet + SpringBoot,2小时搞定百万级实时排行榜,性能拉满,成本可控。

兄弟们,你们在生产环境中做过排行榜功能吗?遇到过同分排序、数据一致性、大榜分页这些问题吗?

posted on 2026-08-22 18:56  k8s-Mango  阅读(8)  评论(0)    收藏  举报

导航