java 篇 1.基础地基 2.设计原理 3.项目实战实时排行榜思路分析积分累加键名相当于文件名成员行分数列z1u118z1u225z2u1100查询 提供键和值查询排行从小到大排从 0 开始从大到小排从 0 开始开始编写代码先定义前缀这里时间戳得格式化所以需要在 common 包下的 utils 当中加个格式化器这里没有:了因为前面没有 uid 了接下来就是在 PointsRecordServiceImpl 当中添加记录后在增加一步累积积分数据到 Redis 的 SortedSet进行测试重新签到一下有记录测试通过实现学霸积分榜接口展示数据可以分成两部分一个是我的积分和排名另一个是榜单列表将二者拼接返回。为啥要单独查我的呢因为你不一定能上榜。之后又得分查的是当前榜单还是历史榜单。避免臃肿以及达到逻辑清晰的目的于是用 4 个方法来实现。咱们先来实现查询当前页面对着表来做为了避免每次都传入 key,所以我们事先把 key 绑定住之后我们只需要传入 userId就可以查询用户的积分和排名了。最后封装的时候 rank 记得 1.接着就是查询榜单列表注意亮点①记得带上 WithScores②并不是每页都是从 1 开始的是从 from1 开始的再后面就是封装了这没啥好说的之前都写过很多个了逻辑理清就行。测试一下通过了萝丝,名字没有显示是因为数据库没有数据库的分区和分表(历史排行榜:分区(数据存储方案)表数据越多.ibd 文件越大小的时候可能只有几十兆几百兆当数据规模很大时可能达到几个 GB甚至 TB,占用的分区越多,做数据检索时经历的磁盘 IO 次数也会越多性能也会越差。水平拆分按赛季切**优点****提高数据检索性能**现在一个赛季就放在.ibd 文件当中了占用的分区也就小了磁盘 IO 次数也就少了。**提高统计性能**对整张表做一种类似求和或者计数的操作原来得在整张表进行操作现在可以用多个线程并行处理多个分区的数据最后汇总。**打破磁盘容量限制**对表做了分区以后还可以给每个分区指定存储位置不同的分区可以在不同的磁盘。那这就可以无限扩容。**根据分区清理数据效率高**比如说某个数据太旧了根本没人看了那你直接把对应的.ibd 文件删了就行。比执行 Mysql 语句的效率高。**对业务没有影响**虽然在物理上拆出来多个表但是在逻辑上还是一张表意思说进行 CRUD 时业务逻辑根本不用改**缺点****分区字段必须是索引的一部分**比如将 season 字段作为分区字段因为 season 字段有很多重复值不可能单独建立索引只能走联合索引和主键绑在一起mp 不支持联合主键而它本身又是很多重复的再建立索引其实没啥意义。**分区方式不够灵活**① 按范围range( , )② 按枚举男女③ 按哈希值某个字段求哈希值对数量取模**只支持水平分区**分表(表设计方案)**水平分表**逻辑和实际都是多张表需要通过业务逻辑判断拆分灵活**垂直分表**宽表会导致单行数据量过多那存储的数据量(行数)就会减少查询性能下降。只想要当中某些字段却把所有字段查出来了性能差。再说如果有个字段更改频繁写频繁那它会加锁那就影响查了性能变差。所以宽表不可取得按字段进行拆分成不同表但他们得有关联(外键关联),CRUD 时注意一下。实际开发中追求这种灵活性以及垂直拆分的能力因此都会采用分表的方案。分库和集群这里的学习库集群是结构相同但是数据不同水平分表。课程库不一样是为了防止高并发所以数据是相同的。定时生成历史榜单表1.获取上月时间当前时间减去一个月的时间当然减去 2 天也可以。2.查询赛季 id3.创建表le,ge 和 lt,gt 的区别前者为闭区间后者为开区间。Optional 是 Java 8 引入的容器类用来优雅地处理可能为 null 的值oneOpt() 的含义one查询一条记录OptOptional 的缩写返回类型Optional而不是直接返回对象或 null查询结果与 Optional 的对应关系Optional 是一个包装器明确告诉调用者这个值可能不存在强制你处理空值情况避免 NullPointerException配合 orElse()、ifPresent()、orElseThrow() 等方法使用更安全return 注释了两者写法都可以不过没注释的优雅一点。创建表部分和 本质是 的特例用 delete 标签都能跑网友提问为什么每次判断 null 值的时候有的时候是 return 有的是返回一个空的集合有的时候又是抛出异常怎么区别啊判断 null 后如何处理取决于业务语义和调用方的期望。**1. 返回静默处理 - 正常业务场景**当 null 是预期内的正常情况不需要调用方关心时适用场景批量处理中跳过无效数据定时任务、触发器可选功能有就执行没有就算了查询列表为空也算正常结果**2. 返回空集合 - 查询结果的正常情况**当方法契约明确表示会返回集合空集合比 null 更合理适用场景查询方法返回集合类型避免调用方判空可以直接 foreach**3. 抛出异常 - 异常业务场景**当 null 代表程序无法继续的致命错误时适用场景前置条件不满足参数校验失败依赖的核心数据不存在配置缺失导致系统无法运行API 接口参数错误总结口诀正常可预期 → return / 空集合错误不应当 → 抛异常可选返回值 → Optional批量循环 → 跳过continue/return核心逻辑 → 必须抛异常关键是保持一致性同一个项目中类似场景要用相同处理方式否则会混乱。学习服务压力很大所以不可能单点去部署那肯定会负载均衡水平扩展部署成多个实例。那这样就会有多个定时任务那其实只需要一个就够了。然后表创建完了还需要将 Redis 当中的数据持久化那就需要后续的定时任务去做了那这两个定时任务需要保证先后顺序。分布式任务调度的常见技术XXL-JOB 快速入门adminAddresses:从.env 文件当中读取填虚拟机的地址和端口就行。appname:注册到调度中心的这个应用的名称一般都用当前微服务的名称如 learning serviceip 端口不用配默认自己读取accessToken:访问令牌提前在调度中心配好了作为访问授权的一个密钥logPath:运行时保存的一个目录logRetentionDays:日志保存的有效期属性配置这是一个 Spring Boot 配置属性绑定类用于将配置文件application.yml 或 application.properties中以 tj.xxl-job 开头的配置项自动绑定到这个 Java 对象中。主要就写划红线的字段从黄色划横线的配置文件当中去读。读完了之后配置执行器把东西一个个塞进去。这就完成了自动装配了。需要我们做的是在 yml 文件里,写配置的属性。当然这里也不用了nacos 当中已经共享配置了橙色划线保持一致把原来的 Scheduled 换成 XxlJob,定义好任务。接着把执行器注册到调度中心把任务注册到调度中心。启动之后执行器会自动注册到调度中心之后在管理页面填一下信息就完了任务也是在管理页面当中去填。自动注册不需要填机器地址手动需要。名称得跟微服务名称保持一致IDE 上启动服务就有了地址本机接下来就是注册任务想测试就直接点击保存控制台就有 sql 语句输出咦怎么没看到建表语句哦原来这个文件放在别的模块下了cc 还是牛的查看调度日志MybatisPlus 的动态表名插件定义的表名处理器只有一个方法如果一个接口里只有一个方法属于函数式接口那就可以用 lambda 表达式代替概念含义示例逻辑表名旧表代码中写的表名模板points_board真实表名新表数据库实际存在的表名points_board_2025、points_board_2026TableNameHandler 接口sql要执行的 SQL 语句通常用不到tableName逻辑表名这里是 points_board返回值替换后的真实表名欧克拦截器定义好后然后注册到 mybatis-plusConditionalOnMissingBean 的作用允许你自定义 MybatisPlusInterceptor如果没有自定义的就用这个默认配置常见于框架/组件库中提供默认实现Autowired(required false) 是 Spring 的依赖注入注解意思是尝试注入这个依赖如果找不到就赋值为 null不要报错。写法找不到依赖时的行为Autowired❌ 抛出异常启动失败Autowired(required false)✅ 赋值为 null正常启动动态表名拦截器可选判断是否有动态表名拦截器就是你之前配的那个如果有就添加到拦截器链中如果没有就跳过不影响其他功能注意注册顺序榜单持久化以及 XXLJob 工作流① 建表 ② 刷盘 ③ 清理 Redis 数据 三个分开不能一起如果后面失败又得重新建表了。①②当中分页查询方法之前写过这里只需要将 private 换成 public,上面加上 Override,然后父接口添加这个方法。得到的 PointsBoard 类包含这么多属性但是我们只需要下面这几个So这里得把自增改成手动添加然后把排名信息写入 idseason 就没赋过值不用管。查到 1000 条就刷盘别堆到一起不如内存要爆了。③DEL vs UNLINK特性DELUNLINK删除方式同步删除异步删除阻塞❌ 会阻塞 Redis✅ 不阻塞 Redis返回结果删除的 key 数量删除的 key 数量适用场景小数据量大数据量版本要求所有版本Redis 4.0DEL - 同步删除UNLINK - 异步删除数据跑批业务和 XXL Job 的分片广播刷盘之后记得 remove 线程域ctl d,新建实例改下端口改造以下地方方法返回值含义示例值getShardIndex()int当前机器的分片序号0, 1, 2...getShardTotal()int总分片数机器总数3把两个启动之后持久化榜单数据这里把轮询改为分片广播清理 Redis 中的历史榜单和创建历史榜单表不需要动就一个人去干就完了。后面进行测试这里我没做图是视频当中的理论上是只有一个后台会显示建表 create 语句但是 insert 都有数据库数据有了Redis 当中的没了网友疑问当一个实例完成后触发子任务时 子任务会导致 redis 数据删除 会使另一个没有完成的实例得不到 redis 数据 所以应该加个分布式锁吧确实存在这个竞态问题。但普通分布式锁解决不了这个问题因为这不是互斥访问的问题而是所有分片都完成后才能删除的问题。如果对你有帮助的话请点赞关注收藏。热爱可抵一切 ❤️