天天看點

MetrAutoAPI系統架構設計

作者:閃念基因

1.背景

公司正在進行資料戰略轉型,是以我們面臨的資料需求越來越多,而我們擁有的名額資料越來越豐富。不過,僅僅擁有資料并不夠,我們需要能夠更加靈活高效的使用這些資料,以應對頻繁多變的資料需求。傳統的資料業務開發流程效率低下,無法及時響應變化的需求。是以,我們必須找到一種快速、靈活的解決方案,以滿足我們快速變化的需求。

2.MetrAutoAPI設計

2.1

平台介紹

MetrAutoAPI(Metric Automate API即名額自動化API)将名額資料與應用層做分離,MetrAutoAPI僅負責管理和處理資料資訊,對外提供了一個通用的API接口,所有的資料請求和響應都通過這個接口進行傳輸和處理。

MetrAutoAPI支援多個資料源內建,其API模組化功能靈活可配置,可通過頁面拖拽和配置生成SQL查詢語句,幫助使用者快速準确地擷取所需資料。此外,規則引擎服務可以對查詢結果進行靈活的運算,幫助使用者實作自動化計算和分析,進而提高效率。

2.2

架構設計

MetrAutoAPI系統架構設計

實體查詢層:通過統一查詢引擎,實作對不同來源資料庫的查詢

語義模型層:負責名額中繼資料的管理,并對API-SQL模型進行管理。

統一服務層:提供名額次元的API建構功能,以及基于名額次元的資料查詢和規則引擎配置服務。

統一接口層:提供一個對外的API接口,所有的資料請求和響應都通過這個接口進行傳輸和處理。

此方案對比傳統開發模式:

MetrAutoAPI系統架構設計

2.3

使用場景

以下是MetrAutoAPI的一些使用場景的介紹:

►資料看闆展示

MetrAutoAPI系統架構設計

2.4

核心功能

►2.4.1 SQL模組化服務

利用Zealot與MySqlStatementParser實作SQL模組化功能

可以根據不同的查詢條件和參數動态生成對應的SQL語句,進而避免手動拼接SQL語句帶來的代碼備援和錯誤,可以很好地支援參數的綁定和傳遞,可以通過占位符或命名參數的方式傳遞參數,同時支援參數類型的自動轉換

SQL模組化過程如下:

public static List<FieldVo> getFieldName(String sqlStr) { 
MySqlStatementParser mySqlStatementParser = new MySqlStatementParser(sqlStr); 
//使用parpser解析生成的AST,這裡sqlStatement是AST,AST為抽象文法樹 
SQLStatement sqlStatement = mySqlStatementParser.parseStatement(); 
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); 
sqlStatement.accept(visitor); 

//list是存儲表名和字段名的集合 
List<FieldVo> list = new ArrayList<>(); 
Collection<TableStat.Column> columns = visitor.getColumns(); 
//通過循環将表名和字段名解析出,并存儲到list集合中 
columns.stream().forEach(row -> { 
if (row.isSelect()) { 
FieldVo fieldVo = new FieldVo(); 
fieldVo.setTableName(row.getTable()); 
fieldVo.setFieldName(row.getName()); 
list.add(fieldVo); 
} 
}); 

List<FieldVo> aliasList = getAliasField(sqlStr); 
for (int i = 0 ; i < list.size() ; i ++) { 
FieldVo vo = list.get(i); 
FieldVo aliasVo = aliasList.get(i); 
if (Objects.isNull(aliasVo.getAliasName())) { 
vo.setAliasName(vo.getFieldName()); 
} else { 
vo.setAliasName(aliasVo.getAliasName()); 
} 
} 
return list; 
}           

自動生成API-SQL接口文檔說明

MetrAutoAPI系統架構設計

►2.4.2 API-aviator規則引擎服務

Aviator是一個高性能、輕量級的java語言實作的表達式求值引擎,主要用于各種表達式的動态求值,使用規則引擎可以把複雜、重複的業務規則同各個業務系統分離開,以提高業務邏輯的複用能力和開發效率。

MetrAutoAPI系統架構設計

規則表達式設定如下:

MetrAutoAPI系統架構設計

初始化規則引擎

public class AviatorEvaluatorUtils { 
private static AviatorEvaluatorInstance instance = AviatorEvaluator.getInstance(); 

public AviatorEvaluatorUtils() { 
} 

public static AviatorEvaluatorInstance getInstance() { 
return instance; 
} 

static { 
instance.addFunction(new TransNullToZeroRule()); 
instance.addFunction(new IsNullRuleFunction()); 
instance.addFunction(new CrrRadioRule()); 
instance.addFunction(new CrrRule()); 
} 
}            

規則引擎服務會解析并執行規則表達式。

private List<Map<String, Object>> expResult(List<RestApiVo> fieldList, List<Map<String, Object>> dataList) { 
Stopwatch started = Stopwatch.createStarted(); 
List<Map<String, Object>> resultList = new ArrayList<>(); 
for (Map<String, Object> map : dataList) { 
Map<String, Object> dataMap = new HashMap<>(); 
fieldList.forEach(e -> { 
try { 
Expression exp = AviatorEvaluatorUtils.getInstance().compile(e.getFieldExp(), true); 
Object value = exp.execute(map); 
if (Objects.isNull(value)) { 
dataMap.put(e.getFieldName(), null); 
} else { 
if (e.getIsFormat().intValue() == 1) { 
BigDecimal decimal = new BigDecimal(value.toString()); 
BigDecimal scale = decimal.setScale(e.getNumberFormat().intValue(), BigDecimal.ROUND_HALF_UP); 
dataMap.put(e.getFieldName(), scale); 
} else { 
dataMap.put(e.getFieldName(), value); 
} 
} 
} catch (Exception ex) { 
log.error("解析表達式異常,字段:{},結果:{}", JsonUtil.serialize(e), JsonUtil.serialize(map), ex); 
dataMap.put(e.getFieldName(), null); 
} 
}); 
resultList.add(dataMap); 
} 
log.warn("轉換結果耗時:{}", started.stop()); 
return resultList; 
}            

►2.4.3 API統一查詢引擎

提供标準化的接口和協定,使得調用端可以通過一緻的方式來請求和響應多個不同的 API。這樣做可以簡化開發人員的工作,加快應用程式的開發速度,同時提高系統的可靠性和可維護性。

/** 
* 統一API接口 
* @param _appId appId 
* @param params 入參 
* @return 
*/ 
@PostMapping("/restApi") 
public Protocol<List<Map<String, Object>>> restApi(String _appId, @RequestBody Map<String, Object> params, String apiKey) { 
ParamsValid valid = new ParamsValid(); 
valid.validNotNull("params", params) 
.valid("apiId與apiKey不能同時為空", () -> { 
if (Objects.isNull(params.get("apiId")) && Objects.isNull(params.get("apiKey"))) { 
return false; 
} 
return true; 
}).valid("apiKey值不正确", () -> { 
if (Objects.nonNull(params.get("apiKey"))) { 
if (Strings.isNullOrEmpty(params.get("apiKey").toString())) { 
return false; 
} 
} 
return true; 
}); 
if (!valid.isValid()) { 
return valid.showInValidMessage(); 
} 
Protocol<List<Map<String, Object>>> protocol = null; 
try { 
protocol = targetAutoService.restApi(params, _appId, 1); 
} catch (DataSelfException ex) { 
log.error("查詢資料異常:param:{},apiKey:{}", JsonUtil.serialize(params), apiKey, ex); 
return new Protocol<>(-1, ex.toString()); 
} catch (Exception e) { 
log.error("查詢資訊異常:param:{}", JsonUtil.serialize(params), e); 
return new Protocol<>(-1, "查詢異常,請重試"); 
} 
return protocol; 
}            

流程如下:

MetrAutoAPI系統架構設計

3.實踐過程中問題及解決方案

3.1

使用過程中遇到的難題

接口性能差:接口性能差,分析日志發現從資料庫中讀取配置與規則引擎資訊耗時較長,性能較差

解決方案:使用redis作為緩存,存儲模型中繼資料資訊,統一API引擎在讀取配置資料前先從redis中擷取,如果擷取不到再從業務庫中讀取,并将讀取到的資料寫入redis緩存,設定過期時間,定期清除redis緩存中過期的資料,避免占用過多的記憶體;通過以上優化,可以有效減少從資料庫中讀取配置資料的時間,提高接口性能。

使用緩存後性能對比

MetrAutoAPI系統架構設計

上線成本高:測試環境模組化并驗證完成後,還需要線上上環境再次模組化,不僅重複操作并且可能因為人為疏忽造成線上線下模型不一緻,進而造成嚴重後果。

解決方案:使用資訊複制可以簡化測試環境到線上環境的配置過程,進而提高工作效率。具體實作步驟如下:

在測試環境中模組化,并将模型中繼資料資訊儲存為一個JSON格式的資料,通過粘貼闆複制功能,将JSON資訊複制到線上環境,通過權限控制來進行安全控制(配置簡單化),避免人為異常。

多API接口合并:由于調用方可能需要的名額過于繁瑣,可能涉及多個名額API接口的調用,造成調用方調用次數過多,造成并發多,壓力大,影響調用方的使用或者造成調用鍊過長

解決方案:采用接口聚合的方式來解決。接口聚合是将多個API接口的資料聚合到一個API接口中,使得調用方隻需要調用一個API接口就能擷取到需要的所有名額資料,避免了多次調用導緻的并發過多和響應時間過長的問題。同時,也可以提高接口的可用性,避免接口出錯或者異常導緻調用失敗。

4.參考文獻

ApiJson: http://apijson.cn/doc/zh/

Mybatis:ttps://github.com/mybatis/mybatis-dynamic-sql

Zealot:ps://gitee.com/chenjiayin1990/zealot

Aviator:ttps://www.yuque.com/boyan-avfmj/aviatorscript

作者簡介

李賀曉

■ 經銷商技術部-i車商團隊。

■ 2018年加入汽車之家,任職于經銷商技術部-i車商團隊,目前主要負責資料類産品開發和探索

來源:微信公衆号:之家技術

出處:https://mp.weixin.qq.com/s/jleYpeR2imQ25mZcbz3wiA

繼續閱讀