2010年畢業(yè)后州袒,加入了一家傳統(tǒng)行業(yè)的公司做Java開發(fā)揭绑。轉(zhuǎn)眼八年過去了,產(chǎn)品架構(gòu)在近一兩年也開始由單體架構(gòu)轉(zhuǎn)變?yōu)槲⒎?wù)架構(gòu)。重點還是想回顧下單體架構(gòu)下開發(fā)的一些心路歷程他匪。
剛加入公司菇存,幸福的入職培訓(xùn)過后開始加入了項目。那時還沒有開始敏捷開發(fā)邦蜜,一個人負責(zé)一個模塊依鸥。公司有嚴(yán)格的KPI考核,個人業(yè)績與個人所負責(zé)模塊的質(zhì)量直接掛鉤悼沈。所以一名新人加入公司贱迟,通常會分配到誰都不想接的模塊。當(dāng)然絮供,這是我后來才知道的关筒。
當(dāng)時有位三年經(jīng)驗的老員工即將內(nèi)部調(diào)動到別的城市工作,他負責(zé)的模塊交到了我的手上杯缺。計算機專業(yè)科班出身蒸播,并且在校時熟讀《Java編程思想》兩遍,內(nèi)心覺得不是啥事萍肆。等看到代碼后袍榆,直接傻眼了。代碼行數(shù)倒不是很多塘揣,不到5000行的樣子包雀。其中一個for
循環(huán)占了大概3000行!你沒看錯亲铡,是一個for
循環(huán)才写。本身對項目的業(yè)務(wù)知識不熟悉,代碼里又各種特殊處理奖蔓,只能一點點啃了赞草。每天用eclipse debug,兩周后吆鹤,逐漸理清了這個循環(huán)的基本邏輯厨疙。如果是現(xiàn)在,我肯定會先做一些不改變代碼邏輯的重構(gòu)疑务,拆分小方法沾凄,抽取重復(fù)邏輯等等,當(dāng)時是不具備這個意識和能力的知允。很快撒蟀,這個模塊發(fā)布到外場商用了。這樣的代碼在商用環(huán)境的表現(xiàn)可想而知了温鸽,各種嚴(yán)重的故障單紛至沓來保屯。前面說過,個人業(yè)績和個人所負責(zé)模塊的質(zhì)量直接掛鉤,可能讀者會認為我的業(yè)績很差配椭。恰恰相反虫溜,我半年考核得了優(yōu)秀雹姊!并且成為了部門的優(yōu)秀畢業(yè)生股缸!除了公司鼓勵新員工的考慮以及領(lǐng)導(dǎo)對我工作態(tài)度的認可,負責(zé)的這個模塊也貢獻不少吱雏。雖然質(zhì)量很差敦姻,但是問題嚴(yán)重啊,問題嚴(yán)重到領(lǐng)導(dǎo)經(jīng)常被投訴歧杏,嚴(yán)重到領(lǐng)導(dǎo)經(jīng)常來我工位前督促镰惦,這個故障什么時候能解決。雖然問題嚴(yán)重犬绒,但是我解決問題速度快啊旺入。
如果沒有維護過這種代碼,你不會體會到《重構(gòu)》《代碼整潔之道》這些書中的觀點的可貴凯力。阿里去年推出的《阿里巴巴Java開發(fā)手冊》茵瘾,里邊的很多條目我們都踩過坑。比如
【強制】ArrayList的subList結(jié)果不可強轉(zhuǎn)成ArrayList咐鹤,否則會拋出ClassCastException
異常拗秘,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.
說明:subList 返回的是 ArrayList 的內(nèi)部類 SubList,并不是 ArrayList 祈惶,而是
ArrayList 的一個視圖雕旨,對于 SubList 子列表的所有操作最終會反映到原列表上。
【強制】在 subList 場景中捧请,高度注意對原集合元素個數(shù)的修改凡涩,會導(dǎo)致子列表的遍歷、增加疹蛉、刪除均會產(chǎn)生 ConcurrentModificationException 異常突照。
還有,曾經(jīng)維護過一段代碼氧吐,這段代碼使用了一個數(shù)據(jù)結(jié)構(gòu)Map<String, Map<Sting, Map<String, Object>>>
讹蘑,業(yè)務(wù)邏輯主要圍繞這個數(shù)據(jù)結(jié)構(gòu)展開。筆者為了搞懂這個數(shù)據(jù)結(jié)構(gòu)筑舅,在公司熬到凌晨兩點座慰,無奈放棄!還有翠拣,對HashMap
的理解版仔,對面向?qū)ο蟮睦斫猓际窃谶@個過程中不斷加深的。
很多知識點你只是看過是無感的蛮粮,只有犯過錯益缎、被坑過才會掌握!
去年阿里推出這個手冊后然想,我一直在思考為什么我們或者其他公司沒有推出這個手冊莺奔?我想一方面是阿里的影響力,但是也能看出阿里對待技術(shù)的嚴(yán)謹以及對業(yè)界的責(zé)任感变泄。這樣的企業(yè)是值得尊敬的令哟!
前面的那個模塊在維護了一年多后跟隨項目終止了,若干年后和那個項目的項目經(jīng)理在一張桌上吃飯妨蛹,他告訴我說:“那個模塊的代碼已經(jīng)不能用爛字來形容了”屏富。
后來開始開發(fā)一個消息驅(qū)動的業(yè)務(wù)模塊,大概流程是這樣的(我們的代碼主要在紅色框中):
階段1:流程是這樣的蛙卤,我就是這樣做的狠半。來一條消息,按這個流程一條道走到黑颤难。
這種設(shè)計很快暴露了問題:
1.處理效率低
2.系統(tǒng)A不僅對接系統(tǒng)B一個系統(tǒng)神年,由于系統(tǒng)B的處理效率低下,經(jīng)常導(dǎo)致和系統(tǒng)A對接的其他系統(tǒng)出現(xiàn)問題乐严。
效率低怎么辦瘤袖,上多線程啊昂验!
階段2:系統(tǒng)A來一條消息捂敌,系統(tǒng)B啟動一個線程處理
由于剛開始商用,消息量不是很大既琴,效率問題暫時解決了占婉,也不會阻塞系統(tǒng)A了。
大概半年后甫恩,業(yè)務(wù)量上來后逆济,這種設(shè)計的弊端呈現(xiàn)了:線程啟動過多,OOM了磺箕。
也就是從這個時候開始,逐漸開始系統(tǒng)學(xué)習(xí)Java的多線程知識松靡。
階段3:系統(tǒng)B用線程池處理
這種設(shè)計不會OOM了,但是效率問題沒有根本解決雕欺。這個時候開始了各種性能優(yōu)化的探索:
1.優(yōu)化各種JVM參數(shù)
2.系統(tǒng)C查詢數(shù)據(jù)庫效率低岛马,所以我們直接連接系統(tǒng)C的數(shù)據(jù)庫,用各種SQL關(guān)聯(lián)查詢提高效率啦逆。
3.數(shù)據(jù)生效到系統(tǒng)A效率低铆遭,我們做消息收集批量處理叙凡。
這樣優(yōu)化后矢劲,系統(tǒng)性能還算差強人意子库,在外場商用了近三年睁冬。
在此期間溉贿,運維的同事反饋了不少問題:
1.有時候只能處理消息A的請求,消息B的請求處理不了
2.數(shù)據(jù)庫查詢經(jīng)常超時浦旱,導(dǎo)致整個業(yè)務(wù)請求處理失敗。
研發(fā)側(cè)也暴露了一些問題:
數(shù)據(jù)模型一旦發(fā)生變更颁湖,查詢數(shù)據(jù)庫的SQL語句就必須重寫,工作量很大甥捺。
為了解決上述問題抢蚀,有了如下的設(shè)計:
階段4:線程池分類镰禾,消息處理設(shè)置超時機制。
主要優(yōu)化點:
1.按照消息類型吴侦,每種消息設(shè)置一個獨立的線程池處理屋休,線程池的配置通過properties
文件動態(tài)生效。
2.按照業(yè)務(wù)功能备韧,查詢數(shù)據(jù)庫和數(shù)據(jù)生效設(shè)置專門的線程池劫樟。
3.每條消息處理設(shè)置超時機制,避免局部失敗影響整體织堂。
這兩步優(yōu)化完成后叠艳,市場的反饋已經(jīng)非常好了,各種性能問題不復(fù)存在易阳。
考慮到后續(xù)業(yè)務(wù)量進一步增加的可能性以及架構(gòu)的穩(wěn)定性附较,繼續(xù)改進:
階段5:引入數(shù)據(jù)庫緩存和領(lǐng)域模型。
主要優(yōu)化點:
1.引入緩存機制闽烙,進一步提升數(shù)據(jù)查詢效率
2.建立領(lǐng)域模型翅睛,業(yè)務(wù)處理與底層數(shù)據(jù)庫模型解耦
至此声搁,單體架構(gòu)下的系統(tǒng)B設(shè)計告一段落。
近兩年捕发,產(chǎn)品開始向微服務(wù)架構(gòu)轉(zhuǎn)型疏旨。引入Spring Cloud
系列技術(shù),當(dāng)筆者第一次看到Hystrix
框架扎酷,看到艙壁模式檐涝、斷路器模式等概念的時候,頗有點相見恨晚法挨!原來我們折騰這么多年的設(shè)計谁榜,業(yè)界早已實現(xiàn)。接觸到Rxjava
對線程池的分類凡纳,也與我們按照業(yè)務(wù)功能對線程池分類異曲同工窃植。我們自己實現(xiàn)的緩存機制,業(yè)界更是有很多優(yōu)秀的實現(xiàn)荐糜。至于我們痛定思痛后抽取的領(lǐng)域模型巷怜,在《實踐領(lǐng)域驅(qū)動設(shè)計》等書中早已闡述明白。貧血模型到充血模型的演進暴氏,服務(wù)層的提取延塑,等等。
不得不承認答渔,在傳統(tǒng)行業(yè)八年的技術(shù)成長與互聯(lián)網(wǎng)公司是沒法比关带。互聯(lián)網(wǎng)公司的技術(shù)之所以先進,我想主要是他們面臨的外部壓力更多沼撕,促使他們不得不去改變宋雏,不得不不斷研究新的技術(shù)方案。但是端朵,如果在互聯(lián)網(wǎng)公司只是做一個小螺絲釘好芭,一入職就是在成熟的技術(shù)框架內(nèi)開發(fā),也未必會對這些技術(shù)有多么深刻的理解冲呢。為什么要有領(lǐng)域模型舍败?為什么要用緩存?為什么用Hystrix
?甚至為什么面向?qū)ο笤O(shè)計要遵循SOLID五原則敬拓?這些問題筆者在八年的職業(yè)生涯不斷挖坑邻薯,不斷填坑的過程中領(lǐng)會到的自然要比直接從書本上看來要深刻的多。
現(xiàn)在幾乎各個公司乘凸、各個項目都在上微服務(wù)架構(gòu),都在玩docker
灵嫌。跟隨技術(shù)潮流本身值得鼓勵,但是筆者始終認為寿羞,如果你的問題在單體架構(gòu)下沒有想明白,只是引入了各種容器技術(shù)绪穆,微服務(wù)技術(shù),你的問題不會得到根本的解決玖院。比如菠红,我的階段1設(shè)計完全可以放在一個微服務(wù)中實現(xiàn)试溯,以容器的形態(tài)發(fā)布扔傅。