日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

移动建模平台元数据存储架构演进

發(fā)布時間:2023/12/4 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 移动建模平台元数据存储架构演进 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

源寶導(dǎo)讀:明源云天際-移動建模平臺是一個快速生成多端移動應(yīng)用的PaaS平臺,元數(shù)據(jù)是移動應(yīng)用設(shè)計與運行的核心數(shù)據(jù)結(jié)構(gòu),本文將從元數(shù)據(jù)存儲這個視角分享我們的技術(shù)思考與實踐。

一、什么是元數(shù)據(jù)(Metadata)?

? ? 這個問題要先從移動建模平臺的定位說起。移動建模平臺是一個高效的應(yīng)用搭建、管理平臺,用戶可以通過拖拉拽的方式,自定義快速生成多端移動應(yīng)用的PaaS平臺。

? ? 目前主流的移動應(yīng)用開發(fā)大都是基于H5為主的前端技術(shù),元數(shù)據(jù)是對移動應(yīng)用內(nèi)部結(jié)構(gòu)的一種數(shù)據(jù)抽象,用于描述應(yīng)用所使用的組件和配置,是整個移動應(yīng)用設(shè)計階段和運行階段的核心數(shù)據(jù),也是移動建模平臺生成的重要產(chǎn)物之一。本文主要從元數(shù)據(jù)這個視角去討論移動建模平臺在元數(shù)據(jù)存儲方面的一些實踐。

? ? 如果把移動建模平臺比作一個汽車生產(chǎn)線的話,那么移動應(yīng)用就好比這條生產(chǎn)線生產(chǎn)的汽車,元數(shù)據(jù)就好比汽車的配置,消費者可以基于汽車的原廠配置進行個性化改裝,也就有了個性化元數(shù)據(jù),改裝完成最終驗車上牌也就有了運行時元數(shù)據(jù)。

? ? 設(shè)計階段通過一個Web版的在線設(shè)計器,設(shè)計器初始化會加載元數(shù)據(jù)進行頁面渲染,元數(shù)據(jù)數(shù)據(jù)結(jié)構(gòu)如下:

? ? 設(shè)計器加載完成后可以通過設(shè)計器進行應(yīng)用的設(shè)計和頁面配置,保存設(shè)計就會產(chǎn)生新的元數(shù)據(jù):

二、元數(shù)據(jù)存儲架構(gòu)演進過程概覽

? ? 移動建模平臺元數(shù)據(jù)存儲的演進過程大致可以分為三個階段:

2.1、單體應(yīng)用階段

? ? 這個階段元數(shù)據(jù)表和其他業(yè)務(wù)數(shù)據(jù)表在同一個數(shù)據(jù)庫中,按照上圖的邏輯結(jié)構(gòu)主要分成四張表來存儲:

? ? 在項目初期數(shù)據(jù)量并不大,這種結(jié)構(gòu)也是最容易實現(xiàn)和最容易想到的。但隨著業(yè)務(wù)的發(fā)展,各種組件越來越豐富,單個應(yīng)用的元數(shù)據(jù)也由最初的普遍幾十KB發(fā)展到幾M,同時伴隨著頁面增多,頁面之間的拷貝、復(fù)制、更新等操作也變得越來越緩慢。

? ? 從上圖的表結(jié)構(gòu)可以看出,metadata字段是使用字符串來存儲json的,并且設(shè)計時和運行時元數(shù)據(jù)存儲在同一張表中。很快這種設(shè)計方案的弊端就顯現(xiàn)出來,主要有幾方面問題:

  • 元數(shù)據(jù)可能會有幾M,對元數(shù)據(jù)的每次讀寫操作都需要對元數(shù)據(jù)進行序列化和反序列化,網(wǎng)絡(luò)IO和內(nèi)存消耗大,程序執(zhí)行時間過長

  • 即使要修改元數(shù)據(jù)中很小的一部分內(nèi)容也必須將元數(shù)據(jù)全部取出,修改后再序列化為字符串存入數(shù)據(jù)庫。由于元數(shù)據(jù)的特殊性,緩存方案也無法使用

  • 頁面、文件夾數(shù)量比較多時對頁面的復(fù)制、刪除等操作需要涉及到多表事務(wù),事務(wù)執(zhí)行效率低。一個租戶升級操作可能需要十幾分鐘。

  • 當(dāng)PHP按照數(shù)組方式來處理后導(dǎo)致空對象和空數(shù)組轉(zhuǎn)換問題,會導(dǎo)致元數(shù)據(jù)損壞無法還原,前端頁面渲染出錯

  • 由于涉及到多張表的操作,多表查詢會讓業(yè)務(wù)邏輯變得極其復(fù)雜,程序很難維護

  • 2.2、服務(wù)拆分階段

    針對以上出現(xiàn)的一些問題,開始采取一些局部的優(yōu)化手段,主要有幾下幾方面:

  • 采用服務(wù)化的方式將原有的元數(shù)據(jù)操作相關(guān)的邏輯從單體應(yīng)用中剝離出來,有了元數(shù)據(jù)服務(wù)。

  • 數(shù)據(jù)表結(jié)構(gòu)增加了一些冗余字段,并針對索引進行了相應(yīng)調(diào)整,提高了查詢性能。

  • 在寫入操作比較多的地方將以前的單條insert改為了一次性多條insert插入,優(yōu)化寫入性能。

  • 對元數(shù)據(jù)的結(jié)構(gòu)進行優(yōu)化,精簡冗余部分,減小元數(shù)據(jù)的體積。

  • PHP操作元數(shù)據(jù)禁止使用數(shù)組方式來處理,統(tǒng)一轉(zhuǎn)為對象。

  • ? ? 這個階段采用了以上一些優(yōu)化方法,雖然性能得到一些改善,但是都沒有從根本上解決問題,根本問題出在存儲層,團隊也有討論過使用NOSQL,比如MongoDB,但是由于元數(shù)據(jù)和其他模塊嚴重耦合,數(shù)據(jù)層的拆分難度很大。加之如果改為MongoDB,新的數(shù)據(jù)模型如何設(shè)計,舊的數(shù)據(jù)如何遷移等問題還沒最佳實踐。所以這個階段的一些改進僅限于應(yīng)用層的拆分,不過對于后續(xù)重構(gòu)提供了參考。

    2.3、微服務(wù)化階段

    ? ? 這個階段也是移動PaaS2.0階段,在2.0中元數(shù)據(jù)相關(guān)的能力完全抽離出來成為單獨的服務(wù),并且使用golang進行重構(gòu),數(shù)據(jù)庫也獨立出來,使用MongoDB進行重新建模設(shè)計。為什么要選用MongoDB來作為數(shù)據(jù)庫存儲,主要基于以下幾個方面:

  • 元數(shù)據(jù)本來就是json結(jié)構(gòu),而MongoDB的使用BSON作為數(shù)據(jù)交換格式,以文檔方式組織數(shù)據(jù),非常符合元數(shù)據(jù)的結(jié)構(gòu)特點。

  • MongoDB4.0之后同樣支持事務(wù)操作,在一些需要事務(wù)的場景下依然能夠保證數(shù)據(jù)的一致性。

  • 通過性能對比,MongoDB在讀寫性能上有明顯優(yōu)勢。

  • JSON 格式存儲最接近真實對象模型,對開發(fā)者友好,方便快速開發(fā)迭代。對于測試人員來說,可以直觀的看到元數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),對測試更加友好。

  • 能夠極大的簡化目前的應(yīng)用層開發(fā),減少大量的多表查詢操作。

  • 可以按需修改元數(shù)據(jù)文檔的某個節(jié)點,而不需要讀取整個元數(shù)據(jù)文檔。

  • 高可用復(fù)制集滿足數(shù)據(jù)高可靠、服務(wù)高可用的需求,運維簡單,故障自動切換。

  • 可擴展分片集群,面對未來海量元數(shù)據(jù)存儲,可以很方便的支持水平擴展。

  • 強大的aggregation & mapreduce,可以將復(fù)雜的查詢分解為一個個小的步驟。

  • ? ? 下圖是在4核8G的同一臺虛擬機上做的一個MySQL和MongoDB的性能對比測試,可以看出隨著插入元數(shù)據(jù)的數(shù)量增加,MySQL和MongoDB所花費的時間的差距也越來越大。

    ? ? 使用MongoDB重新設(shè)計后的元數(shù)據(jù)結(jié)構(gòu):

    {"_id": ObjectId("5f3de7507cda70000e433ca2"),"workspaceId": "26043287605354496","common": {"style": {"globalBgColor": "#FFFFFF","primaryColor": "#FF543D","secondaryColor": "#FF6954"},"body": {"header": {"hide": false}}},"configs": [{"_id": ObjectId("5f3de7507cda70000e433ca3"),"type": "role","name": "游客","alias": "default","isGuest": true,"remark": "用戶未登錄時所使用...","position": 1.59789243277115e+18,"viewIds": [ObjectId("5f3e45c62ef1d50013b3303e")],"metadata": {"tabs": {"items": [{"isDefault": true,"text": "123","activeIcon": "appicon-house","href": {"name": "bde68663-6f93-2206-0b29-cf910711f71e"},"icon": "appicon-house","iconClass": "appicon"}]}}},{"_id": ObjectId("5f3de7507cda70000e433ca4"),"type": "role","name": "已登錄用戶","alias": "default-login","isGuest": false,"remark": "用戶登錄時所使用...","position": 1.59789243277116e+18,"viewIds": [ ],"metadata": { }},{"_id": ObjectId("5f470ced59221f0014d2a144"),"type": "page","ancestors": [ObjectId("5f3de7507cda70000e433ca4")],"name": "login","routeName": "ef214890-b3e6-9a24-9dd8-80d12343f76c","routePath": "/ef214890-b3e6-9a24-9dd8-80d12343f76c","remark": "","design": { },"metadata": {"name": "ef214890-b3e6-9a24-9dd8-80d12343f76c","path": "/ef214890-b3e6-9a24-9dd8-80d12343f76c","body": {"header": {"title": "login","items": [ ]},"content": {"items": [ ]}}},"position": 1.59849188522867e+18,"viewIds": [ ]}],"createdAt": ISODate("2020-08-20T03:00:32Z"),"updatedAt": ISODate("2020-08-20T03:00:32Z") }

    ? ? 從新的結(jié)構(gòu)可以看出之前的元數(shù)據(jù)中的配置變成了一個內(nèi)嵌數(shù)組configs,configs下包含了角色配置、文件夾、頁面。三者之間的關(guān)系由以前的層次關(guān)系被打平后變成了并列關(guān)系。那么如何實現(xiàn)他們之前的那種上下級關(guān)系呢?仔細看就能發(fā)現(xiàn)configs中的每一個對象里都有一個ancestors字段,這個字段用于記錄祖先節(jié)點,也就是通過這個節(jié)點就可以輕松找到當(dāng)前項有幾個上級,只需要增加一個索引字段就可以高效的得到一個樹狀結(jié)構(gòu)。根據(jù)ancestors創(chuàng)建索引:

    xxxxxxxxxx db.metadata.createIndex({"configs.ancestors": 1 })

    ? ??如圖所示,在1.0中,如果想要按照箭頭所指的方向移動往往需要配合數(shù)據(jù)庫中的

    ? ? 這兩個字段,更新這兩個字段來標(biāo)注頁面的位置。

    ? ? 在新的數(shù)據(jù)庫當(dāng)中,由于頁面、文件夾、配置是平等關(guān)系,所以只需要一個 "position": 1.59849188522867e+18字段來記錄就行了,當(dāng)需要移動上下頁面時候只需要取相鄰兩個元素的position的平均值,最后結(jié)果按照position來排序,性能得到很大提升。

    ? ? 通過前后數(shù)據(jù)結(jié)構(gòu)的對比,可以很明顯發(fā)現(xiàn),在使用MySQL存儲時,為了要保證元數(shù)據(jù)節(jié)點之間的關(guān)系,往往需要設(shè)計多張表,而使用MongoDB后,只要一個集合就能搞定設(shè)計時元數(shù)據(jù)的存儲,這樣帶來的直接好處就是性能提升和應(yīng)用程序開發(fā)的簡化。

    ? ? 元數(shù)據(jù)服務(wù)端使用了golang代替之前的php,其實也是為了方便元數(shù)據(jù)的操作和提升性能,由于配置、文件夾和頁面的差異被抹平,三者被統(tǒng)一抽象為配置,于是就很方便的提供統(tǒng)一的元數(shù)據(jù)操作API,golang結(jié)構(gòu)體可以完美的將元數(shù)據(jù)的結(jié)構(gòu)映射到MongoDB的文檔模型中,開發(fā)者可以清楚的看到數(shù)據(jù)庫中元數(shù)據(jù)結(jié)構(gòu)和代碼中是完全一致的,這對新人理解元數(shù)據(jù)結(jié)構(gòu)會有很大幫助。

    //元數(shù)據(jù)結(jié)構(gòu)體 type Metadata struct {Model `bson:"-"`Id bson.ObjectId `bson:"_id" json:"id"`WorkspaceId string `bson:"workspaceId" comment:"工作區(qū)ID"`Common bson.M `bson:"common" comment:"公共配置"`Configs []Config `bson:"configs" comment:"配置信息"`IsPublished bool `bson:"isPublished" comment:"是否發(fā)布"`CreatedAt time.Time `bson:"createdAt"`UpdatedAt time.Time `bson:"updatedAt"` }type Config interface {Add(metadataId string, data interface{}) errorEdit(metadataId, configId string, data interface{}) errorDelete(metadataId, configId string) errorGetType() string }

    ? ? 解決了存儲問題后,需要返回樹狀結(jié)構(gòu)給前端,這就需要應(yīng)用端重新組裝數(shù)據(jù)。

    type PageListResponse []TreeNode//統(tǒng)一定義菜單樹的數(shù)據(jù)結(jié)構(gòu) type TreeNode struct {Id string `json:"id"` //節(jié)點idParentId string `json:"-"` //父idType string `json:"type"` //類型Name string `json:"name"` //節(jié)點名字RouteName string `json:"routeName,omitempty"` //標(biāo)識RoutePath string `json:"routePath,omitempty"` //路徑Leaf bool `json:"leaf,omitempty"` //葉子節(jié)點IsGuest bool `json:"isGuest,omitempty"` //是否是游客配置IsLogin bool `json:"isLogin,omitempty"` //是否是登錄頁面Ancestors []string `json:"ancestors,omitempty"` //祖先節(jié)點Remark string `json:"remark,omitempty"` //備注Position string `json:"position"` //位置Design map[string]interface{} `json:"design,omitempty"` //組件屬性Metadata map[string]interface{} `json:"metadata,omitempty"` //元數(shù)據(jù)Children []TreeNode `json:"children,omitempty"` //子節(jié)點 }// GenerateTree 自定義的結(jié)構(gòu)體實現(xiàn) TreeNode 接口后調(diào)用此方法生成樹結(jié)構(gòu) // nodes 需要生成樹的節(jié)點 func GenerateTree(nodes []TreeNode) (trees []TreeNode) {trees = []TreeNode{}// 定義頂層根和子節(jié)點var roots, childs []TreeNodefor _, v := range nodes {if len(v.ParentId) <= 0 {// 判斷頂層根節(jié)點roots = append(roots, v)}childs = append(childs, v)}for _, v := range roots {childTree := &v// 遞歸recursiveTree(childTree, childs)// 遞歸之后,根據(jù)子節(jié)確認是否是葉子節(jié)點childTree.Leaf = (len(childTree.Children) == 0)trees = append(trees, *childTree)}return }// recursiveTree 遞歸生成樹結(jié)構(gòu) // tree 遞歸的樹對象 // nodes 遞歸的節(jié)點 func recursiveTree(tree *TreeNode, nodes []TreeNode) {for _, v := range nodes {if len(v.ParentId) <= 0 {// 如果當(dāng)前節(jié)點是頂層根節(jié)點就跳過continue}if tree.Id == v.ParentId {childTree := &vrecursiveTree(childTree, nodes)// 遞歸之后,根據(jù)子節(jié)確認是否是葉子節(jié)點childTree.Leaf = (len(childTree.Children) == 0)tree.Children = append(tree.Children, *childTree)}} }

    ? ? 這個階段golang結(jié)構(gòu)體處理json的便利性凸顯出來,omitempty可以將空的節(jié)點數(shù)據(jù)忽略掉,這就有效的降低了元數(shù)據(jù)的體積,降低了網(wǎng)絡(luò)I/O。

    ? ? 設(shè)計時的元數(shù)據(jù)存儲性能和邏輯復(fù)雜的問題解決了,剩下的就是運行時元數(shù)據(jù)的問題了。元數(shù)據(jù)在運行時階段其實是不會變動的,在1.0當(dāng)中,移動應(yīng)用在運行時需要動態(tài)請求元數(shù)據(jù)的服務(wù),從元數(shù)據(jù)服務(wù)接口中拉取運行時元數(shù)據(jù)來渲染頁面,顯然如果訪問量大后元數(shù)據(jù)服務(wù)會成為性能的瓶頸。針對這個問題結(jié)合元數(shù)據(jù)的業(yè)務(wù)特點,最終運行時元數(shù)據(jù)就采用靜態(tài)json文件的方式存儲在OSS上,不僅消除了后端服務(wù)訪問壓力問題,同時也提高了運行時元數(shù)據(jù)加載的穩(wěn)定性。最終生成的路徑其實訪問的是一個真實存在的json問題。

    xxxxxxxxxx https://xxxxxx.com/_assets/mobile_three/demo/exp/1.0.12/meta/default.json

    三、總結(jié)

    ? ? 好了,以上就是本次分享的移動建模平臺元數(shù)據(jù)存儲的演進過程,當(dāng)然實際演進過程遠比本次講述的要復(fù)雜得多,分享的內(nèi)容也是挑選幾個比較重要的場景展開,后續(xù)可以分享一些MongoDB設(shè)計模式方面的內(nèi)容,總結(jié)一下從開發(fā)選型角度大致有以下幾點實踐經(jīng)驗:

  • 使用MySQL和MongoDB同時進行數(shù)據(jù)建模,對比兩者之間的優(yōu)劣,在表關(guān)系比較復(fù)雜時可能涉及到多表關(guān)聯(lián)查詢較多的場景下利用MongoDB內(nèi)嵌文檔、內(nèi)嵌數(shù)組等靈活的文檔數(shù)據(jù)結(jié)構(gòu)往往能設(shè)計出結(jié)構(gòu)更清晰、性能更好的存儲方案。

  • 小心MongoDB單個文檔16M的存儲限制,對于那種可能無限增長的數(shù)據(jù)不適合直接使用內(nèi)嵌方式存儲,可改為內(nèi)嵌引用方式。

  • 盡量不要使用ORM框架來操作MongoDB,往往會誤把MongoDB當(dāng)成MySQL來使用,同時不能很好的使用MongoDB強大的API。

  • Golang和MongoDB的結(jié)合能在提升性能的同時,帶來開發(fā)上的便利。

  • MongoDB 4.0以后已經(jīng)支持多文檔事務(wù),擴展了MongoDB的使用場景,越來越多的場景其實是可以使用MongoDB代替MySQL。如果沒有特別的必要和限制,采用MongoDB往往會給程序設(shè)計帶來更大的靈活性,提高數(shù)據(jù)庫開發(fā)效率,更好的滿足快速迭代開發(fā)的需求。

  • MongoDB不能簡單理解為一個json文檔存儲所有數(shù)據(jù),同時要結(jié)合具體的業(yè)務(wù)場景考慮讀寫操作是否方便來設(shè)計文檔模型。

  • ------ END ------

    作者簡介

    段同學(xué):?研發(fā)工程師,目前負責(zé)天際-移動平臺產(chǎn)品的研發(fā)工作。

    也許您還想看

    基于 Go 的微服務(wù)運行情況監(jiān)控實踐

    在明源云客,一個全新的服務(wù)會經(jīng)歷什么?

    云客后臺優(yōu)化的“前世今生”(一)

    云客后臺優(yōu)化的“前世今生”(二)

    回歸統(tǒng)計在DMP中的實戰(zhàn)應(yīng)用

    總結(jié)

    以上是生活随笔為你收集整理的移动建模平台元数据存储架构演进的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。