ERP之痛
曾幾何時,我混跡於電商、珠寶行業4年多,為這兩個行業開發過兩套大型業務系統(ERP)。作為一個ERP系統,系統主要功能模組無非是訂單管理、商品管理、生產採購、倉庫管理、物流管理、財務管理等等。作為一個管理系統,大家的一般開發習慣就是使用.Net或Java技術,建立一個單塊(單程式)架構的應用,只有一個SQLServer或MySql資料庫。然後在專案檔案中分一下各個模組,三層結構方式組織程式碼編寫開發。最後測試,交付上線。
起初,因為資料量不大,系統效能還不錯,各種列表查詢,報表查詢,Excel資料匯出功能等用的都很流暢。但是隨著公司業務發展,訂單量日積月累,後期各種業務部門的報表查詢、資料匯出需求不斷增多,我們漸漸就感覺系統執行越來越慢。於是我們可能最先想到的解決方案就是,優化系統瓶頸資料庫這個大頭。我們可能的一種嘗試就是將資料庫單獨放置到一個伺服器,實現資料庫和應用程式分離,或者是建立各種資料庫表索引,優化程式程式碼等方法。經過這樣一番研究優化,系統某些功能可能效能的確大大提高,但是我們還是發現某些功能列表的資料查詢匯出依然很慢,或者隨著資料量繼續積累,原來較快的列表匯出功能,也愈來愈變得緩慢了。我們用盡各種辦法,最後也達不到理想的系統效能速度。
為了提高系統效能,我們也許會主動學習一些網際網路公司的技術經驗,什麼高併發、高效能、大資料、讀寫分離等方案,發現自己根本無從下手。我們會覺得因為系統業務特點不一樣。ERP系統併發量不高,主要是業務複雜,各種業務耦合度遠高於那些網際網路應用,不好做拆分,資料查詢邏輯要遠比網際網路系統複雜,一個列表頁查詢出來的資料,往往需要關聯4、5張表才能得到結果。有些報表類的甚至更多。加上各種業務操作事務性、資料一致性要求很高,很多時候導致我們措不及手,無法進一步優化系統。
曾幾何時,我也被這樣或那樣的理由所挫敗,認為ERP系統非常特殊,無藥可救,可是後來。。。
我現在已經不這麼認為了,似乎有了新的解決方案O(∩_∩)O哈哈~
曙光乍現
在敘述具體方案前,先說下自己的想法。我首先覺得我們做ERP系統前,就得有當今網際網路思維。我們不要再去做一個大一統的系統了。我們要分拆一個大系統,做成一個個小系統。然後通過系統介面讓這些小系統相互通訊。這樣來組成一個大系統,具體來說就是“分散式”、“服務化”的網際網路思維。讓系統在架構設計上就是一個先天支援高度可擴充套件的系統。
怎麼做呢?具體來說就是要將訂單管理、商品管理、生產採購、倉庫管理、物流管理、財務管理拆分成一個個子系統。這些子系統可以單獨設計開發,對外暴露出各種其他子系統需求的資料介面即可。每個子系統都有單獨的資料庫。甚至這些子系統可以交由不同的團隊去開發和維護,使用不同的技術體系,使用不同的資料庫。而不是再像以前那樣,都整合在同一個大而全的系統中,一個大而全的資料庫。
對於新架構的系統他有什麼優點呢?
首先,也是最重要的就是解決系統的效能問題。以往資料庫例項只有一個,沒法擴充套件出多個例項,以便在效能受限的情況下依靠增加資料庫例項來達到負載均衡。也許有人會說可以使用讀寫分離方案,但是因為ERP系統的特點,這個方案很多時候不現實。比如說操作庫存的時候,你不能從讀庫裡讀庫存,然後在寫庫裡寫入庫存。因為主從複製會有時效性,寫入的庫存並不能馬上寫入從庫。這樣的場景在ERP中也有多處。何況寫庫不能擴充套件,只能有一個。而新設計方案是寫庫是分離的,每個子系統有自己的資料庫。
其次,就是更新非常方便,各個子系統以後臺微服務的方式存在。前臺一個單獨的web專案,這個web專案呼叫後臺這些子系統的服務介面。這樣的設計,在某個業務子系統需要更新的時候,可以單獨更新。不用像以前那種單程式架構時,一個小更新需要整個系統重啟,導致使用者會話也丟失,使用者需要新登入。而現在的這種設計就不會有這個問題。
系統整體設計
系統物理部署檢視
詳細設計
拆分應用層
拆分應用層,是踐行“微服務”架構的理念。將原來大而全的單程式架構按照業務模組拆分成可獨立部署的應用程式,以此來達到平滑系統更新、升級、方便負載擴充套件的目的。具體來說,技術上可以使用restfull風格的介面,也可以使用像java中dubbo框架方式來簡化開發複雜度。ERPWeb端或其他移動端也是一個單獨的應用充當表現層。非常薄,只是簡單的接受引數,調取後臺其他各種微服務程式的介面獲取所需展示的資料。微服務充當業務邏輯層,每個微服務都是可獨立部署上線的程式,對外提供資料訪問介面。
微服務可以使用流行的各種RPC框架,比如dubbo,可以支援多種呼叫協議Http、TCP等,這些框架使得編碼比較容易,框架封裝底層資料通訊細節,使得客戶端執行遠端方法如同執行本地方法一樣簡單。
dubbo微服務架構,還支援服務治理,負載均衡等功能。這樣不僅可以提高系統的可用性,還能動態提升系統應用層的效能。比如倉庫管理中入庫業務非常繁忙,佔用非常多的CPU和記憶體資源,我們可以另外加一臺機器,單獨再部署一個倉庫管理服務上去。這樣使得整個系統,有兩個倉庫管理服務在同時工作,平衡負載。而這一切都是在服務註冊中心,比如Zookeeper下自動完成的。
微服務結構,天生很好的支援系統更新升級操作。比如財務模組有個新需求需要上線,我們只需要替換財務模組的服務重啟即可。這對已經登入系統的使用者來說,沒有多少影響,不用重新登陸系統,其他模組服務使用也不受影響。
拆分資料層
資料庫瓶頸是ERP系統的永久之傷。大量複雜的資料查詢表連線邏輯充斥著整個系統。資料庫垂直拆分成功的關鍵就是如何重新設計系統資料層各個模組相互耦合的問題。能解決這個問題,永久之傷便可以解決了。
我們先來看一個典型資料層模組耦合問題。需求是展示物料庫存,列表欄位:物料編號、物料名稱、品類、倉庫、數量
物料表:
物料ID | 名稱 | 品類ID |
Z0001 | Iphone6紅色手機殼 | Z |
Z0002 | iPhone6黑色手機殼 | Z |
庫存表:
物料ID | 倉庫ID | 數量 |
Z0001 | W1 | 10 |
Z0002 | W1 | 20 |
品類和倉庫表省略。。。
很顯然,傳統一個資料庫中,我們只需要簡單的join操作,即可關聯這兩張表,外加關聯品類和倉庫表即可查詢出我們所要的資料。但是現在我們的架構中,物料表和商品表不在同一個資料庫例項中,我們不能使用join操作了,那我們該怎麼實現需求呢?
新的架構,只允許我們通過對方的服務介面來獲取資料,不能直接關聯對方服務的私有資料庫。至少從架構上,服務化角度來說不能直接訪問對方服務的資料庫。這種情況下,假設web模組子系統呼叫倉庫子系統來獲取資料,則我們需要在倉庫模組中建立一個service方法來裝配這些資料。然後返回給web子系統。如下圖所示,倉庫管理方法首先獲取本地庫存表的物料編碼、和倉庫表的倉庫名稱欄位資訊,並且分頁完後最終準備返回20條資料到Web模組前,將這20條資料中的物料ID作為引數請求商品模組子系統,商品子系統返回這20個物料ID相關的商品資訊給到倉庫管理模組,然後倉庫管理模組重新組裝上列表所需的物料名稱和品類兩個欄位資料,實現最終要返回給Web子系統的資料。
也許你會說,這太麻煩了,這種方法的效能肯定沒有直接join來的高,解決不了效能問題。咋看起來好像是這麼回事,但是仔細考慮看看,在系統併發量低、資料量小、業務不算繁忙的環境下,的確效能還不如傳統一個資料中join方式來的快速。但我們想想以後吧!我們現在的架構設計是將一個資料庫拆成多個資料庫,每個資料庫可以執行在單獨的伺服器上去,這樣以後就能負載資料庫的壓力了。整體來說這樣才能不會讓資料庫成為未來業務繁忙時候的效能瓶頸了。想想都覺得讓人興奮不已,是不是?
這時候有人又會問,那以後系統資料量、業務更大了,連你這個拆分成幾個資料庫還不夠用怎麼辦呢?我的方法是,可以基於拆分的資料庫,單獨每個庫可以做讀寫分離、使用快取等。甚至可以繼續拆分下去,將子系統再次拆分成多個孫子系統。視業務模組繁忙程度而定。
報表系統
有人又會問,有些列表查詢邏輯非常複雜,關聯十多張表,如果按上述方法拆分資料,那簡直是災難啊!是的,你說的沒有錯。這種情況下我的方案是將這種更加複雜的報表級別的資料查詢展示需求,可以單獨做個報表系統。報表資料庫設計採用資料倉儲方式。為了更高的讀取效能,我們可以將資料庫表設計成很多冗餘欄位方式也就是反正規化設計,以及建立非常多的組合索引。
這種系統成功的關鍵就是資料和主ERP系統業務庫的同步問題了。一般可以寫一個定時同步程式,將ERP主業務系統的資料經過帥選、轉化等方式直接生成報表檢視所需的最終或中間資料,簡化關聯查詢。報表系統也可以採用微服務架構設計。如下圖所示:
如果報表所需的資料要求實時的,我們可以讓ERP系統業務操作時,觸發同步資料的請求,實時同步至報表庫。
分散式事務
也許有人又又問了,ERP系統很多操作都要求事務性,你拆分系統後怎麼實現事務性,保障資料一致性呢?
這個問題很好,也是我決定寫這篇文章前思考的最後一個問題。在微服務架構中,實現誇服務的事務並不容易,至少不像本地應用使用本地資料庫事務那樣方便,效能高效,資料一致性好。
也許你聽過分散式事務這個概念。有兩種情景,一種是一個應用中使用多個資料庫,為保障資料一致性,需要使用分散式事務。還有一種情況就是針對我們這個架構而言的。微服務環境下的分散式事務,具體來說打個比方。採購入庫這個操作設計在倉庫管理服務中。入庫後,需要更新採購子系統中的採購單中的入庫數量。這個過程要求資料一致性,也就是採購單入庫成功後寫入了庫存表中的數量,同時要更新採購單表中的入庫數量。我們不能直接在倉庫服務中去訪問採購服務中的資料庫,必須通過採購服務提供的服務介面才行。如果這樣,我們怎麼能保證資料一致性呢?因為很有可能庫存表寫入成功,但調取採購服務寫入採購單資料時失敗了。可能是網路問題原因導致的,這樣資料就不一致了。
在分散式事務技術中,有實現最終一致性這麼一說,意思就是隻要我能保證兩邊資料最終實現了一致性就行,不一定要使用事務。這樣說來就有方案了。如倉庫子系統在處理採購入庫時需要增加入庫單資料和更新庫存資料等多個表。這多個表都在倉庫子系統中,我們可以使用一個本地事務來保證倉庫子系統中的表資料一致性。然後呼叫採購子系統更新採購單裡的入庫數量。為了防止這個過程突然中斷導致呼叫失敗,我們考慮增加一個訊息佇列中介軟體如ActiveMQ。如果介面返回失敗我們就往MQ裡寫入這個處理請求,等到採購子系統恢復正常後,MQ通知採購子系統處理這個更新操作。由於訊息消費掉以後不會再有通知了,採購子系統處理過程中發生異常導致更新失敗,需要將問題寫入本地的日誌庫,以便通知管理員做後續補償處理。就這樣通過各種辦法來達到資料的最終一致性即可。雖然聽上去有點坑,但這就是解決方案。沒有其他更好的了。或者更新失敗後重新呼叫倉庫子系統回滾入庫單和庫存資料,達到最終一致性!如圖所示:
非常有幸能和大家一起分享知識和經驗,正是由於大家的無私分享,才讓我們得以成長和進步,我最近幾年來都很少分享東西,有時候是因為工作很忙沒有時間寫點東西,有時候也是因為自己懶或是沒有什麼新東西可以分享給大家的。最後也希望大家對我的分享不足之處給予批評指正,一起進步!