为了避免对于生产环境当前的部署情况以及压测时对于其他资源的侵占导致的可能出现的各种 bug,我选择在我自己的主机上重新部署 qData 的这个项目。
这个花费了一定的时间,但是也让我感受到了本地开发的 IDEA 的重要性,它有很直观的图形界面,也易于调试,只是稍微需要学习一下,并且不同的 Java 版本可以通过项目结构进行管理,不需要在环境变量里面配置,降低了工作环境的复杂度。
使用的工具
在 JVM 中,绝大部分的数据和垃圾回收都是在 RSS 中发生的。
分析 JVM 中的内存使用情况,我需要使用什么工具?
Idea 中有 Profiler 插件可以进行监测和分析。我先使用这个。
对堆文件进行分析。对 dump 文件进行分析。
目前发生的情况
当使用 k6 进行压测时,能够观察到,当线程数为 200 的时候,堆内存分配会非常快地迅速增加。我直觉上觉得存在可以优化的地方,目前的设计是有很大的问题的。
目前调用数据中台的接口发生的情况是:
目前几个比较大的问题是:
- 每个线程在请求数据库时都会创建实例。也就是说,在高并发的情况下,每个线程都反复在堆中创建了自己的实例,在创建请求结束后又要销毁,这就是一个反复的对象创建和销毁的操作。可以被认为是一种资源的浪费。目前一个比较好的解决方法是将该对象作为单例模式创建。而线程调用时是对应单例进行调用。幸运的是,Spring 的 NamedParameterJdbcTemplate 类是线程安全的。
- 另外一个比较严重的问题是目前的数据库查询是全表查询扫描,将查询到的结果全部载入到 List 中,也就是直接全量加载到内存中返回。在生产环境中肯定是明确不可取的。我觉得这是有两方面的设计问题,一个是数据接口的 SQL 查询语句的接口 SQL 脚本设计问题,一个是内部不做分页查询、流式查询的逻辑处理问题。前者是我们说的 SQL 查询优化,后者是代码需要健壮性处理的重构需求。
将 NamedParameterJdbcTemplate 调整为单例模式的实现后,压测并发 200 线程总体下降了 50MB 左右的内存占用。
数据服务层的 API 如何实现动态路由配置
数据服务层的
在 StartUpRunner.java 中,代码通过读取加载数据库的配置(dsApiService.java 进行的逻辑操作),通过将数据条目一条条转入 requestMappingHandlerMapping 实例对象的父类 AbstractHandlerMethodMapping 中的 class MappingRegistry 的实例对象进行管理。由此数据库表中的条目映射为了具体的路由规则,并成为了由 Java 程序进一步进行管理和控制生命周期的对象。
下一步?既然程序在内存中维护了这个 requestMappingHandlerMapping 类的 this.mappingRegistry 表,那么当一个外部程序要访问这里面的链接(尤其是从数据库表中读取的条目的对应映射时),又是如何进行的。
外部程序(例如前端)向该程序发起一个 HTTPS 请求,在本场景中,具体的情况如下:
- 经过 Filter 做基础的权限校验
- 解析该请求中的 URL,做请求路径、方法的提取。根据路径做定位匹配——也就是在 mappingRegistry 中做定位。根据优先级有不同的处理措施。
- 起调用链,根据存储的该路径的 handlerMethod 起函数的调用,并交付给程序执行。
- 程序执行完毕,返回所需的内容,进行可能的视图渲染,返回给外部程序。