一則小故事-和時間一起做MongoDB的朋友
- 2020 年 3 月 4 日
- 筆記
有關 MongoDB 是什麼,MongoDB 如何用,如何發揮最大優勢的相關問題,歡迎大家交流探討。
和時間一起做 MongoDB 的朋友
我是在 2010 年的一期程式設計師雜誌上開始接觸 MongoDB 資料庫和 Nosql 的概念,當時感覺很新奇,並不明白具體的用途和優勢,直到 2013 年才有機會真正的使用和了解。
初識 MongoDB
當時的環境是 Windows 平台與 C#,在一個基於內容的網站首頁功能開發中,最初希望能夠提高列表的響應速度,這樣一個契機,有機會把 MongoDB 應用到具體項目中。一句話概括
「聽說 MongoDB 快,所以開始用 MongoDB 做資料庫,用小功能做驗證。
整個業務的小部分功能數據存儲使用 MongoDB,其餘大部分功能數據存儲使用 SqlServer。從 MongoDB 里取列表數據,可以理解為數據的預載入,也可以理解為數據的快取。
除了速度快之外,另外一個感受是 MongoDB 的日誌量很大。雲計算公有平台的概念逐漸提出,RDS 等雲資料庫就是當時那個階段的產品概念,記得當時我想在雲平台上找一款 MongoDB 雲資料庫,找不到。現在在各個平台的雲產品中,MongoDB 應該是標配了。
進階使用
物聯網領域
隨後同樣是工作的機會,我把 MongoDB 的使用擴展到了物聯網領域,用於存儲不同產品的差異性屬性,屬性無法統一,還存在著需求的反覆變化,MongoDB 正好有寬表的概念和集合按需存儲的提倡。

圖1-智趣健康feed
圖 1 是一款智慧硬體 APP 的效果圖,簡單的展開來講,基於智慧硬體的產品連接硬體,用戶以及業務系統,涉及到硬體資訊(即源數據收集),使用者(用戶資訊),業務處理(社交聚合,邊緣計算處理,日統計,地理位置)等。

圖2-產品功能模型
圖 2 是產品的一些功能對象屬性
我們可以看到,隨著產品的側重點,業務發展階段不同,業務對象的屬性是多變和不確定的。
這種場景正是 MongoDB 的嵌套模型和模式自由的用武之地。對於社交 APP 的 Feed 流查詢,時序數據的採集和統計都可以友好的支援。
在可控的範圍之內,這裡的可控是說 MongoDB 本身的存儲規則,例如單文檔最大存儲限制。
使用者在組織產品功能,開發實現業務系統時,不需要在數據集合的修改和維護上花費太多功夫。
總結下來是以下幾點:
1 程式可以自行創建集合,不需要在程式執行前預處理。
2 集合中數據的欄位數目不需要保持統一,並且被提倡為按需存儲。
3 關聯關係藉助於嵌套包含模型單集合存儲,查詢友好,提高程式性能,降低聯合查詢複雜度。
靈活的數組模型
一個集合中的嵌套,層級,關聯使用,免不了提到數組。這裡想重點說一下數組模型,在我看來 MongoDB 的數組模型可以 廣泛的應用在基於父子結構,組織員工分組等經典的 1 對多業務領域中。
以下是員工分組的一對多數據模型案例
用於企業員工組織架構和工作組的分配管理,包含組資訊和員工資訊兩部分,員工資訊是一個數組集合
Data Model
"createTime": ISODate("2017-08-23T17:15:56.173+08:00"), "groudId": 10,工作組編號 "users": [ 員工數據節點 { "name": "wangmm", "uid": 1240, "email": "[email protected]", "status": 1, "edit": ISODate("2017-08-16T00:00:18.685+08:00"), "scope": 1, "desc": "服務人員" }, { "name": "lmd", "uid": 1241, "email": "[email protected]", "status": 1, "edit": ISODate("2017-08-16T00:00:18.685+08:00"), "scope": 2, "desc": "管理員" }, ]
我把這種設計模型進行了抽象,適合於總和模式,包含模式等多種業務場景。
除了上文提到的,還可以想到的有
1 每個商圈下的店鋪資訊集合
2 每個倉庫關聯的攝影機監控硬體設備集合 …
基於數組模型,可以做如下幾個典型的操作
「使用 和pull 追加,刪除數組元素
使用$push 操作符將子元素追加到集合元素末尾,也就是 1:N 的 N。
使用$pull 移除單個子元素
以下是一段參考程式碼:
$where = [ 'groupId' => 20 ]; $result = static::getCollection()->update( $where, ['$pull' => ['user' => ['uid' => 309]]], ['multi' => true] ); return $result > 0;
「使用$unwind 聚合分離數組元素
如果按照組員 Id 查詢 如下
db.getCollection('collectionname').find({'user.uid':519})
返回的結果是整個集合,會包含整個數組,我們可以使用 unwind 操作符解決這個問題,進行精確輸出。
$unwind 實現對 1:N 存儲的集合實現 1:1 的輸出,這樣就可以做分頁列表,條件查詢了。避免了複雜的連接查詢和不必須的冗餘輸出,總是好的。
以下是一段參考程式碼:
$ops = [ [ '$unwind' => '$user', ], [ '$project' => [ 'user' => 1, 'groupId' => 1, 'name' => 1, '_id' => 0, 'uid' => 1, ], ], [ '$match' => [ 'user.uid' => $userId, ], ], ]; $collection = self::_aggregateInstance(); return $data = $collection->aggregate($ops);
凌厲的數據聚合
基於基礎業務數據的沉澱和收集,我們可以做一些統計分析,運營支援相關的數據操作,MongoDB 中的聚合就是強有力的工具助手。
聚合(Aggregation)提供分組和統計文檔的功能。算是 MongoDB 中的進階使用。關於聚合,網路上還有一些資料,說通過 key reduce 函數實現,這種方式已經被放棄了。官方推薦採用管道實現聚合。
db.collection.aggregate(pipeline, options)
業務抽象場景如下:
「某個商店按時間維度對訂單進行統計,排序等管理
Data Model
{ "_id" : ObjectId("5a6d08f38919090e847b659b"), "obtain_time" : ISODate("2018-01-25T16:00:00.000Z"), "datepark" : "201801261618", "create_time" : ISODate("2018-01-27T23:19:15.000Z"), "epay_count" : 15, "pay_date" : ISODate("2018-01-25T16:00:00.000Z"), "pre_week_e_count" : 30, "state" : 2, "epay_week_rate" : 50.0, "storeid" : 1618, "storename" : "解放路SOHO店", "address" : "解放路66號", "city_id" : 579 }
原始數據是一天一條數據,業務要求按照店鋪做聚合,統計每周,每月的銷售訂單。
實現方式,式例方法如下
基於日統計的原始數據,聚合欄位進行 sum 等操作,group 參數如下
/* * 根據店鋪編號聚合訂單數 * * */ public function aggregateStoreEpay($startTime,$endTime,$storeId) { $mongo = Mongodb::getInstance(); $ops = [ [ '$match' => [ 'state' => ['$eq' => 2], 'storeid'=>['$eq'=>$storeId], 'date_time' => ['$gte' => $conditions["start_time"], '$lte' => $conditions["end_time"]] ] ], [ '$group' => [ '_id' => ['storeid' => '$storeid'], 'sum' => ['$sum' => 1], 'date_time' => ['$push' => '$pay_date'] ] ], [ '$sort' => [ 'pay_date' => -1 ] ], [ '$limit' => $limit ] ]; $collection = $mongo->dbname->epay_stat; $data = $collection->aggregate($ops); return $data['result']; }
同系統多資料庫產生的數據同步問題
在一個技術團隊中,當技術決策者決定使用 MongoDB 時,除非是全新的項目,不然大多數屬於探索性使用,按功能模組一步一步的遷移調整。即使是全新項目,基礎的行業數據,核心業務數據,也難免不和關係型資料庫做交互。
對於業務來說,用戶需要一個完整的業務場景,而數據會被分散到 MongoDB 和 Mysql 或者(SqlServer 中),也就是 Sql 和 Nosql 共存。
這種情況會出現數據相關問題,我們思考下邊的場景:
「查詢展示列表頁面,數據源分散在不同的資料庫
數據源不同,數據的展示涉及到組裝和整合。數據展示時數據源從哪裡取,是使用時從不同的庫同步取還是提前把數據存儲到一個統一的數據源,從一處取?
前者有查詢的數據性能問題,後者有數據同步的維護延遲問題,如何選擇?
在以往的使用過程中,我也遇到過類似的問題,得出的結論是,在開發初期做好規劃,整塊的數據盡量放到一處,也就是說不要把業務分的太散。
如果已經遇到類似場景,數據源不一致,暴露性能問題是遲早的事,前期將數據同步的延遲控制在業務方可以忍受的範圍內,得業務成熟後,最好能夠逐步統一到 MongoDB 平台,當然這樣研發成本和時間的花銷是不可避免的。
小有心得
借用之前收藏的一張圖,做一個簡短的總結。

圖3 MongoDB 應用場景
優勢梳理
在我看來,對於互聯網業務系統,特別是靠近用戶側的前端應用系統,MongoDB 豐富的數據結構,可以輕鬆應對多變的需求和複雜的使用場景。
為什麼是靠近用戶側,靠近用戶側代表著靈活和多變,特別是近兩年中台設計的提出,本質上也是在降低協作和開發成本,推進應用落地的靈活性,為業務賦能。
模式自由的特性並不代表集合無設計,相比較關係型資料庫設計,根據現實事物的業務屬性映射生成集合的基本欄位,基本的設計原則還是要遵循。
集合結構修改調整不需要 DBA 著重參與,減少溝通成本,加快版本更新迭代速率,DBA 們可以把精力投入到資料庫運維層面架構設計上,複製集的健壯性,索引優化,數據備份,故障預警等其它方面。
在問題中成長
學習 MongoDb 資料庫的基本姿勢,邊學習,邊實踐,邊參考,邊改進,在問題中成長。
在業務的推動下,對一項技術的學習,力求能把技術的優勢盡量的發揮出來,本身就是一個磨合,演化,學習的過程。
如果大家關注 MongoDB 官方的一些產品動態,可以發現 MongoDB 自身也在不斷的擴展 數據處理,事務支援,雲原生等方面的產品形態,版本在不斷的升級。相較於個人的成長也是這樣的。
更多的使用場景,需要一起探索和實踐。