type
status
date
slug
summary
tags
category
titleIcon
password
icon
calloutIcon
主要技术点:动态表名分表,XXL-JOB分区任务与避免重复执行,Redis的Bitmaps与Zset使用,业务点:积分记录定时分表,榜单持久化,点赞记录持久化。
Redis客户端选择
IDEA
- 使用中发现IDEA自带的Redis连接功能有限
- 比如不能对zset里面的数据按字段选择排序

- 无法查看bitmap的数据

- 只能靠BITFIELD返回的结果估计(此命令Redis以10进制返回结果)



Another Redis Desktop Manager

- 可以根据字段做排序

- 以二进制显示bitmap数据


Tiny RDM
- 界面更好看的客户端,但是通过GUI直接更改bitmap数据的时候存在问题,比如将某一偏移位置值改为1,往后的所有数据也都全部设置为1了。

点赞记录的持久化方案
核心思路
- 基本方法:通过分离Redis里存放更新数据的集合与全量数据的集合来进行增量更新。
- 数据结构选择:由于需要批量取出数据并保证数据唯一不重复,采用Zset存放更新部分数据,出于方便,将新增的数据放至NEW中,删除的数据放至DEL中。
流程图

- 部分环节trick:1.点赞/取消中,同时发送ZSCORE与ZINCRBY,通过ZSCORE返回值(数据或nil)可以区分点赞数是否已在Redis中,如果返回不为null,那么数据在Redis中,业务已经结束,反之则查询数据库,将数据库结果ZINCRBY上去即可。2.查询已点赞时,通过Redis中ALL判断是否有数据,同时可以通过DEL检查用户是否先前取消了点赞,减少二次检查的查询量。3.定时同步操作里,由于业务id没有办法给定,需要自己构造key模板,交给ScanOptions,通过 executeWithStickyConnection在长连接中不断scan各个键将数据加入集合,用于数据库一次批量处理(防止数据过多限定了一次读取的数量上限)。
新增/取消




查询点赞情况



定时同步(限制单个Key的取出数据量与总共的取出数据量)



MP动态表名
插件顺序
- 根据官方文档的说明,需要确保插件的注册顺序。

- 修改tj-common下MybatisPlus配置类,先注册动态表名,再注册分页,最后自动填充是自定义拦截器,补全用户信息(如果遗漏)。
- 在传参的部分@Autowired自动注入,由于目前只有tj-learning一个服务用到,使用required = false参数,兼容其他微服务。
展开

拦截器中获取数据
- 在tj-learning学习服务里,添加MybatisPlus动态表名拦截器配置类,对points_board积分榜单表注册对应表名处理函数,加入表名映射map,传参构造拦截器返回。
- 拦截器不能直接被调用,但是由于流程中一直是同一线程,所以可以通过ThreadLocal暂存和获取表名数据。

展开

海量数据存储
分区(Partition)
- 在数据库层面按照规则对表做水平拆分。
- 以InnoDB为例,一张表的数据在磁盘上对应一个ibd文件,如果表数据过多,就会导致文件体积非常大。文件就会跨越多个磁盘分区,数据检索时的速度就会非常慢。
- 按照某种规则,把表数据对应的ibd文件拆分成多个文件来存储。从物理上来看,一张表的数据被拆到多个表文件存储了;从逻辑上来看,他们对外表现是一张表。
- 逻辑操作不变,MySQL底层处理上有所变更。
优势
- 可存储超过单表上限的数据,也可存储到不同磁盘。
- 查询时可以按规则只检索一个文件,提高效率。
- 可多文件并行统计,提高效率。
- 可以删除分区文件,直接清除一部分数据,提高效率。
分区方式
- Range:按指定字段范围。
- List:按字段枚举,需提前指定所有可能值。
- Hash:字段hash后结果分区,数值类型。
- Key:字段值运算结果分区,不限定字段类型。
分表
- 在业务层面按照规则对表做水平拆分。
- 逻辑上与物理存储上都变成多张表,需要更改CRUD语句。
优势
- 拆分灵活,可水平,可垂直。
- 解决数据量大或字段多的问题。
劣势
- CRUD中需要添加判断访问哪张表。
- 聚合操作数据合并需要额外处理。
- 单表变多表带来的事务问题与数据关联问题。
集群与分库
- 微服务项目模块划分,每个微服务有独立的不同业务的数据库,进行了垂直分库。
- 对数据库做主从集群,主从数据同步,保证高可用,进行了水平扩展。
优势
- 突破单机存储瓶颈。
- 提高并发能力,突破单机性能瓶颈。
- 避免单点故障。
劣势
- 系统复杂度与成本高。
- 主从数据一致性问题。
- 分布式事务问题。
总结
- 单表数据多可以先库内分表,再分库。
- 读写压力大可以垂直分表,再读写分离集群。
- 索引→分表/ES→分库/集群
榜单持久化
Cron表达式详细


每月初创建历史榜单分表
展开


持久化上一赛季Redis榜单数据
展开


清理Redis中的历史榜单
展开

代码
代码
- 注意清理的XXL-JOB分片任务只应有一次业务执行。
积分记录的定时分表方案
- 重命名方案:简单快捷
- 延时删除:保证服务从始至终一直可用
流程图

重命名方案
展开


延迟任务方案
展开






Bitmaps类型使用
常用命令
- 主用setbit和bitfield,相关使用(Spring Data Redis)参见代码模板。

业务相关
- 获取本月至今的签到详情:BITFIELD key GET u[dayOfMonth] 0
- 获取连续签到天数:通过从最后一次签到向前统计,直至第一次未签到,计算总的签到次数,代码实现上从后向前遍历每个bit:循环&1,得最后一bit,循环无符号>>>右移动1位,重复进行。
Zset类型使用
常用命令

业务场景
- 实时排行榜
代码模板
Spring Data Redis Pipeline模板

Spring Data Redis BitMap操作模板



Mybatis注解中多值SQL模板

其他注意事项
消息队列传递数据的类型
- 消息队列API需要生产者和消费者数据类型一致,否则可能出现消息失败,送到error去重试。当时传递数据写成了Integer,类型错误导致消息错误。
案发现场
- 😅因为API里不限定消息类型,这里消息失败后断点调试问题又多发了几次,消息一直重试给电脑整蓝屏了,强制重启之后虚拟机MySQL Docker容器开始无限重启(启动就是restarting状态),整的数据库用不了,直接重装虚拟机了。
- 善用VMware快照,多备份虚拟机状态用于回退。

数据库查询返回可为null值的类型
- MySQL SUM函数在不满足条件的情况下返回不为0,为null,需要用Integer包装类接收结果。


- 采用int接收出现了数据库操作正常(日志正常打印)下的业务失败。
案发现场
注:上方枚举类型传递mapper中,放入sql语句自动转为value。
修改points_board表中类型
- 在Redis查询到以前赛季中有的数据实际上已经达到200以上,但数据库对应表的points_board在积分值字段仍采用的tinyint,考虑到之前的积分值一天有几十分的上限设置,十分不合理,将其更改为int类型,而且本身在PO类代码中映射也是Integer。
点击查看points_board定义图

点击查看Redis旧赛季数据

点击查看points_board PO定义代码
测试数据生成
- 原季度数据只到2023年9月,测试同步上一赛季的数据时业务需要获取查询季度id,此处添加测试数据至2030年。
展开

- 测试重命名方案实现积分记录分表存档的开销时间是否可以接受,插入一百万随机数据。
展开

Mybatis DDL语句使用事项
- Mybatis注解里一次性写多条DDL会失效。
- 个人理解Mybatis一个语句应该是在一次事务提交里面,但是DDL需要每次直接提交,所以放一起多条会不生效,日志反馈就是没有第三行的返回值(单个执行就有)。
- 总结下来DDL单句一个函数,然后DDL和DML分开不同的函数写(参照上方写法)。

Day06练习参考实现
完善互动问答功能
- 前文复盘已经实现
点赞业务类型的动态配置
展开
在Nacos自行新添配置,后缀.yml,.yaml,或者没有也可以

参考版本

在bootstrap文件里添加配置

可以添加到常量里使用

用${}获取配置,:为设置默认值,注意层级与文件内容保持一致

点赞记录持久化
- 参见上方对应标题部分
XXL-JOB定时任务
- 可以在Day08 20p视频里快速入门,以下提供参考实现
展开
Java代码部分


XXL-JOB控制台


Day07练习参考实现
查询签到记录
展开

完善积分功能
展开


查询赛季列表功能
展开

Day08练习参考实现
查询积分榜
展开




清理积分明细
- 见上方积分记录定时分表方案
了解更多
技术重点在三个方面:幂等性保障|延迟任务|合并写,除去这一部分,其他主要为基本业务,简单且量大,对初学友好,如果不是第一次做微服务项目,选看这三个方面就可以跳了。文中图片与代码块较多,善用展开懒加载。
- 作者:CamelliaV
- 链接:https://camelliav.netlify.app/article/tjxt-day06-08
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。