老舊專案二次開發指南
背景:
最近新入職公司,負責技術,由于各種原因現在專案全權交由我們團隊負責,之前的研發團隊不再參與(及以后可能完全聯系不上),作為技術負責人,又剛入職公司壓力巨大,經過兩個多月的改造,算是接手得還算行,該專案存在的問題比較典型,特此記錄,歡迎各位大佬批評指教,技術能力有限,文中所說解決方案(思路)只代表筆者的愚見,
1 老專案存在的問題
-
專案采用的主要技術:
后端: spring-boot+dubbo+redis+mybatis+oracle+jeager+apollo+flyway
前段: node+react
1.1 架構層面
1.1.1 微服務并不微
多個服務的代碼量遠超許多單體服務;
1.1.2 依賴代碼不受控制
技術框架二次封裝:由于spring框架提供的便利性,上個團隊應該是出于對技術規范的考慮,對整個專案所使用的技術框架進行了二次封裝,技術框架做了封裝;
依賴第三方團隊代碼:專案中存在對三方非開源代碼的依賴;
1.1.3 開源專案版本老舊
專案框架老舊,專案框架被二次封裝,導致依賴的專案主體技術版本老舊,不能及時更新,
1.1.4 沒有事務(包含分布式事務)
單體服務事務:使用不規范,部分CUD操作甚至沒有開啟事務;
分布式事務:雖然專案框架中有引入seata事務解決分布式事務,但可能是代碼先行框架后用,代碼中存在大量復雜SQL,導致seata不能使用,
1.1.5 缺少主要服務
沒有字典服務,字典服務、用戶資訊服務、權限服務為同一個服務(暫且叫做“主資料服務”)沒有分離;
需要說明下
字典服務:指維護字典資料的服務,及簡單的key-value資料,例如:性別(1:男,2:女),民族(1:漢,2:滿,3,蒙古...),
用戶資訊服務:指維護用戶資訊的服務;
權限服務:指用戶權限資訊的維護,設計各個子服務操作權限,選單權限的單獨維護,
服務是否需要如此拆分,各個業務場景不同,需慎重考慮,只是筆者評估目前接手的專案適合如此拆分,
1.1.6 網關服務定位混亂
后端沒有統一的網關服務,每個服務單獨校驗用戶登錄資訊,前段node服務只做了介面轉發并沒有承擔應有的大前端作業,
1.2 資料庫層面
1.2.1 未分庫分表
未分庫:整個工程四百多張表全部創建在同一個用戶下;
未分表:用戶基本資訊表,欄位上百,存在大量非常用欄位;
1.2.2 依賴資料庫
使用觸發器
使用自定義函式
使用存盤程序
1.3 快取層面(主要說明redis快取)
1.3.1 使用不同的序列化方式
專案中沒有使用統一的序列化方式
1.3.2 沒有充分使用資料型別(只使用String型別)
例如用戶基本資訊,直接序列化成jSON字串存盤,而不是使用hash,導致當需要使用用戶資訊中的某個欄位時得全部取出用戶資訊,無端增加IO,
1.4. 性能層面
1.4.1 存在回圈使用呼叫資料庫的情況而沒有使用批量操作(List,batch)
資料CUD等操作時,沒有使用批量操作執行器,而是for回圈獲取SQL拼接(并不是說SQL拼接等操作性能就一定查,這里只說明部分資料量大的情況下更推薦使用批量執行器來操作);
同一個資料回圈跨服務呼叫,舉例說明:
例如,“A應用服務”的某一個資料A_data保存著“用戶ID”,現在需要在頁面展示A應用的A_data資料,此時肯定需要把用戶名字也展示出來,對于該業務就存在回傳資料給前端時,需要呼叫主資料獲取“用戶ID”對應的“用戶資訊",
就這樣的一個應用場景,如果是回傳一個A_data串列資料給前端,則應該是先獲取所有的用戶ID串列,再通過用戶ID串列去獲取所有需要的用戶資料,可實際代碼中大量地方是使用的回圈呼叫,這無疑是一個巨大的性能損耗,
1.4.2 回傳大集合物件
就介面請求的資料回傳不夠精細,往往存在一個回傳資料再多個地方使用,而消費端卻僅僅只需要使用少量欄位,
1.4.3 存在重復解釋欄位情況
對字典資料的解釋沒有統一放到前段處理,而是由后端解釋翻譯;
后端介面沒有隔離,存在同樣的欄位A服務解釋了之后B服務接著重復解釋,具體距離:
A服務依賴B服務,現在前端請求A服務獲取資料,A服務呼叫B服務,B服務回傳資料(對欄位進行了解釋),A服務再回傳給前端之前對欄位又進行了解釋,
1.5 代碼規范
1.5.1 沒有使用列舉,全部使用常量;
1.5.2 命名不規范
1.5.3 沒有注釋
1.6 專案檔案
1.6.1 沒有任何檔案
2. 對應的解決方案
優先級確定
可以看到整個專案存在問題較多,加上研發任務也再同時進行,基于自己多年研發的直覺,對上述問題進行簡單的優先級排序,并逐一闡述理由,
經過前面對專案中存在問題的整體梳理,對專案現狀有了比較全面的了解,由于研發任務同時進行,無法投入太多人力對以上問題進行全面休整,因此基于自己多年研發累積經驗對以上問題進行了簡單的優先級排序處理,
2.1 風險規避——必須盡快解決的問題
-
未分庫分表
原因
由于專案是整體交接,之前的團隊退出,新來接手的團隊對之前的專案完全不了解,因此為了避免專案變得更亂,資料庫分離迫在眉睫, 因為所有子服務的表全部放到同一個用戶下面,如果不從技術層面實作資料的分離,由于join操作的便利性,必然會產生更多的跨服務關聯查詢,技術實作
1.通過python腳本(正則匹配)找出各個子服務中在使用的表,按照CRUD操作進行歸類; 2.對于已經存在的多個服務同時存在CUD同一個表的情況,仔細確定表的歸宿問題;對于無法確認歸宿問題的表先保存記錄,待后續業務熟悉之后再次確認(非常感謝之前的開發者,整個專案梳理下來這樣的表也只有十幾個); 3.將所有表都按照服務歸類之后,通過使用資料庫提供的公共同義詞,對跨服務使用的表分配對應的CRUDE權限(除非像上文2中提到無法確認歸宿的表之后,禁止分配CUD權限), -
依賴代碼不受控制
原因
相信這一點不用多說,不受控制的東西千萬不要使用,所用一段無法控制的代碼,不知道啥時候會爆雷;當沒有利益做橋梁,很難期望別人會給予恩惠,要不被人鎖住咽喉,唯有將不穩定的因素解決, 當然對于這樣全面的技術堆疊替換,測驗的支持必不可少,這也是我將優先級排到此處的原因,由于專案剛接受,剛好產品、測驗都需要對專案進行全面了解,這樣避免到以后真的被技術鎖喉時,還需要來進行一次全方位的測驗(這作業不是一般的小),技術實作
就框架的二次封裝而言,相對比較容易解決;把專案中使用到的技術整理出來,逐一使用開源技術替換即可;還在是對開源技術的二次封裝,大家的使用技術比較統一,專案中并沒有太多依賴二次封裝框架的代碼存在,少量的存在也通過反編譯的方式生成了原始碼, 其中最麻煩的是上個研發團隊自己封裝的工具包,整個專案涉及十幾個,由于一些緣由無法獲得該部分原始碼,沒有辦法對于這樣,也只有使用最原始的方式——反編譯(這無疑是整個專案最頭疼的一部分), -
開源專案版本老舊
原因
一、專案安全是任何一種技術被采用必須被考慮的問題,一個三年前更新的技術版本,讓我無法放心使用;以后技術版本越老舊,以后升級成本就越高,這也是需要先行處理開源技術被二次封裝的原因, 二、過時的版本,也意味著使用不到新的技術特性,技術實作
由于已經處理好了專案框架被二次封裝的問題,對于開源技術的升級相對來說比較愉悅,各個技術官網、論壇都能找到幫助檔案,
2.2 架構優化——保證業務進度的前提下,盡快處理
-
缺少主要服務
原因
主資料服務過重,性能將成為短期可預見的瓶頸,尤其是包含了字典服務的功能,而且該專案原有的架構還存在大量重復解釋同一欄位的問題,必然加重服務負擔,技術實作
筆者結合專案的實際情況,對服務的拆分目前沒有徹底進行,而是循序漸進(在迭代中重構),目前只是將相對獨立的字典服務先抽取出來,以后需要使用字典資料的時候之后呼叫字典服務,要請所有研發人員在迭代開發的程序中逐步替換掉原有走主資料的方式,改為直接呼叫欄位服務,權限服務和用戶資訊服務,由于涉及到的業務相對復雜,考慮到專案穩定性的問題暫時未做處理(待后期業務熟悉), -
沒有事務(包含分布式事務)
原因
沒有事務帶來的可怕性,不言而喻,技術實作
由于目前對整個專案業務并不熟悉,因此筆者目前還未深入處理這塊;只是先行升級開源框架,使用spring-boot 提供的優雅停服務功能來減少由于直接關閉服務帶來的資料錯亂問題; 筆者目前對這一塊的想法本來是想通過橫向切面統一給所有給GET介面都加上事務,但由于考慮到整個專案目前可以正常使用,且重新發版時間還不是特別緊因此未做處理,在此提出僅供大家參考, 另外對于分布式事務,由于專案中存在大量復雜SQL,原有seata框架沒有辦法繼續使用(這也是為啥之前引入了此框架沒有使用的原因),筆者烤爐到跨服務的事務畢竟是少數再結合目前專案進度緊張的實際情況,暫時未做處理,目前的想法是等服務先行全面測驗之后,再通過呼叫鏈路服務梳理出所有的跨服務寫資料業務,逐一排查處理, -
網關服務定位混亂
原因
網關服務混亂,每個自服務都有對用戶登錄資訊的校驗,這無疑是沒有意義的,反倒使代碼不夠整潔, 權限認證如有任何的調整都將牽涉到所有的服務, 而且如果現階段不準確的定位各個服務職能,由于每個人的喜好(代碼習慣)不同,必然后導致這個服務越來越混亂, 由于“不信任原則”,沒有服務都會過重的對客戶端進行校驗,技術實作
前端雖有node服務,但由于并沒有實際運用起來,在結合目前的人員問題,因此決定后端引入網關服務,將各個服務的權限認證業務分離,統一由網關服務承擔, 說明: 筆者將該服務重新拆分,放棄大前端模式的主要原因是: 1.原有本應由原有node服務所聚合分離不同客戶端介面的功能,并沒有在node中,而是在后端介面中看到各種webController,AppController代碼; 2.受限于前端目前人手及人員技術, 3.筆者自身對前端架構不夠熟悉,前端負責人也贊同此設計, -
使用不同的序列化方式
原因
不同的序列化方式使得對快取問題的排查,資料不夠直觀;
相同的服務由于序列化方式的不同導致同時存在多分;技術實作
1.技術選型確定合理的序列化方式;
2.新開module,完成對redis客戶端的統一包裝RedisService;
3.應用spring-boot提供的自動裝配技術,統一裝配RedisService bean;
4.使用RedisService替換原有服務中所有使用redis客戶端的地方,
2.3 性能優化——保證業務進度的前提下,盡快處理
-
存在回圈使用呼叫資料庫的情況而沒有使用批量操作(List,batch)
原因
回圈中呼叫介面是編碼的大忌,無需多言,尤其還設計跨服務呼叫,IO,時間都是幾何級增長,技術實作
使用呼叫鏈路記錄,統計出所有的介面呼叫,再分析出所有回圈呼叫介面的鏈路交由開發人員逐一處理, -
沒有充分使用資料型別(只使用String型別)
原因
涉及具體業務邏輯,改造時間長,需等業務熟悉之后方可修改技術實作
通過python腳本,分析統計出所有的大資料Key,在代碼中逐一排查審核,
2.4 風格理念——暫不處理
以下幾點由于只是風格理念的問題,因此暫不處理,作為對新服務的規范,新增服務嚴格按照新的規范實施,老服務在之后的重構計劃中再行調整,
-
依賴資料庫
原因
單一職責,資料庫只存盤資料,業務邏輯交由代碼,技術實作
禁止使用資料庫存盤之外的其他功能, -
微服務并不微
原因
使用微服務架構,就使用微服務理念技術實作
需求評審,合理定位,嚴格按照微服務的理念設計服務(掌握粒度,避免過度拆分), -
回傳大集合物件
原因
回傳大物件會暴露一些不必要的欄位,泄漏隱私;增加IO消耗,技術實作
VO獨立,介面回傳欄位按需回傳,避免大的VO設計, -
存在重復解釋欄位情況
原因
無端的性能消耗技術實作
介面分離,同時提供解釋欄位和不解釋欄位的介面(處于對現有介面的兼容,因此提供解釋欄位的介面),這樣消費者可以按需獲取, 說明:按照的架構風格,所有的介面不對字典資料做解釋處理,全部交由前段展示時翻譯,這樣的好處有: 1.對于字典資料的處理,只需要在用戶登錄系統時從后臺請求一次回傳保存到本地即可,一次請求多次使用, 2.將解釋欄位的作業交由各個客戶端處理,減少服務器壓力, -
代碼規范
1.老服務的命名問題不做處理,干掉強迫癥;
2.在迭代開發中增加代碼注釋;
3.新開發研發按照編碼規范實施,使用sonar檢測代碼質量,不合格代碼一律不予以合并,
-
專案檔案
給產品和測驗提需求,要求逐步完善專案需求相關檔案,
技術相關檔案在迭代開發中同步完善,
筆者的話
以上調整,只是筆者對當前專案存在問題處理方式;由于技術能力有限,此中難免有不合理之處,誠邀各位大佬批評指教,
以下連接為筆者平時對技術的整理歸檔也歡迎各位大佬評論交流!
zero mgy ProcessOn
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/458314.html
標籤:架構設計
上一篇:系統架構的11條原則
下一篇:老舊專案二次開發指南
