当前位置: 首页 > 后端技术 > Java

Java热更新Groovy实践与陷阱指南

时间:2023-04-02 01:02:39 Java

什么是Groovy?Apache的Groovy是一种在Java平台上设计的面向对象的编程语言。这种动态语言具有类似于Python、Ruby和Smalltalk的一些特性,可以作为Java平台的脚本语言。Groovy代码被动态编译成Java字节码,在Java虚拟机(JVM)上运行,并与其他Java代码和库互操作兼容。由于其在JVM上运行的特性,Groovy可以使用以其他Java语言编写的库。Groovy的语法与Java非常相似,而且大多数Java代码也符合Groovy的语法规则,尽管语义可能不同。Groovy1.0于2007年1月2日发布,Groovy2.0于2012年7月发布。从版本2开始,Groovy也可以静态编译,提供类型推断和类似Java的性能。截至2015年3月,Groovy2.4是PivotalSoftware赞助的最后一个主要版本。Groovy已将其治理结构更改为Apache软件基金会的项目管理委员会(PMC)[1]。为什么Java需要Groovy?Groovy的特点是:在句法上支持动态类型、闭包等新一代语言特性,无缝集成所有现有的Java类库,同时支持面向对象编程和面向过程编程。文件被编译成class字节码文件,然后交给JVM执行,也可以直接解释groovy源文件执行。Groovy可以和Java完美结合,你可以使用所有的Java库。Groovy的优点如下:敏捷的groovy在语法上加了很多句法糖,很多Java严格写语法。在Groovy中,只需要少量的语法糖就可以实现Groovy的灵活性。它可以用作编程语言或脚本语言,零成本学习Groovy。完美适配Java语法热部署技术的设计与实现。使用场景我将介绍以下适合Groovy脚本热更新的常用场景,供大家学习风控安全——规则引擎。风控的规则引擎很适合用groovy实现。为了打击黑产,兵家们每天都会制定拦截规则。如果每次都需要出一个版本,可能发完了观察,最后该榨的薅羊毛都被黑社会给蹭光了。因此利用groovy脚本引擎的动态解析和执行,通过规则脚本抽象出搜索拦截规则,实现快速部署,提高效率。监控中心拥有海量的互联网系统,各级人员需要时刻关注业务各个维度的指标。这个时候,单靠人肉是无法达到某个指标异常的。此时需要监控中心介入,提前部署异常规则。当出现异常时,监控中心会发出警报,通知相应的规则创建人员,以便尽快查明原因,挽回资金损失。这时候就需要保证监控中心极其灵活,能够满足业务人员或者研发人员随时随地配置监控指标。对于测试,我们可以使用Groovy条件表达式来满足灵活的监控规则配置需求。事件营销营销事件配置是我个人发现的最复杂的业务之一。各种活动模板,千人千面,不同人群看到的活动形式或“奖品”各不相同。而且活动要迅速开展,效果恢复,投入产出比要马上观察。这时,工程方需要将整个活动模板抽象出来,在需要改动的地方嵌入Groovy脚本,以减少测试和发布的时间,让活动在线可配置。技术实现脚加载/更新代码实现展示:/***添加脚*@paramscript*@return*/publicstaticGroovyObjectbuildScript(Stringscript){if(StringUtils.isEmpty(script)){thrownewRuntimeException("script是空的”);}StringcacheKey=DigestUtils.md5DigestAsHex(script.getBytes());if(groovyObjectCache.containsKey(cacheKey)){log.debug("groovyObjectCachehit");返回groovyObjectCache.get(cacheKey);}GroovyClassLoaderclassLoader=newGroovyClassLoader();尝试{ClassgroovyClass=classLoader.parseClass(script);GroovyObjectgroovyObject=(GroovyObject)groovyClass.newInstance();类加载器.clearCache();groovyObjectCache.put(cacheKey,groovyObject);log.info("groovybuildScript成功:{}",groovyObject);返回常规对象;}catch(Exceptione){thrownewRuntimeException("buildScripterror",e);}最后{尝试{classLoader.close();}catch(IOExceptione){log.error("关闭GroovyClassLoader错误",e);}}}重点注意:脚本开启缓存处理:否则多次更新可能导致MetaspaceOutOfMemeryscriptExecution//程序需要关联脚本才能执行。尝试{MapsingleMap=GroovyUtils.invokeMethod2Map(s.getScriptObject(),s.getInvokeMethod(),params);data.putAll(singleMap);}catch(Throwablee){log.error(String.format("RcpEventMsgCleanScriptGroovyHandlegroovyerror,guid:%deventCode:%s",s.getGuid(),s.getEventCode()),e);}//三执行方法,看脚本返回的结果是什么);}publicstaticbooleaninvokeMethod2Boolean(GroovyObjectscriptObject,StringinvokeMethod,Object[]params){return(Boolean)scriptObject.invokeMethod(invokeMethod,params);}publicstaticStringinvokeMethod2String(GroovyObjectscriptObject,StringinvokeMethod,Object[]params){log.debug("GroovyObject类:{}",scriptObject.getClass().getSimpleName());return(String)scriptObject.invokeMethod(invokeMethod,params);}制作踩坑指南Java8lambda和Groovy语法问题说的是Groovy完美兼容Java语法,即直接将Java代码复制到Groovy文件中也能编译成功。真的是这样吗?我们看执行的代码如下:Setdemo=newHashSet<>();demo.add("111");demo.add("222");for(Strings:demo){executor.submit({->println"submit:"+s;});}for(Stringsindemo){executor.submit({->println"spsubmit:"+s;});}//输出//submit:222//spsubmit:222//submit:222//spsubmit:222此时代码并没有按预期输出111、222,为什么?答:Groovy中lambda语法的语义与Java中的不一致。虽然编译是正确的,但是所表达的语义不一致表达了Groovy中闭包的概念。如果你在这里不熟悉,你可以谷歌了解更多关于Groovy语法的信息。GroovyClassLoader加载机制导致频繁的gc问题。Groovy类代码通常加载如下:每次执行groovyLoader.parseClass(groovyScript),为了保证每次执行的都是新的脚本内容,Groovy每次都会生成一个新名字的Class文件,这个在上一篇文章中已经说明。当每次都对同一个脚本执行该方法时,现象就是加载的Class越来越多,导致PermGen用完。同时,这里也存在性能瓶颈问题。如果分析这段代码,你会发现90%的耗时都被Class占用了。在上面的实战过程中,已经给出了解决方案:缓存parseClass后生成的Class对象,key是groovyScript脚本的md5值。脚本的第一次执行需要很长时间。最初的解决方案上线时,压测发现第一次加载脚本的性能较慢,后续脚本的执行速度非常快。猜测是Groovy在第一次脚本化的时候做了其他的检查(这个我没有跟进,有兴趣的读者可以查看断点处的链接了解具体的路耗时间在哪里)就是面临的问题第一次加载慢,解决方法如下://1.加载脚本并缓存GroovyObjectobject=loadClass(classSeq);cacheMap.put(md5(classSeq),object);//2.预热//模拟方法调用cacheMap.get(md5(classSeq)).invoke();//3.开通线上流量使用之前精彩的性能调优——小日志大坑性能优化必不可少——火焰图Flink在风控场景下的实时特性实现欢迎关注公众号:咕咕鸡技术专栏个人技术博客:https://jifuwei.github.io/参考:[1]GroovyWiki