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.開放給線上流量使用
往期精彩
個人技術博客:https://jifuwei.github.io/
公眾號:是咕咕雞
- 性能調優——小小的log大大的坑
- 性能優化必備——火焰圖
- Flink 在風控場景實時特征落地實戰
參考:
[1] Groovy Wiki
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/507121.html
標籤:Java
上一篇:Redis6.0.6的三大記憶體過期策略和八大淘汰策略
下一篇:Java 函式式編程「一」
