基于TableStore的海量气象格点数据解决方案实战
前言
氣象數據是一類典型的大數據,具有數據量大、時效性高、數據種類豐富等特點。氣象數據中大量的數據是時空數據,記錄了時間和空間范圍內各個點的各個物理量的觀測量或者模擬量,每天產生的數據量常在幾十TB到上百TB的規模,且在爆發性增長。如何存儲和高效的查詢這些氣象數據越來越成為一個難題。
傳統的方案常常采用關系型數據庫加文件系統的方式實現這類氣象數據的存儲和實時查詢,這種方案在可擴展性、可維護性和性能上都有一些缺陷,隨著數據規模的增大,缺點越來越明顯。最近幾年,業界開始越來越多的基于分布式NoSQL來解決這一問題,比如基于TableStore來實現氣象格點數據的存儲和查詢。TableStore是一款阿里自研的分布式NoSQL服務,可以提供超大規模的存儲容量,支撐超大規模的并發訪問和低延遲的性能,可以很好的解決氣象數據的規模和查詢性能問題。
我們之前也寫過相關的解決方案文章《基于云上分布式NoSQL的海量氣象數據存儲和查詢方案》,也有一些客戶基于這個方案進行了開發。出于減少客戶開發難度,提供通用的實現的想法,我們最近開發了一個TableStore-Grid的Library,基于這個Library用戶可以非常方便的實現氣象格點數據的存儲、查詢和管理。本文作為一個實戰文章,主要講解這一解決方案的設計以及使用方式。
背景
格點數據的特點
格點數據具有明顯的多維特點,以模式系統每次產生的數據為例,一般包含以下五個維度:
當我們固定某一要素某一預報時效,那么高度、經度、緯度就構成一個三維網格數據,如下圖所示(圖片來自互聯網)。每個格點代表了一個三維空間上的點,上面的數值為該點在某一預報時效(比如未來三小時)下,某一物理量(比如溫度)的預報值。
假設一個三維格點空間包含10個不同高度的平面,每個平面為一個2880 x 570的格點,每個格點保存一個4字節數據,那么這三維的數據量為2880 x 570 x 4 x 10, 大約64MB。這僅僅是某個模式系統對某個物理量某一時效下的一次預報,可見模式數據的總量是非常大的。
格點數據的查詢方式
預報員會通過頁面的形式瀏覽各種模式數據(格點數據),并進行數值模式預報。這個頁面需要提供多種模式數據的查詢方式,比如:
上面提到,一個格點數據集一般是一個五維結構,各種查詢方式實際上就是對這個五維數據進行切分,比如查詢某個平面,每個剖面,某個點序列,某個三維、四維子空間等等。而我們的方案設計要保證在各種查詢條件的查詢性能,這是數據查詢方面的主要技術難點。
基于TableStore的方案設計
標準化格點數據模型
首先,我們定義一個規整的五維網格數據為一個GridDataSet,表示一個格點數據集,按照維度順序,其五維分別為:
GridDataSet = F(variable, time, z, x, y)。
一個GridDataSet除了包含五維數據,以及各個維度的長度等外,還包含一些其他信息:
GridDataSetId:唯一標記這個GridDataSet的Id。 Attributes:自定義屬性信息,比如該數據的產生時間、數據來源、預報類型等等。用戶可以自由定義自定義屬性,也可以給某些屬性建立索引,建立索引后就可以通過各種組合條件來查詢符合條件的數據集。復制代碼舉個例子來說,假設某種氣象預報,每次預報未來72小時的每個整點的各個高度、各個經緯度的各種物理量,則這次預報就是一個標準的五維數據,是一個單獨的數據集(GridDataSet),下一次相同的預報則是另一個數據集,這兩個數據集需要有不同的GridDataSetId。這兩個數據集比較類似,只是起報時間不同,但是因為起報時間不在五維模型中(五維內的時間為一次預報中的未來不同時刻),所以屬于不同的數據集,起報時間可以作為數據集的自定義屬性。本方案中,也支持對自定義屬性設置條件進行檢索。
數據存儲方案
我們設計了兩張表分別存儲數據集(GridDataSet)的meta和data,meta表示這個數據集的各種元數據,比如GridDataSetId、各維度長度、自定義屬性等等,data表示這個數據集里實際的網格數據。data相比meta在數據大小上要大很多。
為什么要分為meta和data兩張表分開存儲,主要是出于這樣的考慮:
meta表設計
meta表的設計比較簡單,主鍵只有一列,記錄GridDataSetId,因為GridDataSetId就可以唯一標記一個GridDataSet。各種系統屬性和自定義屬性保存在meta表的屬性列中。
查詢meta表有兩種方式,一種是通過GridDataSetId直接查詢,另外一種是通過多元索引,可以根據多種屬性條件組合進行查詢,比如篩選某種類型的數據,按照入庫時間從新到老返回等。
data表設計
data表的設計要解決五維數據在不同的切分模式下的查詢效率問題,不能簡單直接的對數據進行存儲。
首先,為了查詢效率最高,我們要盡量減少一次查詢需要掃描的數據量。一個數據集的數據量可能在幾GB的級別,但是一次查詢往往只需要其中的幾MB的數據,如果無法高效的定位要查詢的數據,那么就要掃描全部的幾GB的數據,從中篩選出符合某個范圍的數據,顯然效率是很低的。那么怎么才能做到高效的定位到需要的數據之中呢?
我們首先設計一種表結構設計方式,我們使用四列主鍵列,分別為:
GridDataSetId:數據集Id,唯一標記這個數據集。
Variable:變量名,即五維模型中的第一維。
Time:時間,即五維模型中的第二維。
Z:高度,即五維模型中的第三維。
這四列主鍵列標記一行TableStore中的數據,這行數據需要保存后兩維的數據,即一個格點平面。
這種設計下,對于五維中的前三維,我們都可以通過主鍵列的值來定位,即對于前三維的每一種情況,都對應TableStore中的一行。因為前三維分別代表變量、時間和高度,一般而言不會特別的多,每個維度在幾個到幾十個的級別,我們可以通過一些并行查詢的方法來加速查詢速度。
剩下的問題就在于后兩維數據如何存儲和查詢。首先后兩維代表了一個水平的平面,一般是一個經緯度網格,這兩維的大小是比前三維要大很多的,每維在幾百到幾千的級別,隨著數值預報越來越精細化,這個網格的大小還會成倍增加。這樣的一個稠密的網格數據,我們不能把每個格點都用一列來保存,這樣列的數量會非常多,存儲效率也會非常的低。另一方面,如果我們把一個平面的格點數據存儲到一列中,在整讀整取時效率比較高,但是如果只讀取某個點,就會讀取很多的無效數據,效率又會變得比較低。因此我們采取一種折中的方案,對平面的二維數據再次進行切分,切分成更小的平面數據塊,這樣就可以做到只讀取部分數據塊,而不總是讀取整個平面,因此極大的提高了查詢性能。
方案實現
基于上面的存儲方案,我們實現了一個TableStore-Grid的library,提供以下接口:
public interface GridStore {/*** 創建相關的meta、data表,數據錄入前調用。* @throws Exception*/void createStore() throws Exception;/*** 寫入gridDataSet的meta信息。* @param meta* @throws Exception*/void putDataSetMeta(GridDataSetMeta meta) throws Exception;/*** 更新meta信息。* @param meta* @throws Exception*/void updateDataSetMeta(GridDataSetMeta meta) throws Exception;/*** 通過gridDataSetId獲取meta。* @param dataSetId* @return* @throws Exception*/GridDataSetMeta getDataSetMeta(String dataSetId) throws Exception;/*** // 創建meta表的多元索引。* @param indexName* @param indexSchema* @throws Exception*/void createMetaIndex(String indexName, IndexSchema indexSchema) throws Exception;/*** 通過多種查詢條件來查詢符合條件的數據集。* @param indexName 多元索引名。* @param query 查詢條件,可以通過QueryBuilder構建。* @param queryParams 查詢相關參數,包括offset、limit、sort等。* @return* @throws Exception*/QueryGridDataSetResult queryDataSets(String indexName, Query query, QueryParams queryParams) throws Exception;/*** 獲取GridDataWriter用于寫入數據。* @param meta* @return*/GridDataWriter getDataWriter(GridDataSetMeta meta);/*** 獲取GridDataFetcher用于讀取數據。* @param meta* @return*/GridDataFetcher getDataFetcher(GridDataSetMeta meta);/*** 釋放資源。*/void close(); }public interface GridDataWriter {/*** 寫入一個二維平面。* @param variable 變量名。* @param t 時間維的值。* @param z 高度維的值。* @param grid2D 平面數據。* @throws Exception*/void writeGrid2D(String variable, int t, int z, Grid2D grid2D) throws Exception; }public interface GridDataFetcher {/*** 設置要查詢的變量。* @param variables* @return*/GridDataFetcher setVariablesToGet(Collection<String> variables);/*** 設置要讀取的各維度起始點和大小。* @param origin 各維度起始點。* @param shape 各維度大小。* @return*/GridDataFetcher setOriginShape(int[] origin, int[] shape);/*** 獲取數據。* @return* @throws Exception*/GridDataSet fetch() throws Exception; }復制代碼下面我們分別給出數據錄入、數據查詢、數據集檢索方面的示例。
數據錄入
數據錄入流程可以分為三部分:
下面的例子中,我們讀取一個NetCDF(氣象格點數據常用的格式)文件,然后將其中的數據通過GridDataWriter錄入到TableStore中。通過GridDataWriter每次寫入時,只能寫入一個二維平面,所以我們需要在外層進行3層循環,分別枚舉變量維、時間維、高度維的值,然后讀取對應的二維平面的數據進行錄入。
public void importFromNcFile(GridDataSetMeta meta, String ncFileName) throws Exception {GridDataWriter writer = tableStoreGrid.getDataWriter(meta);NetcdfFile ncFile = NetcdfFile.open(ncFileName);List<Variable> variables = ncFile.getVariables();for (Variable variable : variables) {if (meta.getVariables().contains(variable.getShortName())) {for (int t = 0; t < meta.gettSize(); t++) {for (int z = 0; z < meta.getzSize(); z++) {Array array = variable.read(new int[]{t, z, 0, 0}, new int[]{1, 1, meta.getxSize(), meta.getySize()});Grid2D grid2D = new Grid2D(array.getDataAsByteBuffer(), variable.getDataType(),new int[] {0, 0}, new int[] {meta.getxSize(), meta.getySize()});writer.writeGrid2D(variable.getShortName(), t, z, grid2D);}}}} }復制代碼數據查詢
GridDataFetcher支持對五維數據進行任意維度的查詢。第一維是變量維,通過setVariablesToGet接口設置要讀取哪些變量,其余四維通過設置起始點(origin)和讀取的大小(shape)就可以實現任意維度讀取。
public Array queryByTableStore(String dataSetId, String variable, int[] origin, int[] shape) throws Exception {GridDataFetcher fetcher = this.tableStoreGrid.getDataFetcher(this.tableStoreGrid.getDataSetMeta(dataSetId));fetcher.setVariablesToGet(Arrays.asList(variable));fetcher.setOriginShape(origin, shape);Grid4D grid4D = fetcher.fetch().getVariable(variable);return grid4D.toArray(); }復制代碼多條件檢索數據集
本方案中,對Meta表建立多元索引后,可以支持通過各種組合條件來進行數據集檢索,查詢出符合條件的數據集,這個功能對于氣象管理系統來說非常重要。
下面舉一個例子,假設我們要查詢已經完成入庫的,創建時間為最近一天的,來源為ECMWF(歐洲中期天氣預報中心)或者NMC(全國氣象中心),精度為1KM的氣象預報,并按照創建時間從新到老排序,可以用以下代碼實現:
查詢條件: (status == DONE) and (create_time > System.currentTimeMillis - 86400000) and (source == "ECMWF" or source == "NMC") and (accuracy == "1km")
QueryGridDataSetResult result = tableStoreGrid.queryDataSets(ExampleConfig.GRID_META_INDEX_NAME,QueryBuilder.and().equal("status", "DONE").greaterThan("create_time", System.currentTimeMillis() - 86400000).equal("accuracy", "1km").query(QueryBuilder.or().equal("source", "ECMWF").equal("source", "NMC").build()).build(),new QueryParams(0, 10, new Sort(Arrays.<Sort.Sorter>asList(new FieldSort("create_time", SortOrder.DESC)))));復制代碼是不是非常簡單?這一部分功能利用了TableStore的多元索引,多元索引可以實現多字段組合查詢、模糊查詢、全文檢索、排序、范圍查詢、嵌套查詢、空間查詢等功能,給元數據管理場景提供了強大的底層能力。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
轉載于:https://juejin.im/post/5cdd12f66fb9a0323f68c509
總結
以上是生活随笔為你收集整理的基于TableStore的海量气象格点数据解决方案实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java版b2b2c社交电商spring
- 下一篇: 大数据时代,如何让个人信息不再“裸奔”?