文章>这次调优横跨java和Groovy(SimpleTemplateEngine)>

这次调优横跨java和Groovy(SimpleTemplateEngine)原创

1月前
173226

一、前言

最近给客户调优过程中,频繁遇到java调用groovy的情况,在排查过程中也发现了一些相关的性能瓶颈。其中比较突出的是调用groovy api时导致的频繁类加载问题,就这个问题在本地模拟了客户相关的代码实现,进行分析解决。
419871564616afb924126e_fix732.png

二、问题现象

(一)高CPU消耗
1. 加载groovy jar时的路经检查
4073877522616af379e1779.png

2. 读取jar包时的解压缩操作

2090751851616af383b07c3.png

(二)类加载耗时

最直观是压测过程中,相关的单接口响应时间很长,至少是秒级起步,这边就不放相关的截图了。

三、问题排查

以下内容以调用SimpleTemplateEngine获取占位符的值为例,以下为本地模拟情况,重在排查思路。

(一)第一次优化

1. 线程dump,在dump文件中,线程栈中个方法调用一目了然

在某个线程中,我们找到了类加载相关的方法,直接看最近的自定义类的方法,一开始千万不要陷入各种框架或组件的方法。这里看到是TestSimpleTemplateEngine类的generateMessage方法。

2123266426616af482285a5 1.png

直接反编译类,查看方法如下:

2334876637616af52231528.png

每次调用此方法都会new SimpleTemplateEngine类(调优过程中,客户那边的代码就是这么实现的),而且每个客户端的请求都会调用到这个方法。

2. 尝试单例解决

看到这里,第一个优化思路就是将new SimpleTemplateEngine()这个操作使用单例实现。
当然结果是:毫无成效。没办法,只能从源码开始了。

(二)源码开始,第二次优化

1. SimpleTemplateEngine类

阅读SimpleTemplateEngine类源码,发现它在实例化的时候根本没做啥骚操作,就是设置了个成员属性groovyShell。真正涉及到类加载器的是SimpleTemplateEnginecreateTemplate方法。

2. createTemplate方法

createTemplate源码如下:

746229979616af6319c554 1.png

重点都在这个方法里,通过传入的文本模版解析groovy脚本等一系列操作,其中会把文本脚本封装成一个GroovyCodeSource类,注意这边它会自动给你的脚本生成一个groovy后缀名结尾的文件名。
filename中counter是自增,所以每次调用createTemplate生成的filename都是不一样的。后面将给定的groovy code转换成java类的时候,会进行类加载。类加载的之前会检查缓存(HashMap)中是否已存在class类,如果存在则不会调用类加载器进行类加载。
直接跳到开始解析类的地方:

2383827068616af69b1d330 1.png

具体加载过程如上源码。其中缓存的检查是就是根据上面提到的GroovyCodeSourcename属性。那么问题来了,这个name每次生成的时候每次都会变化,也就是说这边类加载的时候永远用不到缓存,每次都需要调用类加载器进行类加载。

3. 开始真正的优化了

缓存大法好。既然真正导致类加载的是createTemplate方法,就直接把createTemplate生成的Template实例给缓存了。缓存用什么呢?最简单直接的就是Map了。当然如果涉及的模版类型比较多,用Map的话可能会占用大量内存,不怕不是还有Guava,Caffeine这种高性能本地缓存框架吗,LRU耍起来,会过期总比每次类加载来的好吧。当然如果每个模版类型都是不一样的,加不加缓存效果都一样,这种情况暂时还没想到解决方法。
3043505068616afa11b15a9.png

四、优化后结果

所有的线程栈中都没有类加载的影子了(因为打印了返回值,日志量比较大,都在刷日志);而且如果关注优化前后的GC情况的话,会发现优化后GC情况好了不是一点点,在同样-Xmx1g的情况下,调优前会频繁的Full GC,优化后只有Minor GC。

1893744139616af7af18ca7.png

五、测试代码

  • 优化前
public class TestSimpleTemplateEngine {

    private final static Logger log = LoggerFactory.getLogger(TestSimpleTemplateEngine.class);
    private final static ArrayList strTemplates = new ArrayList();

    static {
        strTemplates.add("${user.name}");
        strTemplates.add("${user.code}");
        strTemplates.add("${user.company}");
        strTemplates.add("${user.address}");
        strTemplates.add("${user.message}");
    }
    public static String generateMessage(Map map, String placeHolder) {
        String msg = null;
        try {
            msg = new SimpleTemplateEngine().createTemplate(placeHolder).make(map).toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return msg;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (;;) {
                    Map<String,Object> map = new HashMap<>();
                    int nameSuffix = new Random().nextInt(900) + 100;
                    int index = new Random().nextInt(strTemplates.size());
                    // 这里随便整个POJO都行,只要相关的属性对的上就行
                    Person userDo = new Person();
                    userDo.setName("TestGroovy" + nameSuffix);
                    userDo.setCode(666);
                    // 添加域对象
                    map.put("user",userDo);
                    String placeHolder = (String) strTemplates.get(index);
                    String userName = generateMessage(map, placeHolder);
                    log.info(placeHolder + ": " + userName + Thread.currentThread().getName());
                }
            }).start();
        }

    }

}
  • 优化后
public class TestSimpleTemplateEngineAfterTuning {

    private final static Logger log = LoggerFactory.getLogger(TestSimpleTemplateEngineAfterTuning.class);
    // 添加模版类缓存
    private final static ConcurrentHashMap<String, Template> templateCaches = new ConcurrentHashMap<>();
    private final static ArrayList strTemplates = new ArrayList();

    static {
        strTemplates.add("${user.name}");
        strTemplates.add("${user.code}");
        strTemplates.add("${user.company}");
        strTemplates.add("${user.address}");
        strTemplates.add("${user.message}");
    }

    public static Template getTemplate(String placeHolder) throws IOException, ClassNotFoundException {
        Template template = templateCaches.get(placeHolder);
        if (template != null) return template;
        template = new SimpleTemplateEngine().createTemplate(placeHolder);
        templateCaches.put(placeHolder, template);
        return template;
    }

    public static String generateMessage(Map map, String placeHolder) {
        String msg = null;
        try {
            msg = getTemplate(placeHolder).make(map).toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return msg;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (; ; ) {
                    Map<String, Object> map = new HashMap<>();
                    int nameSuffix = new Random().nextInt(900) + 100;
                    int index = new Random().nextInt(strTemplates.size());
                    Person userDo = new Person();
                    userDo.setName("TestGroovy" + nameSuffix);
                    userDo.setCode(666);
                    map.put("user", userDo);
                    String placeHolder = (String) strTemplates.get(index);
                    String userName = generateMessage(map, placeHolder);
                    log.info(placeHolder + ": " + userName + Thread.currentThread().getName());
                }

            }).start();
        }
    }
}
请先登录,再评论

大佬多分享这种实际案例🙏

3周前

很好

1月前

为你推荐