Java 热更新 Groovy 实践及踩坑指南(推荐)

目录
  • Groovy 是什么?
  • Java 为何需要 Groovy ?
  • 热部署技术设计及实现
  • 使用场景
  • 风控安全——规则引擎
  • 监控中心
  • 活动营销
  • 技术实现
  • 脚本加载/更新
  • 脚本执行
  • 生产踩坑指南
  • Java8 lambda 与 Groovy 语法问题
  • GroovyClassLoader 加载机制导致频繁gc问题
  • 脚本首次执行耗时高

Groovy 是什么?

Apache的Groovy是Java平台上设计的面向对象编程语言。这门动态语言拥有类似Python、Ruby和Smalltalk中的一些特性,可以作为Java平台的脚本语言使用,Groovy代码动态地编译成运行于Java虚拟机(JVM)上的Java字节码,并与其他Java代码和库进行互操作。

由于其运行在JVM上的特性,Groovy可以使用其他Java语言编写的库。Groovy的语法与Java非常相似,大多数Java代码也符合Groovy的语法规则,尽管可能语义不同。 Groovy 1.0于2007年1月2日发布,并于2012年7月发布了Groovy 2.0。从版本2开始,Groovy也可以静态编译,提供类型推论和Java相近的性能。Groovy 2.4是Pivotal软件赞助的最后一个主要版本,截止于2015年3月。Groovy已经将其治理结构更改为Apache软件基金会的项目管理委员会(PMC)[1]。

Java 为何需要 Groovy ?

Groovy 特性如下:

  • 语法上支持动态类型,闭包等新一代语言特性
  • 无缝集成所有已经存在的Java类库
  • 既支持面向对象编程也支持面向过程编程
  • 执行方式可以将groovy编写的源文件编译成class字节码文件,然后交给JVM去执行,也可以直接将groovy源文件解释执行。
  • Groovy可以与Java完美结合,而且可以使用java所有的库

Groovy 优势如下:

  • 敏捷

    • groovy 在语法上加入了很多语法糖,很多 Java 严格的书写语法,在 Groovy 中只需要少量的语法糖即可实现
  • Groovy 的灵活性是的它既可以作为变成语言,亦可作为脚本语言
  • 0成本学习 Groovy,完美适配 Java 语法

热部署技术设计及实现

使用场景

我将介绍如下几种常用的适合 Groovy 脚本热更新的场景,供您学习

风控安全——规则引擎

风控的规则引擎非常适合用 groovy 来实现,对抗黑产,策略人员每天都都会产出拦截规则,如果每次都需要发版,可能发完观测完后,该薅的羊毛都被黑产薅没了。

所以利用 groovy 脚本引擎的动态解析执行,使用规则脚本将查拦截规则抽象出来,快速部署,提升效率。

监控中心

大型互联网系统,伴随着海量数据进入,各个层级的人员需要时时刻刻关注业务的各个维度指标,此时某个指标异常光靠人肉是没办法实现的。此时需要监控中心介入,提前部署好异动规则,当异常发生时,监控中心发出告警通知到对应的规则创建人员,从而尽快查明原因,挽回资损。

此时要保证监控中心异常灵活,可以随时随地满足业务人员或者研发人员配置监控指标,测试我们可以使用 Groovy 条件表达式,满足灵活监控规则配置需求。

活动营销

营销活动配置是我个人觉得最复杂的业务之一。活动模板多样,千人千面,不同人群看到的活动样式或者“奖品”不一。且活动上线要快,效果回收,投入产出比等要能立即观测。

此时需要工程侧抽象出整个活动模板,在需要变化的地方嵌入 Groovy 脚本,这样就减少了测试和发版的时间,做到活动可线上配置化。

技术实现

脚本加载/更新

代码实现展示:

/**
 * 加载脚本
 * @param script
 * @return
 */
public static GroovyObject buildScript(String script) {
    if (StringUtils.isEmpty(script)) {
        throw new RuntimeException("script is empty");
    }

    String cacheKey = DigestUtils.md5DigestAsHex(script.getBytes());
    if (groovyObjectCache.containsKey(cacheKey)) {
        log.debug("groovyObjectCache hit");
        return groovyObjectCache.get(cacheKey);
    }

    GroovyClassLoader classLoader = new GroovyClassLoader();
    try {
        Class<?> groovyClass = classLoader.parseClass(script);
        GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
        classLoader.clearCache();

        groovyObjectCache.put(cacheKey, groovyObject);
        log.info("groovy buildScript success: {}", groovyObject);
        return groovyObject;
    } catch (Exception e) {
        throw new RuntimeException("buildScript error", e);
    } finally {
        try {
            classLoader.close();
        } catch (IOException e) {
            log.error("close GroovyClassLoader error", e);
        }
    }
}

重点关注:

  • 脚本开启缓存处理:否则多次会更新可能会导致 Metaspace OutOfMemery

脚本执行

// 程序内部需要关联出待执行的脚本即可
try {
    Map<String, Object> singleMap = GroovyUtils.invokeMethod2Map(s.getScriptObject(), s.getInvokeMethod(), params);
    data.putAll(singleMap);
} catch (Throwable e) {
    log.error(String.format("RcpEventMsgCleanScriptGroovyHandle groovy error, guid: %d eventCode: %s",
            s.getGuid(), s.getEventCode()), e);
}

// 三种执行方式,看 脚本内部返回的结果是什么
public static Map<String, Object> invokeMethod2Map(GroovyObject scriptObject, String invokeMethod, Object[] params) {
    return (Map<String, Object>) scriptObject.invokeMethod(invokeMethod, params);
}

public static boolean invokeMethod2Boolean(GroovyObject scriptObject, String invokeMethod, Object[] params) {
    return (Boolean) scriptObject.invokeMethod(invokeMethod, params);
}

public static String invokeMethod2String(GroovyObject scriptObject, String invokeMethod, Object[] params) {
    log.debug("GroovyObject class: {}", scriptObject.getClass().getSimpleName());
    return (String) scriptObject.invokeMethod(invokeMethod, params);
}

生产踩坑指南

Java8 lambda 与 Groovy 语法问题

都说 Groovy 能完美兼容 Java 语法,即直接复制 Java 代码到 Groovy 文件内,亦能编译成功。

事实真的如此么,我们看如下执行的代码:

Set<String> demo = new HashSet<>();
demo.add("111");
demo.add("222");
for (String s : demo) {
    executor.submit({ ->
        println "submit: " + s;
    });
}

for (String s in demo) {
    executor.submit({ ->
        println "sp submit: " + s;
    });
}
// 输出结果
// submit: 222
// sp submit: 222
// submit: 222
// sp submit: 222

此时代码并没有按照预期的结果输出 111, 222,这是为什么呢?

答:lambda 语法在 Groovy 中语义和在Java 中不一致,虽然编译不出错,但表达的语义不一致

在 Groovy 中表示闭包概念,此处不熟悉的可以 Google 详细了解 Groovy 语法。

GroovyClassLoader 加载机制导致频繁gc问题

通常加载 Groovy 类代码如下:

GroovyClassLoader groovyLoader = new GroovyClassLoader();
Class<Script> groovyClass = (Class<Script>) groovyLoader.parseClass(groovyScript);
Script groovyScript = groovyClass.newInstance();

每次执行 groovyLoader.parseClass(groovyScript),Groovy 为了保证每次执行的都是新的脚本内容,会每次生成一个新名字的Class文件,这个点已经在前文中说明过。当对同一段脚本每次都执行这个方法时,会导致的现象就是装载的Class会越来越多,从而导致PermGen被用满。

同时这里也存在性能瓶颈问题,如果去分析这段代码会发现90%的耗时占用在Class。

如上实战过程中,已经给出了解决办法:

  • 对于 parseClass 后生成的 Class 对象进行cache,key 为 groovyScript 脚本的md5值

脚本首次执行耗时高

在初期方案上线时,压测后显示,首次加载脚本性能较慢,后续脚本执行速度非常快,猜测可能是 Groovy 内部在首次脚在脚本时还做了其他的校验(本人还没跟进这块,如果有读者感兴趣,可以断点详细看下链路耗时在哪里)

正对首次加载缓慢问题,解决方法如下:

// 1.加载脚本,并缓存
GroovyObject object = loadClass(classSeq);
cacheMap.put(md5(classSeq), object);
// 2.预热
// 模拟方法调用
cacheMap.get(md5(classSeq)).invoke();

// 3.开放给线上流量使用

到此这篇关于Java 热更新 Groovy 实践及踩坑指南的文章就介绍到这了,更多相关Java 热更新 Groovy内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java动态脚本Groovy获取Bean技巧

    目录 一.使用BeanFactoryPostProcessor注入Bean: 第一步:创建实现SpringUtils 接口工具(组件)来获取spring bean 第二步:创建Groovy脚本装载类,动态解析脚本为Class 第三步:读取脚本内容,执行脚本 第四步:在resources目录下创建.groovy文件 第五步:实例化脚本,执行方法  二.使用ApplicationContext注入Bean 第一步:修改项目启动类,获得ApplicationContext 第二步:修改resource

  • 详解Java执行groovy脚本的两种方式

    记录Java执行groovy脚本的两种方式,简单粗暴: 一种是通过脚本引擎ScriptEngine提供的eval(String)方法执行脚本内容:一种是执行groovy脚本: 二者都通过Invocable来传递参数并获取执行结果: Invocable:脚本引擎的解释器接口,提供invokeFunction和invokeMethod两种传递参数并获取执行结果的方法,Java JDK API文档解释如下: invokeFunction: invokeMethod: 以下为案例: 引入依赖 <depe

  • Java动态脚本Groovy

    目录 1.Groovy特性 2.核心涉及 3.Java与Groovy转换 第一步:引入Groovy依赖 第二步:创建interface接口声明方法 第三步:在resources目录下创建.groovy文件 第四步:创建Groovy脚本装载类,动态解析脚本为Class 第五步:读取脚本内容,执行脚本 4.Groovy特性验证 第一步:将之前Groovy脚本数据修改.存于数据库表中,动态加载脚本 第二步:数据库表中:添加.查询Groovy脚本,动态加载执行 第三步:多次修改表数据值,查看执行结果 5

  • Java调用groovy实现原理代码实例

    一.概述 Groovy is a multi-faceted language for the Java platform. Apache Groovy是一种强大的.可选的类型化和动态语言,具有静态类型和静态编译功能,用于Java平台,目的在于通过简洁.熟悉和易于学习的语法提高开发人员的工作效率.它可以与任何Java程序顺利集成,并立即向您的应用程序提供强大的功能,包括脚本编写功能.特定于域的语言编写.运行时和编译时元编程以及函数式编程. Groovy是基于java虚拟机的,执行文件可以是简单的

  • 详解如何热更新线上的Java服务器代码

    一.前言 1.热更新代码的场景 (1)当线上服务器出现问题时,有些时候现有的手段不足以发现问题所在,可能需要追加打印日志或者增加一些调试代码,如果我们去改代码重新部署,会破坏问题现场,可以通过热部署的手段来增加调试代码 (2)线上出现紧急bug,通过Review代码找到问题,修改好后打包部署的流程可能比较久,可以通过热部署代码及时解决问题 二.Arthas的使用 使用阿里巴巴开源的Java诊断工具---Arthas,他可以附着在我们的Java服务器进程上面,查看服务器状态,jvm状态等各种参数指

  • 使用arthas命令redefine实现Java热更新(推荐)

    arthas 是一个 Java 开源诊断神器. 今天分享一个非常重要的命令 redefine ,主要作用是加载外部的 .class 文件,用来替换 JVM 已经加载的类,总结起来就是实现了 Java 的热更新. redefine 在一下几种情况中会失败:1.增加了 field :2.增加了 method :3.替换正在运行的方法. 前两个比较好理解,第三个意思就是这个方法必须结束之后才会被替换,如果有个方法开始运行之后就不会跳出,那么这个方法所在的类是无法被替换的,类似无限循环的方法. 中间提到

  • Java 热更新 Groovy 实践及踩坑指南(推荐)

    目录 Groovy 是什么? Java 为何需要 Groovy ? 热部署技术设计及实现 使用场景 风控安全——规则引擎 监控中心 活动营销 技术实现 脚本加载/更新 脚本执行 生产踩坑指南 Java8 lambda 与 Groovy 语法问题 GroovyClassLoader 加载机制导致频繁gc问题 脚本首次执行耗时高 Groovy 是什么? Apache的Groovy是Java平台上设计的面向对象编程语言.这门动态语言拥有类似Python.Ruby和Smalltalk中的一些特性,可以作

  • .NET+PostgreSQL实践与避坑指南(推荐)

    简介 .NET+PostgreSQL(简称PG)这个组合我已经用了蛮长的一段时间,感觉还是挺不错的.不过大多数人说起.NET平台,还是会想起跟它"原汁原味"配套的Microsoft SQL Server(简称MSSQL),其实没有MSSQL也没有任何问题,甚至没有Windows Server都没问题,谁说用.NET就一定要上微软全家桶?这都什么年代了-- PG和MSSQL的具体比较我就不详细展开了,自行搜一下,这种比较分析文章很多.应该说两个RDBMS各有特色,MSSQL工具集庞大(大

  • 记一次MySQL更新语句update的踩坑

    背景 最近在一次线上作业过程中执行了一句DML语句,本以为万无一失,结果应用反馈说没有更新,数据还是没有变,最后经过排查才发现是我语句写错了,导致update语句执行的结果与预期不符. 情景再现 为了方便演示,建立一张用户表,同时插入五条数据. create table user( id int(12) comment '用户主键id', name varchar(36) comment '用户名', age int(12) comment '年龄'); insert into user val

  • Java Bean转Map的那些踩坑实战

    目录 一.背景 二.那些坑 2.0 测试对象 2.1 JSON 反序列化了类型丢失 2.1.1 问题复现 2.2.2 问题描述 2.2 BeanMap 转换属性名错误 2.2.1 commons-beanutils 的 BeanMap 2.2.2 使用 cglib 的 BeanMap 三.解决办法 3.1 解决方案 3.2 原理解析 四.总结 一.背景 有些业务场景下需要将 Java Bean 转成 Map 再使用. 以为很简单场景,但是坑很多. 二.那些坑 2.0 测试对象 import lo

  • 关于实现Vue3版抖音滑动插件踩坑指南

    目录 起步 调研 实现思路 工程构建 代码实现 video实现 slide.vue 组合使用 视频自动播放问题 git地址 总结 起步 年前单位需要搞一个类似抖音的需求,这本应是客户端的任务,然而,不知天高地厚的我却接了下来,然而下细致调研之下,发现网上并没有成熟的方案,但是却又很多需求,各大论坛全是提问的帖子,却少有人回答和解决. 这一瞬间,俺慌了,毕竟单位的活,排期都是定死的,这时候临阵退缩,实乃下下策.于是只能撸起袖子加油干.毕竟自己揽的事,含着泪也要干完,这就是男人,一个吐沫一个钉! 调

  • Docker 安装Jenkins全过程及踩坑指南

    目录 Docker安装Jenkins 1.安装过程 2.Jenkins安装npm 2.1.替换容器的源 2.2.安装nodejs和npm 3.Jenkins流水线+Docker+Maven+Githubwebhooks+Springboot 3.1.需要的插件和配置 3.1.1.插件 BlueOcean MavenIntegration 3.1.2.配置 3.1.3.安装maven 3.1.4.配置Jenkins使用Docker 3.1.5.配置Jenkins中的GitSSH 3.1.6.Git

  • vue-cli2.x旧版本卸载不掉的问题踩坑指南(附Vue脚手架安装教程)

    目录 旧版本卸载 问题说明 问题解释 问题解决 总结 旧版本卸载 问题说明 vue2.x脚手架旧版本卸载不掉的问题:(卸载命令运行后输入vue --version仍一直显示旧版本) 问题解释 首先明确一点,卸载不掉肯定说明你的电脑中至少有两个地方存在脚手架,而你的卸载操作只是卸载了其中之一.而出现这个情况的原因,一般来说是你在npm的默认路径下安装过脚手架,在配置了npm全局安装路径之后又安装过脚手架,所以有两个脚手架.(顺带一提,这样的情况提示的都是默认安装路径下的vue脚手架版本) 问题解决

  • ant-design-vue 快速避坑指南(推荐)

    ant-design-vue是蚂蚁金服 Ant Design 官方唯一推荐的Vue版UI组件库,它其实是Ant Design的Vue实现,组件的风格与Ant Design保持同步,组件的html结构和css样式也保持一致. 用下来发现它的确称得上为数不多的完整的VUE组件库与开发方案集成项目. 本文主要目的是总结一些开发过程中比较耗时间去查找,文档中没有具体说明的常见问题,同时希望能给新上手此框架的同学提供一些参考作用. 1.Table对接后台返回数据 针对Table数据格式与后他接口返回数据格

  • Python使用ClickHouse的实践与踩坑记录

    目录 1. 关于ClickHouse使用实践 1.1. ClickHouse 应用于数据仓库场景 1.2. 客户端工具DBeaver 1.3. 大数据应用实践 2. Python使用ClickHouse实践 2.1. ClickHouse第三方Python驱动clickhouse_driver 2.2. 实践程序代码 3. 小结一下 操作ClickHouse删除指定数据 ClickHouse是近年来备受关注的开源列式数据库(DBMS),主要用于数据联机分析(OLAP)领域,于2016年开源.目前

随机推荐