基于TableStore的海量电商订单元数据管理
一、背景
訂單系統(tǒng)存在于各行各業(yè),如電商訂單、銀行流水、運營商話費賬單等,是一個非常廣泛、通用的系統(tǒng)。對于這類系統(tǒng),在過去十幾年發(fā)展中已經(jīng)形成了經(jīng)典的做法。但是隨著互聯(lián)網(wǎng)的發(fā)展,以及各企業(yè)對數(shù)據(jù)的重視,需要存儲和持久化的訂單量越來越大。數(shù)據(jù)的重視程度與數(shù)據(jù)規(guī)模的膨脹帶來了新的挑戰(zhàn),原有的系統(tǒng)是否還能繼續(xù)滿足需求成了焦點?
需求場景
某電商平臺A,需要進行持久化所有平臺產(chǎn)生的訂單數(shù)據(jù)。同時,基于所有的訂單數(shù)據(jù),系統(tǒng)又需要向外提供面向多種角色:消費者、店家、平臺三類人群的多元化的查詢服務(wù)。消費者可以查詢自己的歷史訂單,商家可以統(tǒng)計熱銷產(chǎn)品,平臺也可以分析用戶行為、平臺交易規(guī)模等。主要查詢方式涵蓋訂單的多維度檢索,以及訂單數(shù)據(jù)的分析、統(tǒng)計等,例如:
面向消費者:【A消費者】*【近1年】*【產(chǎn)品名含'電腦'字段】訂單查詢;
面向店家:【B店家】*【近1個月】*【每個產(chǎn)品】銷售量排名;
......
技術(shù)點
在訂單場景中,技術(shù)上通常需要考慮的技術(shù)點,主要包含如下幾個方面:
- 查詢能力:需要具備豐富的查詢類型,如多維度、范圍、模糊查詢等,同時具備排序、統(tǒng)計等功能;
- 數(shù)據(jù)量:存儲海量數(shù)據(jù)的同時,滿足強一致、高可用、低成本等要求;
- 服務(wù)性能:應(yīng)對高并發(fā)請求高并發(fā)的同時,保證低延遲;
?
二、方案演進
應(yīng)對訂單場景,電商通常會采用MySQL傳統(tǒng)方案。借助關(guān)系型數(shù)據(jù)庫強大的查詢能力,用戶可直接通過SQL語句實現(xiàn)訂單數(shù)據(jù)的多維度查詢、數(shù)據(jù)統(tǒng)計等。所謂數(shù)據(jù)膨脹,分為橫向、縱向兩種,橫向即不斷迭代引入的新字段維度,縱向即總的存儲數(shù)據(jù)量。在面對這兩種訂單數(shù)據(jù)膨脹上,單MySql方案逐漸變得吃力。?SQL + NoSQL的組合方案(以下稱:組合方案)便應(yīng)運而生,借助兩個數(shù)據(jù)庫各自的優(yōu)勢分別解決不同場景各自的需求。但組合方案同樣也帶來了新的問題,組合方案犧牲空間成本,同時也增加了開發(fā)工作量與運維復(fù)雜度。在保證數(shù)據(jù)一致性上產(chǎn)生額外開銷。
下面讓我們看一下如下幾個常規(guī)方案:
常規(guī)方案
1、MySql分庫分表方案
MySql自身擁有強大的數(shù)據(jù)查詢、分析功能,基于MyQql創(chuàng)建訂單系統(tǒng),可以應(yīng)對訂單數(shù)據(jù)多維查詢、統(tǒng)計場景。伴隨著訂單數(shù)據(jù)量的增加,用戶會采取分庫、分表方案應(yīng)對,通過這種偽分布式方案,解決數(shù)據(jù)膨脹帶來的問題。但數(shù)據(jù)一旦達到瓶頸,便需要重新創(chuàng)建更大規(guī)模的分庫+數(shù)據(jù)的全量遷移,麻煩就會不斷出現(xiàn)。數(shù)據(jù)迭代、膨脹帶來的困擾,是MySql方案難于逾越的。僅僅依靠MySql的傳統(tǒng)訂單方案短板凸顯。
1、數(shù)據(jù)縱向(數(shù)據(jù)規(guī)模)膨脹:采用分庫分表方案,MySql在部署時需要預(yù)估分庫規(guī)模,數(shù)據(jù)量一旦達到上限后,重新部署并做數(shù)據(jù)全量遷移;
2、數(shù)據(jù)橫向(字段維度)膨脹:schema需預(yù)定義,迭代新增新字段變更復(fù)雜。而維度到達一定量后影響數(shù)據(jù)庫性能;
2、MySql+HBase方案
引入雙數(shù)據(jù)的方案應(yīng)運而生,通過實時數(shù)據(jù)、歷史數(shù)據(jù)分存的方案,可以一定程度解決數(shù)據(jù)量膨脹問題。該方案將數(shù)據(jù)歸類成兩部分存儲:實時數(shù)據(jù)、歷史數(shù)據(jù)。同時通過數(shù)據(jù)同步服務(wù),將過期數(shù)據(jù)同步至歷史數(shù)據(jù)。
1、實時訂單數(shù)據(jù)(例如:近3個月的訂單):將實時訂單存入MySql數(shù)據(jù)庫。實時訂單的總量膨脹的速度得到了限制,同時保證了實時數(shù)據(jù)的多維查詢、分析能力;
2、歷史訂單數(shù)據(jù)(例如:3個月以前的訂單):將歷史訂單數(shù)據(jù)存入HBase,借助于HBase這一分布式NoSql數(shù)據(jù)庫,有效應(yīng)對了訂單數(shù)據(jù)膨脹困擾。也保證了歷史訂單數(shù)據(jù)的持久化;
但是,該方案犧牲了歷史訂單數(shù)據(jù)對用戶、商家、平臺的使用價值,假設(shè)了歷史數(shù)據(jù)的需求頻率極低。但是一旦有需求,便需要全表掃描,查詢速度慢、IO成本很高。而維護數(shù)據(jù)同步又帶來了數(shù)據(jù)一致性、同步運維成本飆升等難題;
3、MySql+Elasticsearch方案
組合方案還有MySql+Elasticsearch,該方案同樣是將數(shù)據(jù)分兩部分存儲,可以一定程度解決訂單索引維度增長問題。用戶自己維護數(shù)據(jù)同步服務(wù),保證兩部分?jǐn)?shù)據(jù)的一致性;
1、全量數(shù)據(jù):將全量的訂單數(shù)據(jù)存入MySql數(shù)據(jù)庫,訂單ID之外的數(shù)據(jù)整體存為一個字段。該全量數(shù)據(jù)作為持久化存儲,也用于非索引字段的反查;
2、查詢數(shù)據(jù):僅將需要檢索的字段存入Elasticsearch(基于Lucene分布式索引數(shù)據(jù)庫),借助于Elasticsearch的索引能力,提供可以應(yīng)付維度膨脹的訂單數(shù)據(jù),然后必要時反查MySql獲取訂單完整信息;
該方案應(yīng)付了數(shù)據(jù)維度膨脹帶來的困擾,但是隨著訂單量的不斷膨脹,MySql擴展性差的問題再次暴露出來。同時數(shù)據(jù)同步至Elasticsearch的方案,開發(fā)、運維成本很高,方案選擇也存在弊端。
| 存儲方式 | 行存儲 | 列存儲 | 索引存儲 | 列存儲+索引存儲 |
| 擴展性 | 單機、擴展性差 | 水平擴展 | 水平擴展 | (自動)水平擴展 |
| 一致性 | 強一致性 | 強一致性、時序一致性 | ? | 強一致性、時序一致性 |
| 檢索 | 較弱的支持 | 不支持 | 支持 | 支持 |
| 數(shù)據(jù)量 | ~ 1T,~億行 | ~10 PB,~萬億行 | ~1 PB,~千億行 | ~10 PB,~萬億行 |
TableStore方案
如果使用表格存儲(TableStore)研發(fā)的多元索引(SearchIndex)方案,則可以完美地解決以上問題。TableStore具有即開即用,按量收費等特點。多元索引隨時創(chuàng)建,是海量電商訂單元數(shù)據(jù)管理的優(yōu)質(zhì)方案。
TableStore作為阿里云提供的一款全托管、分布式NoSql型數(shù)據(jù)存儲服務(wù),具有【海量數(shù)據(jù)存儲】、【熱點數(shù)據(jù)自動分片】、【海量數(shù)據(jù)多維檢索】等功能,天然地解決了訂單數(shù)據(jù)大爆炸這一挑戰(zhàn);
同時,SearchIndex功能在保證用戶數(shù)據(jù)高可用的基礎(chǔ)上,提供了數(shù)據(jù)多維度搜索、統(tǒng)計等能力。針對多種場景創(chuàng)建多種索引,實現(xiàn)多種模式的檢索。用戶可以僅在需要的時候創(chuàng)建、開通索引。由TableStore來保證數(shù)據(jù)同步的一致性,這極大的降低了用戶的方案設(shè)計、服務(wù)運維、代碼開發(fā)等工作量。
?
三、基于表格存儲實現(xiàn)的訂單場景Demo
業(yè)務(wù)描述:
每成功完成一筆交易,就會生成一筆交易數(shù)據(jù)。交易數(shù)據(jù)包含了交易中的必要元素,如:交易時間、交易的雙方、交易的產(chǎn)品、數(shù)量、價格等,這里選擇最基本元素舉例,僅將必要字段簡歷索引,格式如下:
訂單持久化數(shù)據(jù)
表名:"order_table"
| order_id(主鍵列) | KEYWORD | String | 均勻散列的字符串 |
| time_stamp | LONG | long | 交易時間戳 |
| consumer_id | KEYWORD | String | 消費者 |
| seller_id | KEYWORD | String | 商家unique編號 |
| product_id | KEYWORD | String | 產(chǎn)品unique編號 |
| product_name | KEYWORD | String | 產(chǎn)品名 |
| product_type | KEYWORD | String | 產(chǎn)品類型 |
| product_price | DOUBLE | double | 產(chǎn)品單價 |
| product_count | ? | double | ? |
| total_pay | ? | double | ? |
| description | ? | String | ? |
| ...... | ...... | ...... | ...... |
創(chuàng)建訂單表
用戶僅需維護一個數(shù)據(jù)庫,按如下方式創(chuàng)建:用戶可以通過控制臺創(chuàng)建、管理Table,也可通過SDK
List<PrimaryKeySchema> primaryKey = Arrays.asList(new PrimaryKeySchema("order_id", PrimaryKeyType.STRING) );TableMeta tableMeta = new TableMeta(tableName); tableMeta.addPrimaryKeyColumns(primaryKey); CreateTableRequest request = new CreateTableRequest(tableMeta, new TableOptions(-1, 1)); CreateTableResponse createTableResponse = otsClient.createTable(request);創(chuàng)建索引
用戶根據(jù)自身需求,在需要的時候隨時創(chuàng)建索引。TableStore自動做全量、增量的索引數(shù)據(jù)同步:用戶可以通過控制臺創(chuàng)建、管理SearchIndex,也可通過SDK按如下方式創(chuàng)建(索引暫不支持update)
CreateSearchIndexRequest createSearchIndexRequest = new CreateSearchIndexRequest(); createSearchIndexRequest.setTableName("tableName"); createSearchIndexRequest.setIndexName("indexName");IndexSchema indexSchema = new IndexSchema(); indexSchema.setIndexSetting(new IndexSetting(1));//必寫 indexSchema.setFieldSchemas(Arrays.asList(new FieldSchema("product_id", FieldType.KEYWORD).setIndex(true).setEnableSortAndAgg(true).setStore(true),new FieldSchema("product_name", FieldType.TEXT).setIndex(true),//TEXT不能設(shè)置docValuesnew FieldSchema("product_type", FieldType.KEYWORD).setIndex(true).setEnableSortAndAgg(true).setStore(true),new FieldSchema("product_count", FieldType.DOUBLE).setIndex(true).setEnableSortAndAgg(true).setStore(true),new FieldSchema("consumer_id", FieldType.KEYWORD).setIndex(true).setEnableSortAndAgg(true).setStore(true),new FieldSchema("seller_id", FieldType.KEYWORD).setIndex(true).setEnableSortAndAgg(true).setStore(true),new FieldSchema("total_pay", FieldType.DOUBLE).setIndex(true).setEnableSortAndAgg(true).setStore(true),new FieldSchema("time_stamp", FieldType.LONG).setIndex(true).setEnableSortAndAgg(true).setStore(true) )); createSearchIndexRequest.setIndexSchema(indexSchema);CreateSearchIndexResponse createSearchIndexResponse = otsClient.createSearchIndex(createSearchIndexRequest);數(shù)據(jù)讀取
數(shù)據(jù)讀取分為兩類:
1、基于原生表格存儲的主鍵列獲取:getRow, getRange, batchGetRow等;
2、基于新SearchIndex功能Query:search;
主鍵讀取
GetRowRequest getRowRequest = new GetRowRequest();PrimaryKey pk = new PrimaryKey(new PrimaryKeyColumn[]{new PrimaryKeyColumn("order_id", PrimaryKeyValue.fromString("fa960b5af")) });SingleRowQueryCriteria singleRowQueryCriteria = new SingleRowQueryCriteria("order_table", pk); singleRowQueryCriteria.setMaxVersions(1); getRowRequest.setRowQueryCriteria(singleRowQueryCriteria);GetRowResponse rowResponse = o?tsClient.getRow(getRowRequest);Search讀取
新增的search接口,通過設(shè)置QueryRequest實現(xiàn)不同query,不同aggregation,不同sort的功能
SearchQuery searchQuery = new SearchQuery();//設(shè)置查詢條件,用戶發(fā)揮 searchQuery.setQuery(Query anyQuery);//做分頁 searchQuery.setLimit(10); searchQuery.setOffSet(0);SearchRequest searchRequest = new SearchRequest("tableName", "indexName", searchQuery);SearchRequest.ColumnsToGet columnsToGet = new SearchRequest.ColumnsToGet(); columnsToGet.setColumns(columnsToShow);//List<String> columnsToShow searchRequest.setColumnsToGet(columnsToGet);SearchResponse resp = otsClient.search(searchRequest);返回結(jié)構(gòu)
SearchResponse extends Response {private long totalCount;//query匹配成功數(shù)據(jù)總數(shù)private List<Row> rows;//query匹配數(shù)據(jù)列表(1)private boolean isAllSuccess; }場景Demo
search功能主要分為三種:(多維度)查詢,排序,聚合,使用上通過三種功能的組合來實現(xiàn);
場景1:多維度查詢
【"consumer_001"用戶】【上個月】購買【產(chǎn)品名含某"牙膏"字段】的訂單記錄
使用:BoolQuery, TermQuery, RangeQuery, MatchPhraseQuery
場景2:查詢,排序
整個平臺【上個月】【單訂單支付金額】排行榜Top10
使用:RangeQuery, FieldSort
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的基于TableStore的海量电商订单元数据管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hadoop迁移MaxCompute神器
- 下一篇: 云端一体化差分+安全升级,AliOS T