java基礎(chǔ)教學(xué)專欄帶大家了解如何便捷的統(tǒng)計(jì)訂單效益。
引言
#統(tǒng)計(jì)訂單收益是做電商類型的APP老生常談的問(wèn)題.常規(guī)需求大致有用戶收益日?qǐng)?bào)/月報(bào)/年報(bào)
.這些報(bào)表型的數(shù)據(jù)對(duì)錶設(shè)計(jì)和程序設(shè)計(jì)有著不小的挑戰(zhàn).常規(guī)的聚合查詢
語(yǔ)句的查詢時(shí)間會(huì)隨著收益表資料日漸龐大而逐漸變長(zhǎng)。這時(shí)候就需要思考如何設(shè)計(jì)收益表可以更有效率的查詢?怎樣的設(shè)計(jì)才可以讓統(tǒng)計(jì)收益變得簡(jiǎn)單?
需求
#效果圖

具體需求
收益類型分為:自購(gòu)訂單收益,分享訂單收益,分銷收益,活動(dòng)收益 統(tǒng)計(jì)當(dāng)日收益,當(dāng)月收益 根據(jù)篩選的時(shí)間統(tǒng)計(jì)出時(shí)間段的收益.
思考
#設(shè)計(jì)想法
#訂單表是肯定需要的.在寫入或修改訂單表的時(shí)候同步寫入修改收益表.只有自購(gòu)和分享訂單會(huì)記錄到訂單表中,分銷以及活動(dòng)贈(zèng)送收益只在特殊業(yè)務(wù)中寫入收益表.再以日為維度,建立一張用戶
收益日?qǐng)?bào)表.單行記錄寫入用戶當(dāng)天收益狀況.
降低
可見(jiàn)問(wèn)題
#同步收益日?qǐng)?bào)表的時(shí)機(jī)問(wèn)題,因?yàn)樵居唵蔚牟僮骶秃苎}雜需要同步寫入收益和計(jì)算寫入收益日?qǐng)?bào)資料,程式碼耦合度太高.有沒(méi)有什麼方法透過(guò)收益表異構(gòu)出收益日?qǐng)?bào)呢? 雖然收益被寫入到了日?qǐng)?bào)表中,但是要滿足效果圖要求的效果,可能需要多次查詢SQL語(yǔ)句,有沒(méi)有辦法在不影響程序效率的情況下盡量少些一些聚合SQL呢?
實(shí)作
總結(jié)出上面這些問(wèn)題.我開始了資料收集.最終採(cǎi)用canal
RocketMQ
做為異質(zhì)方案.
#技術(shù)堆疊
簡(jiǎn)單介紹一下這兩款技術(shù)框架:
canal:主要用途是基於MySQL 資料庫(kù)增量日誌解析,提供增量資料訂閱和消耗 RocketMQ:一款開源的分散式訊息系統(tǒng),基於高可用分散式叢集技術(shù),提供低延時(shí)的、高可靠的訊息發(fā)布與訂閱服務(wù)。
註:我用的aliyun的全家桶,MQ和mysql都是阿里雲(yún)的,如果是自建伺服器的可能有區(qū)別,我在後面盡量標(biāo)出
方案流程

#在寫入或修改收益表的同時(shí)透過(guò)canal監(jiān)控mysql收益表的binlog日誌. #canal檢測(cè)到變更,組裝變更的JSON封包,發(fā)送RocketMQ中事先定義好的TOPIC. 程式消費(fèi)該TOPIC,異質(zhì)收益日?qǐng)?bào)表.
canal設(shè)定部分
canal的安裝請(qǐng)參考官方文檔 解壓縮後可得到一個(gè)canal資料夾,包含三個(gè)目錄
bin:存放啟動(dòng)重啟腳本 #conf:存放核心設(shè)定檔
lib:存放核心jar套件
我們需要專注於conf資料夾裡的conf/canal.properties核心設(shè)定檔以及conf/example/instance.properties單一監(jiān)控節(jié)點(diǎn)設(shè)定檔
conf/canal.properties
## tcp, kafka, RocketMQ,這里默認(rèn)是tcp讀取模式,采用RocketMQ需要將其改變?yōu)镽ocketMQ模式 canal.serverMode = RocketMQ # 如果是aliyun的RocketMQ需要配置以下兩個(gè)KEY,ak/sk canal.aliyun.accessKey =xxxxxxx canal.aliyun.secretKey =xxxxxxx # 監(jiān)控的節(jié)點(diǎn)名稱.這個(gè)默認(rèn)就是example如果有多節(jié)點(diǎn)可以逗號(hào)隔開,如下方的例子 canal.destinations = example,sign # 如果是aliyun的RocketMQ需要修改canal.mq.accessChannel為cloud默認(rèn)為local canal.mq.accessChannel = cloud #MQ的地址,需要注意這里是不帶http://,但是需要帶端口號(hào) canal.mq.servers = #rocketmq實(shí)例id canal.mq.namespace =
##conf/example/instance. properties#mysql地址
canal.instance.master.address=
#以下兩個(gè)參數(shù)需要在開啟數(shù)據(jù)庫(kù)binlog日志后得到,在數(shù)據(jù)庫(kù)查詢界面輸入查詢語(yǔ)句`show master status`,canal.instance.master.journal.name對(duì)應(yīng)File參數(shù),canal.instance.master.position對(duì)應(yīng)Position參數(shù)
canal.instance.master.journal.name=
canal.instance.master.position=
#數(shù)據(jù)庫(kù)的賬號(hào)密碼
canal.instance.dbUsername=
canal.instance.dbPassword=
#需要監(jiān)控變動(dòng)的表
canal.instance.filter.regex=xxx.t_user_order,xxx.t_user_cash_out
#定義發(fā)送的mq生產(chǎn)組
canal.mq.producerGroup =
#定義發(fā)送到mq的指定主題
canal.mq.topic=
註:監(jiān)視器表的書寫規(guī)則格式參考監(jiān)控表書寫規(guī)則這時(shí)候會(huì)發(fā)現(xiàn)canal目錄中多了一個(gè)log檔,進(jìn)入可以看到canal主日誌檔和example節(jié)點(diǎn)啟動(dòng)日誌.
canal日志中出現(xiàn) the canal server is running now ...... example日志中出現(xiàn) init table filter : ^tablename xxxxxxxxx , the next step is binlog dump
表示你已經(jīng)成功了一大步,canal監(jiān)控已正常運(yùn)作.
RocketMQ部分
如果用的aliyun的RocketMQ,配置代碼部分直接可參考文檔 自建的RocketMQ也可參照簡(jiǎn)單的消費(fèi)例子監(jiān)控對(duì)應(yīng)的TOPIC即可 消費(fèi)Canal發(fā)來(lái)的數(shù)據(jù),格式如下:
{ "data":[ { //單個(gè)修改后表數(shù)據(jù),如果同一時(shí)間有多個(gè)表變動(dòng)會(huì)有多個(gè)該JSON對(duì)象 } ], "database":"監(jiān)控的表所在數(shù)據(jù)庫(kù)", "es":表變動(dòng)時(shí)間, "id":canal生成的id, "isDdl":Boolean類型,表示是否DDL語(yǔ)句, "mysqlType":{ 表結(jié)構(gòu) }, "old":如果是修改類型會(huì)填充修改前的值, "pkNames":[ 該表的主鍵,如"id" ], "sql":"執(zhí)行的SQL", "sqlType":{ 字段對(duì)應(yīng)的sqlType,一般使用mysqlType即可 }, "table":"監(jiān)控的表名", "ts":canal記錄發(fā)送時(shí)間, "type":"表的修改類型,入INSERT,UPDATE,DELETE" }
MQ消費(fèi)代碼主要用了反射,映射到對(duì)應(yīng)的表
//這里的body就是Canal發(fā)來(lái)的數(shù)據(jù) public Action process(String body) { boolean result = Boolean.FALSE; JSONObject data = JSONObject.parseObject(body); log.info("數(shù)據(jù)庫(kù)操作日志記錄:data:{}",data.toString()); Class c = null; try { //這里監(jiān)控了訂單和收益表分別做訂單統(tǒng)計(jì)和收益日?qǐng)?bào)統(tǒng)計(jì) c = Class.forName(getClassName(data.getString("table"))); } catch (ClassNotFoundException e) { log.error("error {}",e); } if (null != c) { JSONArray dataArray = data.getJSONArray("data"); if (dataArray != null) { //把獲取到的data部分轉(zhuǎn)換為反射后的實(shí)體集合 List list = dataArray.toJavaList(c); if (CollUtil.isNotEmpty(list)) { //對(duì)修改和寫入操作分別進(jìn)行邏輯操作 String type = data.getString("type"); if ("UPDATE".equals(type)) { result = uppHistory(list); } else if ("INSERT".equals(type)) { result = saveHistory(list); } } } } return result ? Action.CommitMessage : Action.ReconsumeLater; } /** * @description: 獲取反射ClassName * @author: chenyunxuan */ private String getClassName(String tableName) { StringBuilder sb = new StringBuilder(); //判斷是哪張表的數(shù)據(jù) if (tableName.equals("t_user_income_detail")) { sb.append("cn.mc.core.model.order"); } else if (tableName.equals("t_user_cash_out")) { sb.append("cn.mc.sync.model"); } String className = StrUtil.toCamelCase(tableName).substring(1); return sb.append(".").append(className).toString(); } /** * @description: 寫入對(duì)應(yīng)類型的統(tǒng)計(jì)表 * @author: chenyunxuan */ private <T> Boolean saveHistory(List<T> orderList) { boolean result = Boolean.FALSE; Object dataType = orderList.get(0); //用instanceof判斷類型進(jìn)入不同的邏輯處理代碼 if (dataType instanceof TUserIncomeDetail) { result = userOrderHistoryService.saveIncomeDaily(orderList); } else if (dataType instanceof UserCashOut) { result = userCashOutHistoryService.delSaveHistoryList(orderList); } return result; }
saveIncomeDaily偽代碼
public synchronized Boolean saveIncomeDaily(List orderList) { //循環(huán)收益明細(xì)記錄 ....... //通過(guò)創(chuàng)建時(shí)間和用戶id查詢收益日?qǐng)?bào)表中是否有當(dāng)日數(shù)據(jù) if(不存在當(dāng)日數(shù)據(jù)){ //創(chuàng)建當(dāng)日的收益日?qǐng)?bào)表記錄 ..... } //因?yàn)椴淮嬖诋?dāng)日記錄也會(huì)立即寫入當(dāng)日的空數(shù)據(jù),所以下面的流程都是走更新流程 //更新當(dāng)日數(shù)據(jù) ....... return Boolean.TRUE; }
注:代碼中應(yīng)該多打一些日志,方便產(chǎn)生異常收益數(shù)據(jù)后的校對(duì)
后記
至此一個(gè)基于canal
+RocketMQ
的收益日?qǐng)?bào)統(tǒng)計(jì)異構(gòu)方案就完成了,下一篇會(huì)圍繞本文提到的第二個(gè)問(wèn)題減少聚合SQL的產(chǎn)生展開.敬請(qǐng)關(guān)注.
相關(guān)免費(fèi)學(xué)習(xí)推薦:java基礎(chǔ)教程
以上是便捷的統(tǒng)計(jì)訂單收益(一)的詳細(xì)內(nèi)容。更多資訊請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6
視覺(jué)化網(wǎng)頁(yè)開發(fā)工具

SublimeText3 Mac版
神級(jí)程式碼編輯軟體(SublimeText3)