摘要: 1、方案背景 訂單系統存在於各行各業,如電商訂單、銀行流水、運營商話費帳單等,是一個很是普遍、通用的系統。對於這類系統,在過去十幾年發展中已經造成了經典的作法。可是隨着互聯網的發展,以及各企業對數據的重視,須要存儲和持久化的訂單量愈來愈大。java
1、方案背景
訂單系統存在於各行各業,如電商訂單、銀行流水、運營商話費帳單等,是一個很是普遍、通用的系統。對於這類系統,在過去十幾年發展中已經造成了經典的作法。可是隨着互聯網的發展,以及各企業對數據的重視,須要存儲和持久化的訂單量愈來愈大。數據的重視程度與數據規模的膨脹帶來了新的挑戰。node
需求場景
某電商平臺A,須要進行持久化全部平臺產生的訂單數據。同時,基於全部的訂單數據,系統又須要向外提供面向多種角色:消費者、店家、平臺三類人羣的多元化的查詢服務。消費者能夠查詢本身的歷史訂單,商家能夠統計熱銷產品,平臺也能夠分析用戶行爲、平臺交易規模等。主要查詢方式涵蓋訂單的多維度檢索,以及訂單數據的分析、統計等,例如:
面向消費者:【A消費者】【近1年】【賣出電腦】訂單查詢;
面向售貨員:【B售貨員】*【近1個月】銷售訂單;
......git
技術點
在訂單場景中,技術上一般須要考慮的技術點,主要包含以下幾個方面:github
查詢能力:須要具有豐富的查詢類型,如多維度、範圍、模糊查詢等,同時具有排序、統計等功能;
數據量:存儲海量數據的同時,知足強一致、高可用、低成本等要求;
服務性能:應對高併發請求高併發的同時,保證低延遲;
實現多維、實時查詢功能,是訂單管理解決方案的核心功能,官網控制檯地址:項目樣例數據庫
2、方案演進
應對訂單場景,電商一般會採用MySQL傳統方案。藉助關係型數據庫強大的查詢能力,用戶可直接經過SQL語句實現訂單數據的多維度查詢、數據統計等。所謂數據膨脹,分爲橫向、縱向兩種,橫向即不斷迭代引入的新字段維度,縱向即總的存儲數據量。在面對這兩種訂單數據膨脹上,單MySql方案逐漸變得吃力。 SQL + NoSQL的組合方案(如下稱:組合方案)便應運而生,藉助兩個數據庫各自的優點分別解決不一樣場景各自的需求。但組合方案一樣也帶來了新的問題,組合方案犧牲空間成本,同時也增長了開發工做量與運維複雜度。在保證數據一致性上產生額外開銷。
下面讓咱們看一下以下幾個常規方案:併發
常規方案
一、MySql分庫分表方案
MySql自身擁有強大的數據查詢、分析功能,基於MyQql建立訂單系統,能夠應對訂單數據多維查詢、統計場景。伴隨着訂單數據量的增長,用戶會採起分庫、分表方案應對,經過這種僞分佈式方案,解決數據膨脹帶來的問題。但數據一旦達到瓶頸,便須要從新建立更大規模的分庫+數據的全量遷移,麻煩就會不斷出現。數據迭代、膨脹帶來的困擾,是MySql方案難於逾越的。僅僅依靠MySql的傳統訂單方案短板凸顯。
一、數據縱向(數據規模)膨脹:採用分庫分表方案,MySql在部署時須要預估分庫規模,數據量一旦達到上限後,從新部署並作數據全量遷移;
二、數據橫向(字段維度)膨脹:schema需預約義,迭代新增新字段變動複雜。而維度到達必定量後影響數據庫性能;運維
二、MySql+HBase方案
引入雙數據的方案應運而生,經過實時數據、歷史數據分存的方案,能夠必定程度解決數據量膨脹問題。該方案將數據歸類成兩部分存儲:實時數據、歷史數據。同時經過數據同步服務,將過時數據同步至歷史數據。
一、實時訂單數據(例如:近3個月的訂單):將實時訂單存入MySql數據庫。實時訂單的總量膨脹的速度獲得了限制,同時保證了實時數據的多維查詢、分析能力;
二、歷史訂單數據(例如:3個月之前的訂單):將歷史訂單數據存入HBase,藉助於HBase這一分佈式NoSql數據庫,有效應對了訂單數據膨脹困擾。也保證了歷史訂單數據的持久化;
可是,該方案犧牲了歷史訂單數據對用戶、商家、平臺的使用價值,假設了歷史數據的需求頻率極低。可是一旦有需求,便須要全表掃描,查詢速度慢、IO成本很高。而維護數據同步又帶來了數據一致性、同步運維成本飆升等難題;分佈式
三、MySql+Elasticsearch方案
組合方案還有MySql+Elasticsearch,該方案一樣是將數據分兩部分存儲,能夠必定程度解決訂單索引維度增加問題。用戶本身維護數據同步服務,保證兩部分數據的一致性;
一、全量數據:將全量的訂單數據存入MySql數據庫,訂單ID以外的數據總體存爲一個字段。該全量數據做爲持久化存儲,也用於非索引字段的反查;
二、查詢數據:僅將須要檢索的字段存入Elasticsearch(基於Lucene分佈式索引數據庫),藉助於Elasticsearch的索引能力,提供能夠應付維度膨脹的訂單數據,而後必要時反查MySql獲取訂單完整信息;
該方案應付了數據維度膨脹帶來的困擾,可是隨着訂單量的不斷膨脹,MySql擴展性差的問題再次暴露出來。同時數據同步至Elasticsearch的方案,開發、運維成本很高,方案選擇也存在弊端。
表格存儲(TableStore)方案
若是使用表格存儲(TableStore)研發的多元索引(SearchIndex)方案,則能夠完美地解決億量級訂單系統問題。TableStore具備即開即用,按量收費等特色。多元索引隨時建立,是海量電商訂單元數據管理的優質方案。
TableStore做爲阿里雲提供的一款全託管、分佈式NoSql型數據存儲服務,具備【海量數據存儲】、【熱點數據自動分片】、【海量數據多維檢索】等功能,自然地解決了訂單數據大爆炸這一挑戰;
同時,SearchIndex功能在保證用戶數據高可用的基礎上,提供了數據多維度搜索、統計等能力。針對多種場景建立多種索引,實現多種模式的檢索。用戶能夠僅在須要的時候建立、開通索引。由TableStore來保證數據同步的一致性,這極大的下降了用戶的方案設計、服務運維、代碼開發等工做量。高併發
基於表格存儲搭建的訂單系統頁面一覽
樣例內嵌在表格存儲控制檯中,用戶可登陸控制檯體驗系統(若爲表格存儲的新用戶,須要點擊開通服務後體驗,開通免費,訂單數據存儲在公共實例中,體驗不消耗用戶存儲、流量、Cu)。
注:該樣例提供了【億量級】訂單數據。官網控制檯地址:項目樣例性能
2、搭建準備
若您對於億量級訂單系統的體驗不錯,但願開始本身系統的搭建之旅,只需按照以下步驟即可以着手搭建了:
一、開通表格存儲
經過控制檯開通表格存儲服務,表格存儲即開即用(後付費),採用按量付費方式,已爲用戶提供足夠功能測試的免費額度。表格存儲官網控制檯、免費額度說明。
二、建立實例
經過控制檯建立表格存儲實例,選擇支持多元索引的Region。(當前階段SearchIndex功能還沒有商業化,暫時開放北京、上海、深圳、杭州四地,後續逐漸開放)
建立實例後,提交工單申請多元索引功能邀測(商業化後默認打開,不使用不收費)。
邀測地址:提工單,選擇【表格存儲】>【產品功能、特性諮詢】>【建立工單】,申請內容以下:
問題描述:請填寫【申請SearchIndex邀測】
機密信息:請填寫【地域+實例名】,例:上海+myInstanceName
三、SDK下載
使用具備多元索引(SearchIndex)的SDK,官網地址,暫時java、go、node.js三種SDK增長了新功能
java-SDK
<dependency>
<groupId>com.aliyun.openservices</groupId> <artifactId>tablestore</artifactId> <version>4.7.4</version>
</dependency>
go-SDK
$ go get github.com/aliyun/aliyun-tablestore-go-sdk
四、表設計
訂單系統不只僅是訂單一張數據表,它應包含:消費者表、售貨員表、產品表、供貨商表、交易訂單表、支付訂單表等。在本樣例中,豬腰使用最基本的四張表(消費者表、售貨員表、產品表、交易訂單表),僅以訂單表舉例以下:
表名:order_contract
3、開始搭建(核心代碼)
一、建立數據表
四張表:訂單表、消費者表、售貨員表、產品表
用戶僅需維護一個實例,按以下方式建立:經過控制檯建立、管理數據表(用戶也能夠經過SDK直接建立):
二、建立數據表索引
TableStore自動作全量、增量的索引數據同步:用戶能夠經過控制檯建立、管理SearchIndex(用戶也可經過SDK建立):
三、數據導入
插入部分測試數據(控制檯樣例中插入了1億條數據,用戶本身能夠經過控制檯插入少許測試數據);
四、數據讀取
數據讀取分爲兩類:
主鍵讀取
基於原生表格存儲的主鍵列獲取:getRow, getRange, batchGetRow等。主鍵讀取用於索引(自動)反查,用戶也能夠提供主鍵(訂單md5)的單條查詢的頁面,億量級下查詢速度極快。單主鍵查詢方式不支持多維度檢索;
索引讀取
基於新SearchIndex功能Query:search接口。用戶能夠自由設計索引字段的多維度條件組合查詢。經過設置選擇不一樣的查詢參數,構建不一樣的查詢條件、不一樣排序方式;目前支持:精確查詢、範圍查詢、前綴查詢、匹配查詢、通配符查詢、短語匹配查詢、分詞字符串查詢,並經過布爾與、或組合。
如【c0001號消費者,且消費在99.99以上的訂單】組合方式以下:
List<Query> mustQueries = new ArrayList<Query>();
TermQuery termQuery = new TermQuery();
termQuery.setFieldName("cId");
termQuery.setTerm(ColumnValue.fromString("c0001"));
mustQueries.add(termQuery);
RangeQuery rangeQuery = new RangeQuery();
rangeQuery.setFieldName("totalPrice");
rangeQuery.setFrom(ColumnValue.fromDouble(99.99));
mustQueries.add(rangeQuery);
BoolQuery boolQuery = new BoolQuery();boolQuery.setMustQueries(mustQueries);