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]。
Groovy 特性如下:
Groovy 優勢如下:
我將介紹如下幾種常用的適合 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);
}
}
}
重點關注:
// 程式內部需要關聯出待執行的指令碼即可
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);
}
都說 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 語法。
通常載入 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。
如上實戰過程中,已經給出瞭解決辦法:
在初期方案上線時,壓測後顯示,首次載入指令碼效能較慢,後續指令碼執行速度非常快,猜測可能是 Groovy 內部在首次腳在指令碼時還做了其他的校驗(本人還沒跟進這塊,如果有讀者感興趣,可以斷點詳細看下鏈路耗時在哪裡)
正對首次載入緩慢問題,解決方法如下:
// 1.載入指令碼,並快取
GroovyObject object = loadClass(classSeq);
cacheMap.put(md5(classSeq), object);
// 2.預熱
// 模擬方法呼叫
cacheMap.get(md5(classSeq)).invoke();
// 3.開放給線上流量使用
個人技術部落格:https://jifuwei.github.io/
公眾號:是咕咕雞
參考:
[1] Groovy Wiki