风控安全——规则引擎
风控的规则引擎非常适合用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);
}
GroovyClassLoader 加载机制导致频繁gc问题
GroovyClassLoader groovyLoader = new GroovyClassLoader();
Class<Script> groovyClass = (Class<Script>) groovyLoader.parseClass(groovyScript);
Script groovyScript = groovyClass.newInstance();
每次执行groovyLoader.parseClass(groovyScript),Groovy为了保证每次执行的都是新的脚本内容,每次生成一个新名字的Class文件,当对同一段脚本执行时,会导致装载的Class会越来越多,导致PermGen被用满。
解决方法:
- 对于parseClass后生成的Class对象进行cache,key为groovyScript脚本md5值
// 1.加载脚本,并缓存
GroovyObject object = loadClass(classSeq);
cacheMap.put(md5(classSeq), object);
// 2.预热
// 模拟方法调用
cacheMap.get(md5(classSeq)).invoke();
// 3.开放给线上流量使用