Java 篇-项目实战-天机学堂(从0到1)-day7
java 篇 1.基础地基 2.设计原理 3.项目实战分析产品原型表有了用 mp 生成器生成相关的实体。实现签到功能接口docker exec -it redis redis-cli这个命令的意思是在运行中的 Redis 容器里执行 redis-cli 命令部分含义说明dockerDocker 命令调用 Docker 程序exec执行在容器中执行命令-it交互式 终端i交互模式t分配伪终端两个通常一起用redis容器名要进入的容器名称redis-cli要执行的命令Redis 的命令行客户端工具返回的是旧值 这里从 0 开始bitfield bm get u2 0部分含义说明BITFIELD位操作命令Redis 的位域操作命令bm键名要操作的 keyget读取操作从位域中读取值u2类型位数u无符号整数2占2个bit位0偏移量从第0位开始读取符号含义取值范围u无符号整数0 到 2^n - 1i有符号整数-2^(n-1) 到 2^(n-1)-1返回十进制的 3前端不需要总的所以用 JsonIgnore 忽略了注意划横线这几处对于日期格式设置了静态 String 类型。spring 默认返回 true or false,redis 当中是 0 or 1,返回 1代表旧值已经有了可以用此判断重复签到了复习一下维度BadRequestExceptionBizIllegalException含义错误的请求业务非法操作HTTP状态码400客户端错误通常 400 或 403触发场景参数格式错误、必填项缺失业务规则违反、权限不足使用者客户端传错了数据客户端做了不该做的操作能否修复修改请求参数即可可能需要权限或改变状态①部分含义redisTemplate.opsForValue()获取 Redis 字符串操作对象.bitField(key, ...)执行 BITFIELD 位域操作BitFieldSubCommands.create()创建位域命令构建器.get(...)执行读取操作BitFieldType.signed(len)有符号整数占 len 个 bit 位.valueAt(0)从第 0 位开始读取List返回结果列表为啥返回的是集合呢因为这当中可以有很多个子命令但是这里只执行了一个子命令得到 Long 的十进制数。②为了避免下面 while 当中频繁的拆箱提前进行了强转处理。③复习一下 和 的区别操作符名称行为无符号右移左边补 0有符号右移左边补符号位这里奖励积分还没有实现先设置个 0测试一下这里采用 0 和 1,在前端看来就是 false or true,并且可以减少请求交互过程中数据的传输本质是用更少的数据量表达相同的信息用 1 个 bit 代替 5 个字节Byte[] 是 Java 中的字节数组对象类型。Byte 为 bite 基本数据类型的包装类。跟前面统计那部分类似只不过没有传入参数需要自己拿一下。后面就是把读取的结果转为 Byte[] arr。实现保存积分明细功能接口定义了学习有关的交换机以及积分相关的路由键这里就实现写回答和签到记录积分为例子消息队列名字换了下然后交换机不变都是学习相关的但是路由键肯定得换了。但是下面的是针对签到的而签到它加的积分不是固定的连续签到多天加的积分不同。那这就提前定义了一个枚举类就不用 map 放 userid 和 points 了最后还得把积分的类型传进去。接着跟着流程完成具体的代码编写先来判断下是否有积分上限其实传入的 type 枚举类当中就有这么一个属性。因为每日签到 一天就一次课程评价对课程评价也就一次又不能重复所以为 0没有设置上限。那这里就判断 maxPoints 是否 0 就行了。如果有上限就需要进一步判断是否超过上限。如果没有上限直接保存积分记录。对于有上限我们需要用 userId,type,时间去数据库查现在已得的积分int currentPoints queryUserPointsByTypeAndDate(userId, type, begin, end);那由于 lambda()当中没有.SUM()方法所以得手写下。混合使用 LambdaQueryWrapper 和手写 SQL 的典型用法Constants.WRAPPER 的作用Constants.WRAPPER 的值是 ewMyBatis-Plus 内部定义Param(Constants.WRAPPER) 相当于 Param(ew)所以在 SQL 中可以用 ${ew.customSqlSegment} 获取 wrapper 生成的 SQL 片段${ew.customSqlSegment} 会生成什么假设参数为userId 1001type SIGN (签到)begin 2026-01-01, end 2026-01-31那么 ${ew.customSqlSegment} 会被替换为customSqlSegment 是 MyBatis-Plus 中 QueryWrapper 的一个内部属性它存储了 wrapper 构建的完整 SQL 片段包括 WHERE 关键字和所有条件。就是传入的 wrapper 参数通过 Param(Constants.WRAPPER)传到了上面的 ew,.customSqlSegment 获得完整 sql 片段与前面部分拼接构成了完整的 sql 语句。最后别忘了健壮性处理因为 userId 肯定不为空的那 type 和 begin、end,正常情况肯定不为 null,这里也判断一下把。下面如果 wrapper 为 null,那得到的 points 为 null所以需要判断一下。那这样就查到了已得积分如果超过了上限那就没有必要保存记录了。没有的话那也不能直接保存如果说已得积分加上现在的积分超过了上限那就有问题了。所以真实记录的积分应该是上限-当前积分。ok完事。之后在签到里发送消息测试一下先注入 RabbitMq 客户端rewardPoints 是连续签到得到的积分这里 1 是本次签到得到的积分就是传的是总积分。完整的流程是这样的这里发送消息到交换机并设置了路由键监听器绑定了队列监听到队列有消息开始处理消息保存数据到数据库。下面开始测试先删除 Redis 当中原先的签到记录发送请求redis 当中又有了这个数据再看 RabbitMq 当中也是有消息的并且数据库当中也有记录的数据从时间上判断也是正确的。实现查询我的今日积分接口这里查询跟之前根据用户类型和数据查询类似不过这里是要返回每个类型的不是单个了。记得起个别名这样才能和 PointsRecord 当中的属性对上然后这里是分组查询查到之后封装返回进行测试测试通过注意查的是今日的你得看下数据库当中是否有数据前端应该是这样的不过我没看发现还是不行突然看到这里怎么是 java 21(图中是已改过的)而其他导入存在的是这个 java 11 这样的形式喔凸(艹皿艹 )原来是这里没配置 JDK 版本改好后果然不报错了。但是重测还是出现了 Learning 下游业务并没有执行比对了代码看了 rabbitMq 相关的信息发现没啥问题然后相当红温于是求助企鹅龙虾。先是判断前端没有传递 bizType 参数于是在结果发现前端有传再去看日志发现String.format({}.times.changed, QA) 应该返回 QA.times.changed而不是 {}.times.changed。这说明传入 send 的 bizType 实际上是 null。所以就是在 addLikeRecord 方法和模板化过程中间出了问题然后列出了这几种可能性日志打印和 send 调用之间 bizType 被清空了不太可能String 是不可变的2.RabbitMQHelper.send 的日志方法打印的参数顺序有问题Learning 服务连接 RabbitMQ 时出现了问题于是通过临时硬编码的方式重新测试发现好了下游服务的数据库发生了变化。所以根本原因是String.format 格式化失败。一查我的妈呀用 String.format() 时 → 用 %s用 StringUtils.format() 时 → 用 {}怪不得我一直用的是 String.format() 搭配 {}路由键一直是错的能处理就怪了最终也是通过了。不过这里还有个小 bug理论上会有最新回答的但我前端测试是没有的不过我也不想改了后面需要改再说吧。过功能改进原来的流程涉及到多处数据库的读写因为这是网课对于点赞功能没有特别大的需求再加上设置了用户 id 和业务 id 的联合索引大部分能满足需求。改进后这里对于新增点赞记录采用 Set,天然保证数据唯一性新增成功输出 1失败输出 0.统计采用 ZSet多实例部署水平扩展进行刷盘那为了避免重复刷盘就需要查到后删除这样其他实例才能去处理下一部分。不用 Map 是因为它没法保证查和删原子性操作且里面的元素是无序的需要通过 SCAN 扫描全部数据量是不确定的这可能会给数据库造成压力。用 ZSet 因为它可以根据 score 进行排序ZPOP 查删原子性且可以设置查询数量避免一次性取过多的数据给下游造成太大压力。然后还有一点要提的就是 bigKey 的问题按业务拆成多个 Key那当然如果某个业务当中还是出现 bigKey 的问题继续拆可以将业务 id 哈希运算然后对 10 取余余数不同Key:n 再次打散改造点赞和取消点赞接口这里用接口因为默认为 public final前缀命名规律前缀拆解含义likes:set:biz:likes set biz :点赞业务的 Set 结构存储业务IDlikes:times:type:likes times type :点赞业务的次数字段按类型分类层级结构copy 原先的然后把原先的 Service 注释掉第一个用 redisTemplate,参数都是 String 类型得到的结果是 Long 类型第二个涉及到自动拆箱得先判断不为 null下面为主要流程框框里也是对于拆箱的处理因为 score 为 double 类型改造查询点赞状态接口可以直接通过这个命令查看 id 在不在当中但是只能看一个业务。想看是不是在其他业务就得在去 for 执行。如果客户端与 Redis 服务端之间的距离非常长导致网络延时很高那总的网络消耗就会很大。这里采用批处理的方式批量发送 n 条命令执行 n 条命令返回 n 个结果。代码实现原先的 for 写法注释中挨个判断有木有如果有把 bizId 加到 Set集合中。现在以管道模式执行 Redis 命令所有命令的执行结果列表顺序与添加命令的顺序一致RedisCallback 回调作用在 Redis 连接中执行自定义操作特点可以获取底层的 RedisConnection 对象new RedisCallback() 本质上就是个匿名类匿名类的特征特征说明没有类名定义时不需要写类名一次性使用通常只用一次立即实例化定义的同时就创建对象编译器生成名字编译后会有 ClassName$1.class 这样的文件不能重复使用无法创建第二个实例里面的 doInRedis 方法默认返回 null但没啥干系需要的结果都在 objects 当中。当然还可以优化一下匿名类换成 lambda 表达式鼠标放在 RedisCallback上有黄色小灯泡可以立即变的框里面就是 stream 仙人写法可读性差看看就行定时任务持久化缓存数据现在启动类上加上然后在 remark 模块新建 task,因为这个模块来统一管理点赞相关的数据落库都在这完成。Scheduled(fixedDelay 20000)里面也可以用 cron 表达式定时任务执行时间。前面的 BIZ_TYPES 和 MAX_BIZ_SIZE 可以写到配置文件里nacos 来管理。这里 MAX_BIZ_SIZE 限制一次性处理最多的业务 id.调用 recordService 进行处理在原来的 ServiceImpl 和新建的 ServiceRedisImpl 都去实现这个.readLikedTimesAndSendMessage 方法不过 ServiceImpl 给个壳子就行避免报错在 ServiceRedisImpl 具体实现。那肯定得执行 redisTemplate 当中查找并删除方法那选 popMin 还是 popMax 呢这里选小的上因为小的对数据更加敏感并且数据量小估摸着都不会变 尽早刷盘把它在 Redis 当中干掉。那大的呢体量大差一俩个无所谓并且数据变化频繁那尽量减少刷盘的频率。那这里需要 key 和 maxBizSize 参数然后加入 key. 得到的 tuples 为元组就是键值对咯。那它跟 LikedTimesDTO 对应。那后面就是一个个将它们放到 list 集合中统一发送 MQ然后修改 learning 模块的 Listener这里不是处理一个了而是整个 list 集合了。调整一下ok进行测试测试通过20s 后这个数据就入库Redis 当中就没了压测新建线程组右键添加相关组件。当前页面配置说明当请求失败时选择如何处理选项含义使用场景继续忽略错误继续执行压力测试统计真实错误率 ✅启动下一进程循环跳过当前开始下一次循环业务依赖时停止线程当前线程停止其他继续某个用户失败后不再重试停止测试所有线程立即停止严重错误时立即停止测试强制停止不等待紧急情况这里看下定义了解下就行配置项你的设置含义推荐值说明线程数2000虚拟用户数量100 → 500 → 2000 逐步增加从低到高测试服务器承受能力Ramp-Up时间空白 ❌启动所有线程的时间秒60-120秒空白瞬间启动2000线程容易压崩服务器循环次数永远1 ⚠️每个线程执行几次1 或 永远选择1或永远不要两个都选Same user on each iteration未勾选是否保持同一用户状态看需求勾选保持Cookie/会话不勾选每次模拟新用户延迟创建线程直到需要未勾选何时创建线程默认不勾选勾选节省内存不勾选提前创建所有线程调度器未勾选启用定时控制压力测试时勾选不勾选手动停止勾选自动停止持续时间秒未设置整个测试运行时长300秒需要先勾选调度器才显示启动延迟秒未设置延迟多久后开始0-10秒需要先勾选调度器才显示字段名你的数值含义判断标准Label点赞取样器/请求的名称你在HTTP请求中填写的名称用于区分不同的请求接口# 样本2000发送的请求总数样本数量数值越大结果越可信平均值2ms所有请求的平均响应时间越小越好✅ 200ms 优秀⚠️ 200-500ms 一般❌ 500ms 较差最小值1ms最快的请求响应时间反映最佳情况最大值7ms最慢的请求响应时间反映最差情况波动越小越稳定标准偏差0.67响应时间的波动程度越小越稳定✅ 50 波动小⚠️ 50-100 波动中等❌ 100 波动大异常 %0.00%请求失败的比例越小越好✅ 1% 优秀⚠️ 1-5% 可接受❌ 5% 有问题吞吐量966.7/sec每秒处理的请求数TPS/QPS越大越好反映服务器的处理能力如果对你有帮助的话请点赞关注收藏。热爱可抵一切 ❤️