【性能提升100倍】springboot+thymeleaf缓存html网页提高并发性能

作者: ʘᴗʘ发布时间:2021-12-16 16:37 浏览量:254 点赞:204 售价:0

性能提升效果

coderbbb最近针对博客文章页面做了一次压力测试,在8C16G机器上压测,在保证响应时间500毫秒内的前提下,压测的最高并发只有80。考虑到文章页面的数据大部分都是静态数据,所以我们针对文章页面的HTML做了缓存。之后再次压测同样的页面,并发在提升到8000之后,仍有一定的提升空间,性能提升100+倍。

针对以上现象,我们强烈建议静态数据较多的WEB网页,使用缓存技术静态化HTML页面,提高性能。

技术栈介绍

coderbbb使用springboot+thymeleaf开发。在该技术栈下,目前并没有针对整个HTML页面进行静态化缓存的方案,只能自己DIY。我们使用了EhCache作为缓存框架,当用户请求文章页时,会先在EhCache中检查是否有缓存。

之所以选择EhCache的原因是该缓存框架可以把数据缓存到内存+硬盘两个地方。对于高热度的文章,缓存到内存中。对于较低热度的文章,整页缓存到硬盘中。最大限度的避免了数据库查询等高耗时操作。

技术实现

一、springboot整合EhCache作为缓存框架

这部分之前写过文章专门讲解,请移步《springboot中配置使用EhCache做持久化缓存(内存+硬盘)》阅读。

二、springboot中新建拦截器,判断是否有缓存

在springboot中创建一个拦截器,该拦截器在收到web请求后,将URL的md5 hash值作为缓存key,在EhCache中查询缓存是否存在,如果存在,则返回缓存。如果不存在,则让用户正常访问,并把返回给用户的HTML字符串缓存到key中。

核心知识点:springboot+thymeleaf如何获取HTML返回结果?这里要用到thymeleaf的ThymeleafViewResolver类,演示代码如下:

WebContext ctx = new WebContext(request, response, request.getServletContext(), new Locale("zh"), modelAndView.getModel());
String html = thymeleafViewResolver.getTemplateEngine().process(modelAndView.getViewName(), ctx);

springboot HTML网页静态化缓存拦截器完整代码如下:

package com.coderbbb.blogv2.config.interceptor;

import com.coderbbb.blogv2.config.anno.CacheHtml;
import com.coderbbb.blogv2.database.dos.UserDO;
import com.coderbbb.blogv2.service.CacheHtmlService;
import com.coderbbb.blogv2.utils.HttpServletUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class CacheHtmlInterceptor implements HandlerInterceptor {

    @Autowired
    private CacheHtmlService cacheHtmlService;
    @Autowired
    private ThymeleafViewResolver thymeleafViewResolver;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        String urlFull = (String) request.getAttribute("urlFull");
        request.setAttribute("urlFull",urlFull);

        String urlHash = DigestUtils.md5Hex(urlFull);
        String cache = cacheHtmlService.getCache(urlHash);
        if (cache != null) {
            //缓存存在,将缓存的数据直接返回给客户端
            try {
                response.setHeader("Content-Type", "text/html;charset=UTF-8");
                OutputStream outputStream = response.getOutputStream();
                outputStream.write(cache.getBytes(StandardCharsets.UTF_8));
                outputStream.close();
            } catch (Exception e) {
                logger.error("返回结果Response写入异常", e);
            }
            return false;
        } else {
             request.setAttribute("refreshCacheHtml", true);
        }
        return true;
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (modelAndView != null && modelAndView.getViewName() != null) {
            //缓存部分HTML页面
            Boolean refreshCacheHtml = (Boolean) request.getAttribute("refreshCacheHtml");
            if (Boolean.TRUE.equals(refreshCacheHtml)) {
                //需要刷新缓存
                String urlFull = (String) request.getAttribute("urlFull");

                //apache codec类库的MD5加密方法,你可以替换为你常用的。
                String urlHash = DigestUtils.md5Hex(urlFull);

                //这个IF语句是防止高并发场景下,缓存重复更新的问题。你可以替换为你的加锁方式,也可以去掉IF,将IF中的代码拿出来。
                if (redisLockService.getLock(urlHash, 60000)) {
                    String viewName = modelAndView.getViewName();
                    try {
                        //获取HTML返回结果,存入缓存中
                        WebContext ctx = new WebContext(request, response, request.getServletContext(), new Locale("zh"), modelAndView.getModel());
                        String html = thymeleafViewResolver.getTemplateEngine().process(viewName, ctx);
                        cacheHtmlService.putCache(urlHash, html);
                        logger.warn("## Cache Html ##" + urlFull);
                    } catch (Exception e) {
                        logger.error("cache html err:" + urlFull, e);
                    }
                }
            }
        }
    }
}

EhCache缓存封装好的Service,CacheHtmlService完整代码如下:

package com.coderbbb.blogv2.service;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class CacheHtmlService {

    @Cacheable(value = "cacheHtml",key = "#urlHash",sync = true)
    public String getCache(String urlHash){
        return null;
    }

    @CachePut(value = "cacheHtml", key = "#urlHash")
    public String putCache(String urlHash,String html){
        return html;
    }

    @CacheEvict(value = "cacheHtml", allEntries = true)
    public void clear(){

    }

    @CacheEvict(value = "cacheHtml", key = "#urlHash")
    public void clearOne(String urlHash){

    }
}

最后,将拦截器注入到Springboot中。之后,当你访问你的web页面时,将会生成缓存到EhCache中,为下一次访问加速。你可以在上面代码的基础上,定制你自己的逻辑。比如设置哪些页面缓存,哪些不缓存等等。

版权声明:《【性能提升100倍】springboot+thymeleaf缓存html网页提高并发性能》为CoderBBB作者「ʘᴗʘ」的原创文章,转载请附上原文出处链接及本声明。

原文链接:https://www.coderbbb.com/articles/55

其它推荐: