从单体服务到微服务:预初始化属性多模式 Web 应用开发记录<四> FreeMarker 视图首次渲染优化

news2025/1/15 17:32:34

欢迎关注公众号:冬瓜白

相关文章:

  • 多模式 Web 应用开发记录<一>背景&全局变量优化
  • 多模式 Web 应用开发记录<二>自己动手写一个 Struts
  • 多模式 Web 应用开发记录<三>预初始化属性

经过一段时间的开发和测试,我们成功地将项目从 “Spring + FreeMarker + Struts2 + Resin” 迁移到了 “Spring Boot + Spring Web MVC + Embedded Tomcat + FreeMarker”。但是在上线后发现了一个问题:视图首次渲染的速度非常慢,甚至可以达到数十秒。

在这里插入图片描述

使用 Trace 工具,发现大部分时间都耗费在了 Render 操作上。在 FreeMarker 中,Render 是指将数据模型和模板文件结合起来,生成最终的输出结果的过程。

在这里插入图片描述

这种时候使用 Arthas Trace 要找出造成渲染慢的底层原因还是相当困难的,但是可以使用 Async-profiler 采样第一次请求的火焰图,但是这里有个细节,在进行火焰图采样之前,可以先请求其他的非涉及 FreeMarker 渲染的接口,避免受到 Spring MVC 底层资源初始化的影响。

我选择了一个相对“干净”的 FreeMarker 渲染接口进行采样,这个接口没有复杂的业务逻辑,仅仅是渲染一个视图。采样结果显示,大量的 CPU 时间都花费在了 org.apache.catalina.webresources.JarWarResourceSet.getArchiveEntries 上。这可能是因为 Tomcat 在处理 FreeMarker 视图时需要加载大量的资源,这些资源主要包括 FreeMarker 模板和静态资源。

在这里插入图片描述

针对这个问题,有三种可能的优化策略:

  1. 减少资源数量:可以尝试减少 FreeMarker 模板和静态资源的数量,但是这种方法并不现实。考虑到我们的项目背景,我们并不想在前端处理上花费太多的精力。
  2. 预加载资源:可以在应用启动时预加载一些资源。
  3. 使用 JAR 文件:一般来说,从 JAR 文件加载资源通常比从 WAR 文件加载资源更快。

由于服务已经使用了 JAR 文件,所以我决定采用“资源预加载”的策略进行优化。

那么,如何进行资源预加载呢?我的思路是:提前渲染那些需要渲染的 FreeMarker 视图。换句话说,提前执行那些执行速度较慢的操作。这种思路其实在很多框架的预处理和预请求优化中都有应用。可以使用 Spring 提供的 MockHttpServletRequest 来实现这个目标。

首先定义一个注解,用来标注需要提前预加载的视图:

/**
 * @author dongguabai
 * @date 2024-04-16 21:38
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreloadOnStartup {
}

在 Spring Boot 启动后异步并发预加载:

/**
 * @author dongguabai
 * @date 2024-04-17 10:31
 */
@Component
public class Preloader implements ApplicationRunner {

    private static final Logger log = LoggerFactory.getLogger(Preloader.class);

    private final ApplicationContext applicationContext;
    private final RequestMappingHandlerAdapter handlerAdapter;
    private final RequestMappingHandlerMapping handlerMapping;
    private final ViewResolver viewResolver;

    private static final int QUEUE_SIZE = 200;

    public Preloader(ApplicationContext applicationContext,
                     RequestMappingHandlerAdapter handlerAdapter,
                     RequestMappingHandlerMapping handlerMapping,
                     ViewResolver viewResolver) {
        this.applicationContext = applicationContext;
        this.handlerAdapter = handlerAdapter;
        this.handlerMapping = handlerMapping;
        this.viewResolver = viewResolver;
    }

    @Override
    public void run(ApplicationArguments args) {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        try {
            Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
            for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
                RequestMappingInfo mappingInfo = entry.getKey();
                HandlerMethod handlerMethod = entry.getValue();
                Method method = handlerMethod.getMethod();
                if (method.isAnnotationPresent(PreloadOnStartup.class)) {
                    Set<RequestMethod> methods = mappingInfo.getMethodsCondition().getMethods();
                    Set<String> patterns = mappingInfo.getPatternsCondition().getPatterns();
                    RequestMethod requestMethod = methods.isEmpty() ? RequestMethod.POST : methods.iterator().next();
                    for (String pattern : patterns) {
                        executorService.execute(() -> {
                            try {
                                MockHttpServletRequest request = new MockHttpServletRequest(requestMethod.name(), pattern);
                                Parameter[] methodParams = method.getParameters();
                                for (Parameter param : methodParams) {
                                    Class<?> type = param.getType();
                                    Object value = null;
                                    if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
                                        value = type.getConstructor().newInstance();
                                    }
                                    request.addParameter(param.getName(), value != null ? value.toString() : null);
                                }
                                MockHttpServletResponse response = new MockHttpServletResponse();
                                ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
                                RequestContextHolder.setRequestAttributes(attributes);
                                HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
                                Object handler = handlerExecutionChain.getHandler();
                                ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
                                String viewName = modelAndView.getViewName();
                                View view = viewResolver.resolveViewName(viewName, Locale.getDefault());
                                view.render(modelAndView.getModel(), request, response);
                                log.info("Successfully loaded method: {}", method.getName());
                            } catch (Exception e) {
                                log.error("Failed to load method: {}", method.getName(), e);
                            } finally {
                                RequestContextHolder.resetRequestAttributes();
                            }
                        });
                    }
                }
            }
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                    log.warn("Preloader did not finish within 60 seconds");
                }
            } catch (InterruptedException e) {
                log.error("Preloader was interrupted", e);
                Thread.currentThread().interrupt();
            }
        } finally {
            executorService.shutdownNow();
        }
    }
}

看下效果对比:

优化前优化后
4s+0.2s+

总结

本文探讨了一个在项目迁移过程中遇到的性能问题:FreeMarker 视图首次渲染速度慢。

使用了 Trace 分析和 Async-profiler 工具定位到问题后,然后提出了三种可能的优化策略。最终选择了预加载资源的策略,并使用 Spring 提供的 MockHttpServletRequest 来实现。优化结果非常显著,首次渲染的时间从4秒以上降低到了0.2秒以上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1895517.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java的工厂设备管理系统-计算机毕业设计源码16179

摘要 在现代制造业中&#xff0c;高效的设备管理对于确保生产过程的顺利进行至关重要。为了满足工厂对于设备管理的需求&#xff0c;我们设计并实现了一个基于 Java 的工厂设备管理系统。 该系统旨在提供一个全面、可靠且易于使用的解决方案&#xff0c;以帮助工厂有效地管理…

temu a4接口 逆向

声明(lianxi a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言(lianxi …

【️讲解下Laravel为什么会成为最优雅的PHP框架?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

为什么进口主食冻干那么高贵?必入榜主食冻干总结分享

新手养猫人常常会有这样的疑问&#xff1a;为何进口主食冻干价格如此昂贵&#xff0c;但仍有大量养猫达人对其推崇备至&#xff1f;与国产主食冻干相比&#xff0c;进口产品的价格高出3-4倍之多&#xff0c;那么这高昂的价格背后&#xff0c;进口主食冻干是否真的值得推荐&…

偏微分方程笔记(驻定与非驻定问题)

椭圆方程可以看成抛物方程 t → ∞ t\rightarrow\infty t→∞的情况。 抛物&#xff1a; 双曲&#xff1a;

音视频流媒体视频平台LntonAIServer视频监控平台工业排污检测算法

在当今社会&#xff0c;环境保护和可持续发展已成为全球关注的焦点。工业生产作为经济发展的重要支柱&#xff0c;其对环境的影响不容忽视。因此&#xff0c;如何有效地监控和管理工业排污&#xff0c;成为了一个亟待解决的问题。LntonAIServer工业排污检测算法应运而生&#x…

【架构-20】死锁

什么是死锁&#xff1f; 死锁(Deadlock)是指两个或多个线程/进程在执行过程中,由于资源的互相占用和等待,而陷入一种互相等待的僵局,无法继续往下执行的情况。 产生死锁的四个必要条件: &#xff08;1&#xff09;互斥条件(Mutual Exclusion)&#xff1a;至少有一个资源是非共享…

2024 年 亚太赛 APMCM (B题)中文赛道国际大学生数学建模挑战赛 |洪水灾害数据分析 | 数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; 完整内容可以在文章末尾领取&#xff01; 该段文字…

概率论与数理统计_下_科学出版社

contents 前言第5章 大数定律与中心极限定理独立同分布中心极限定理 第6章 数理统计的基本概念6.1 总体与样本6.2 经验分布与频率直方图6.3 统计量6.4 正态总体抽样分布定理6.4.1 卡方分布、t 分布、F 分布6.4.2 正态总体抽样分布基本定理 第7章 参数估计7.1 点估计7.1.1 矩估计…

python库(3):Cerberus库

1 Cerberus简介 Cerberus 是一个Python数据验证库&#xff0c;设计用于验证数据结构的有效性和一致性。它提供了一种简单而强大的方式来定义和应用验证规则&#xff0c;特别适用于处理用户输入的验证、配置文件的检查以及API的参数验证等场景。下面将详细介绍 Cerberus 的特点…

精准畜牧业:多维传感监测及分析动物采食行为

全球畜牧业呈现出一个动态且复杂的挑战。近几十年来&#xff0c;它根据对动物产品需求的演变进行了适应&#xff0c;动物生产系统需要提高其效率和环境可持续性。在不同的畜牧系统中有效行动取决于科学技术的进步&#xff0c;这允许增加照顾动物健康和福祉的数量。精准畜牧业技…

【C++ | 继承】|概念、方式、特性、作用域、6类默认函数

继承 1.继承的概念与定义2.继承的方式2.1继承基本特性2.2继承的作用域2.2.1隐藏赋值兼容 派生类的创建和销毁构造函数拷贝构造赋值重载 1.继承的概念与定义 继承是面向对象编程中的一个重要概念。它的由来可以追溯到软件开发中的模块化设计和代码复用的需求。 在软件开发过程…

山西车间应用LP-LP-SCADA系统的好处有哪些

关键字:LP-SCADA系统, 传感器可视化, 设备可视化, 独立SPC系统, 智能仪表系统,SPC可视化,独立SPC系统 LP-SCADA&#xff08;监控控制与数据采集&#xff09;系统是工业控制系统的一种&#xff0c;主要用于实时监控、控制和管理工业生产过程。 在车间应用LP-SCADA系统&#xf…

【项目日记(四)】搜索引擎-Web模块

❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多项目内容 目录 1.前言2.前端模块2.1页面设计2.2后端交互 3.部署到云服务器4.总结 1.前言 在前面的文…

25届最近5年华北电力大学自动化考研院校分析

华北电力大学&#xff08;北京保定&#xff09; 目录 一、学校学院专业简介 二、考试科目指定教材 三、近5年考研分数情况 四、近5年招生录取情况 五、最新一年分数段图表 六、初试大纲复试大纲 七、学费&奖学金&就业方向 一、学校学院专业简介 二、考试科目指…

论文导读 | knowledge-based VQA

背景介绍 传统的视觉问答&#xff08;Visual Question Answering, VQA&#xff09;基准测试主要集中在简单计数、视觉属性和物体检测等问题上&#xff0c;这些问题不需要超出图像内容的推理或知识。然而&#xff0c;在knowledge-based VQA中&#xff0c;仅靠图像无法回答给定的…

DA-LSTM多输入分类|蜻蜓算法-长短期神经网络|Matlab

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…

大疆2025校招内推

需要内推码的请留言哦 期待你的加入

哪个麦克风唱歌效果好,哪个麦克风好,无线麦克风十大排名分享

​在数字化时代的背景下&#xff0c;声音的传播与记录变得日益重要。无论是会议室、教室还是户外场所&#xff0c;无线领夹麦克风凭借其便携性和稳定的连接性能&#xff0c;成为人们沟通表达的首选工具。面对众多选择&#xff0c;我为你精选了几款性能卓越且性价比高的无线领夹…

DVWA sql手注学习(巨详细不含sqlmap)

这篇文章主要记录学习sql注入的过程中遇到的问题已经一点学习感悟&#xff0c;过程图片会比较多&#xff0c;比较基础和详细&#xff0c;不存在看不懂哪一步的过程 文章目录 靶场介绍SQL注入 lowSQL注入 MediumSQL注入 HighSQL注入 Impossible 靶场介绍 DVWA&#xff08;Damn…