回顧下自己近八年來所做的一些系統(tǒng)設(shè)計(jì)弓颈,看看犯的一些比較大的血淋淋的錯(cuò)誤(很多都是推倒重來)边臼,發(fā)現(xiàn)大的錯(cuò)誤基本集中在前面幾年嗤瞎,從這個(gè)點(diǎn)看起來能比較自豪的說最近的幾年在系統(tǒng)設(shè)計(jì)的掌控上確實(shí)比以前成熟了很多椿访。
第1個(gè)錯(cuò)
在設(shè)計(jì)服務(wù)框架時(shí)手趣,我期望服務(wù)框架對(duì)使用者完全不侵入壳影,于是做了一個(gè)在外部放一個(gè).xml文件來描述spring里的哪些bean發(fā)布為服務(wù)的設(shè)計(jì)拱层,這個(gè)版本發(fā)布后,第一個(gè)小白鼠的用戶勉強(qiáng)在用宴咧,但覺得用的很別扭根灯,不過還是忍著用下去了,到了發(fā)布的時(shí)候掺栅,發(fā)現(xiàn)出現(xiàn)了兩個(gè)問題烙肺,一是這個(gè)xml文件研發(fā)也不知道放哪好,所以到了發(fā)布的時(shí)候都不知道去哪拿這個(gè)xml文件氧卧。
這個(gè)設(shè)計(jì)的關(guān)鍵錯(cuò)誤就在于在設(shè)計(jì)時(shí)沒考慮過這個(gè)設(shè)計(jì)方式對(duì)研發(fā)階段桃笙、運(yùn)維階段的影響,后來糾正這個(gè)錯(cuò)誤的方法是去掉了這個(gè)xml文件假抄,改為寫了一個(gè)Spring FactoryBean怎栽,用戶在spring的bean配置文件中配置下就可以丽猬。
第2個(gè)錯(cuò)
服務(wù)框架在核心應(yīng)用上線時(shí),出現(xiàn)了前端web應(yīng)用負(fù)載高熏瞄,處理線程數(shù)不夠用的現(xiàn)象脚祟,當(dāng)時(shí)處理這個(gè)故障的方式是回滾了服務(wù)框架的上線,這個(gè)故障排查了比較長的時(shí)間后强饮,查到的原因是服務(wù)框架用的JBoss Remoting在通信時(shí)默認(rèn)時(shí)間是60s由桌,導(dǎo)致一些處理速度慢的請(qǐng)求占據(jù)了前端web應(yīng)用的處理線程池。
上面這里故障的原因簡(jiǎn)單來說是分布式調(diào)用中超時(shí)時(shí)間太長的問題邮丰,但更深層次來思考行您,問題是犯在了設(shè)計(jì)服務(wù)框架時(shí)的技術(shù)選型,在選擇JBoss-Remoting時(shí)沒有充分的掌握它的運(yùn)行細(xì)節(jié)剪廉,這個(gè)設(shè)計(jì)的錯(cuò)誤導(dǎo)致的是后來決定放棄JBoss-Remoting娃循,改為基于Mina重寫了服務(wù)框架的通信部分,這里造成了服務(wù)框架的可用版本發(fā)布推遲了兩個(gè)多月斗蒋。
第3個(gè)錯(cuò)
在服務(wù)框架大概演進(jìn)到第4個(gè)版本時(shí)捌斧,通信協(xié)議上需要做一些改造,突然發(fā)現(xiàn)一個(gè)問題是以前的通信協(xié)議上是沒有版本號(hào)的泉沾,于是悲催的只能在代碼上做一個(gè)很齷蹉的處理來判斷是新版本還是老版本捞蚂。
這個(gè)設(shè)計(jì)的錯(cuò)誤非常明顯,這個(gè)其實(shí)只要在最早設(shè)計(jì)通信協(xié)議時(shí)參考下現(xiàn)有的很多的通信協(xié)議就可以避免了跷究,因此這個(gè)錯(cuò)誤糾正也非常簡(jiǎn)單姓迅,就是參考一些經(jīng)典的協(xié)議重新設(shè)計(jì)了下。
說到協(xié)議俊马,就順帶說下丁存,當(dāng)時(shí)在設(shè)計(jì)通信協(xié)議和選擇序列化/反序列化上沒充分考慮到將來多語言的問題,導(dǎo)致了后來在多語言場(chǎng)景非常的被動(dòng)潭袱,這也是由于設(shè)計(jì)時(shí)前瞻性的缺失柱嫌,所謂的前瞻性不是說一定要一開始就把未來可能會(huì)出現(xiàn)的問題就解掉,而是應(yīng)該留下不需要整個(gè)改造就可以解掉的方法屯换,這點(diǎn)對(duì)于架構(gòu)師來說也是非常重要的。
第4個(gè)錯(cuò)
在服務(wù)框架切換為Mina的版本上線后与学,發(fā)布服務(wù)的應(yīng)用重啟時(shí)出現(xiàn)一個(gè)問題彤悔,就是發(fā)現(xiàn)重啟后集群中的機(jī)器負(fù)載嚴(yán)重不均,排查發(fā)現(xiàn)是由于這個(gè)版本采用是服務(wù)的調(diào)用方會(huì)通過硬件負(fù)載均衡去建立到服務(wù)發(fā)布方的連接索守,而且是單個(gè)的長連接晕窑,由于是通過硬件負(fù)載均衡建連,意味著服務(wù)調(diào)用方其實(shí)看到的都是同一個(gè)地址卵佛,這也就導(dǎo)致了當(dāng)服務(wù)發(fā)布方重啟時(shí)杨赤,服務(wù)調(diào)用方重連就會(huì)集中的連到存活的機(jī)器上敞斋,連接還是長連,因此就導(dǎo)致了負(fù)載的不均衡現(xiàn)象疾牲。
這個(gè)設(shè)計(jì)的錯(cuò)誤主要在于沒有考慮生產(chǎn)環(huán)境中走硬件負(fù)載均衡后植捎,這種單個(gè)長連接方式帶來的問題,這個(gè)錯(cuò)誤呢還真不太好糾正阳柔,當(dāng)時(shí)臨時(shí)用的一個(gè)方法是服務(wù)調(diào)用方的連接每發(fā)送了1w個(gè)請(qǐng)求后焰枢,就把連接自動(dòng)斷開重建,最終的解決方法是去掉了負(fù)載均衡設(shè)備這個(gè)中間點(diǎn)舌剂。
第5個(gè)錯(cuò)
服務(wù)框架在做了一年多以后济锄,某個(gè)版本中出現(xiàn)了一個(gè)嚴(yán)重bug,然后我們就希望能通知到用了這個(gè)版本的應(yīng)用緊急升級(jí)霍转,在這個(gè)時(shí)候悲催的發(fā)現(xiàn)一個(gè)問題是我們壓根就不知道生產(chǎn)環(huán)境中哪些應(yīng)用和機(jī)器部署了這個(gè)版本荐绝,當(dāng)時(shí)只好用一個(gè)臨時(shí)的掃全網(wǎng)機(jī)器的方法來解決。
這個(gè)問題后來糾正的方法是在服務(wù)發(fā)布和調(diào)用者在連接我們的一個(gè)點(diǎn)時(shí)避消,順帶把用的服務(wù)框架的版本號(hào)帶上很泊,于是就可以很簡(jiǎn)單的知道全網(wǎng)的服務(wù)框架目前在運(yùn)行的版本號(hào)了。
第6個(gè)錯(cuò)
服務(wù)框架這種基礎(chǔ)類型的產(chǎn)品沾谓,在發(fā)布時(shí)會(huì)碰到個(gè)很大的問題委造,就是需要通知到使用者去發(fā)布,導(dǎo)致了整個(gè)發(fā)布周期會(huì)相當(dāng)?shù)拈L均驶,當(dāng)時(shí)做了一個(gè)決定昏兆,投入資源去實(shí)現(xiàn)完全動(dòng)態(tài)化的發(fā)布,就是不需要重啟妇穴,等到做的時(shí)候才發(fā)現(xiàn)這完全就是個(gè)超級(jí)大坑爬虱,最終這件事在投入兩個(gè)人做了接近半年后,才終于決定放棄腾它,而且最終來看其實(shí)升級(jí)的問題也沒那么大跑筝。
這個(gè)問題最大的錯(cuò)誤在于對(duì)細(xì)節(jié)把握不力,而且決策太慢瞒滴。
第7個(gè)錯(cuò)
服務(wù)發(fā)布方經(jīng)常會(huì)碰到一個(gè)問題曲梗,就是一個(gè)服務(wù)里的某些方法是比較耗資源的,另外的一些可能是不太耗資源妓忍,但對(duì)業(yè)務(wù)非常重要的方法虏两,有些場(chǎng)景下會(huì)出現(xiàn)由于耗資源的方法被請(qǐng)求的多了些導(dǎo)致不太耗資源的方法受影響,這種場(chǎng)景下如果要去拆成多個(gè)服務(wù)世剖,會(huì)導(dǎo)致開發(fā)階段還是挺痛苦的定罢,因此服務(wù)框架這邊決定提供一個(gè)按方法做七層路由的功能,服務(wù)的發(fā)布方可以在一個(gè)地方編寫一個(gè)規(guī)則文件旁瘫,這個(gè)規(guī)則文件允許按照方法將生產(chǎn)環(huán)境的機(jī)器劃分為不同組祖凫,這樣當(dāng)服務(wù)調(diào)用方調(diào)用時(shí)就可以做到不同方法調(diào)用到不同的機(jī)器琼蚯。
這個(gè)功能對(duì)有些場(chǎng)景來說用的很爽,但隨著時(shí)間的演進(jìn)和人員的更換惠况,能維護(hù)那個(gè)文件的人越來越少了遭庶,也成為了問題。
這個(gè)功能到現(xiàn)在為止我自己其實(shí)覺得也是一直處于爭(zhēng)議中售滤,我也不知道到底是好還是不好...
減少犯錯(cuò)誤的方法:多閱讀罚拟、多實(shí)踐、多總結(jié)完箩。
第8個(gè)錯(cuò)
服務(wù)框架在用的越來越廣后赐俗,碰到了一個(gè)比較突出的問題,服務(wù)框架依賴的jar版本和應(yīng)用依賴的jar版本沖突弊知,服務(wù)框架作為一個(gè)通用技術(shù)產(chǎn)品阻逮,基本上沒辦法為了一個(gè)應(yīng)用改變服務(wù)框架自己依賴的jar版本,這個(gè)問題到底怎么去解秩彤,當(dāng)時(shí)思考了比較久叔扼。
可能是由于我以前OSGi這塊背景的原因,在設(shè)計(jì)上我做了一個(gè)決定漫雷,引入OSGi瓜富,將服務(wù)框架的一堆jar處于一個(gè)獨(dú)立的classloader,和應(yīng)用本身的分開降盹,這樣就可以避免掉jar沖突的問題与柑,在我做了引入OSGi這個(gè)決定后,團(tuán)隊(duì)的1個(gè)資深的同學(xué)就去做了蓄坏,結(jié)果是折騰了近兩個(gè)月整個(gè)匹配OSGi的maven開發(fā)環(huán)境都沒完全搭好价捧,后來我自己決定進(jìn)去搞這件事,即使是我對(duì)OSGi比較熟涡戳,也折騰了差不多1個(gè)多月才把整個(gè)開發(fā)的環(huán)境结蟋,工程的結(jié)構(gòu),以及之前的代碼基本遷移為OSGi結(jié)構(gòu)渔彰,這件事當(dāng)時(shí)折騰好上線后嵌屎,效果看起來是不錯(cuò)的,達(dá)到了預(yù)期胳岂。
但這件事后來隨著加入服務(wù)框架的新的研發(fā)人員越來越多编整,發(fā)現(xiàn)多數(shù)的新人都在學(xué)習(xí)OSGi模式的開發(fā)這件事上投入了不少的時(shí)間,就是比較難適應(yīng)乳丰,所以后來有其他業(yè)務(wù)問是不是要引入OSGi的時(shí)候,我基本都會(huì)建議不要引入内贮,主要的原因是OSGi模式對(duì)大家熟悉的開發(fā)模式产园、排查問題的沖擊汞斧,除非是明確需要classloader隔離、動(dòng)態(tài)化這兩個(gè)點(diǎn)什燕。
讓我重新做一個(gè)決策的話粘勒,我會(huì)去掉對(duì)OSGi的引入,自己做一個(gè)簡(jiǎn)單的classloader隔離策略來解決jar版本沖突的問題屎即,保持大家都很熟悉的開發(fā)模式庙睡。
第9個(gè)錯(cuò)
服務(wù)框架在用的非常廣了后,團(tuán)隊(duì)經(jīng)常會(huì)被一個(gè)問題困擾和折騰技俐,就是業(yè)務(wù)經(jīng)常會(huì)碰到調(diào)用服務(wù)出錯(cuò)或超時(shí)的現(xiàn)象乘陪,這種情況通常會(huì)讓服務(wù)框架這邊的研發(fā)來幫助排查,這個(gè)現(xiàn)象之所以查起來會(huì)比較復(fù)雜雕擂,是因?yàn)榉?wù)調(diào)用通常是多層的關(guān)系啡邑,并不是簡(jiǎn)單的A-->B的問題,很多時(shí)候都會(huì)出現(xiàn)A-->B-->C-->D或者更多層的調(diào)用井赌,超時(shí)或者出錯(cuò)都有可能是在其中某個(gè)環(huán)節(jié)谤逼,因此排查起來非常麻煩。
在這個(gè)問題越來越麻煩后仇穗,這個(gè)時(shí)候才想起在09年左右團(tuán)隊(duì)里有同學(xué)看過G家的一篇叫dapper的論文流部,并且做了一個(gè)類似的東西,只是當(dāng)時(shí)上線后我們一直想不明白這東西拿來做什么纹坐,到了排查問題這個(gè)暴露的越來越嚴(yán)重后枝冀,終于逐漸想起這東西貌似可以對(duì)排查問題會(huì)產(chǎn)生很大的幫助。
到了這個(gè)階段才開始做這件事后恰画,碰到的主要不是技術(shù)問題宾茂,而是怎么把新版本升級(jí)上去的問題,這個(gè)折騰了挺長時(shí)間拴还,然后上線后又發(fā)現(xiàn)了一個(gè)新的問題是跨晴,即使服務(wù)框架具備了Trace能力,但服務(wù)里又會(huì)調(diào)外部的例如數(shù)據(jù)庫片林、緩存等端盆,那些地方如果有問題也會(huì)看不到,排查起來還是麻煩费封,于是這件事要真正展現(xiàn)效果就必須讓Trace完全貫穿所有系統(tǒng)焕妙,為了做成這件事,N個(gè)團(tuán)隊(duì)付出了好幾年的代價(jià)弓摘。
第10個(gè)錯(cuò)
服務(wù)的發(fā)布方有些時(shí)候會(huì)碰到一個(gè)現(xiàn)象是焚鹊,服務(wù)還沒完全ready,就被調(diào)用了韧献;還有第二個(gè)現(xiàn)象是服務(wù)發(fā)布方出現(xiàn)問題時(shí)末患,要保留現(xiàn)場(chǎng)排查問題研叫,但服務(wù)又一直在被調(diào)用,這種情況下就沒有辦法很好的完全保留現(xiàn)場(chǎng)來慢慢排查問題了璧针。
這兩個(gè)現(xiàn)象會(huì)出現(xiàn)的原因是服務(wù)框架的設(shè)計(jì)是通過啟動(dòng)后和某個(gè)中心建立連接嚷炉,心跳成功后其他調(diào)用方就可以調(diào)用到,心跳失敗后就不會(huì)被調(diào)到探橱,這樣看起來很自動(dòng)化申屹,但事實(shí)上會(huì)導(dǎo)致的另外一個(gè)問題是外部控制上下線這件事的能力就很弱。
這個(gè)設(shè)計(jì)的錯(cuò)誤主要還是在設(shè)計(jì)時(shí)考慮的不夠全面隧膏。
第11個(gè)錯(cuò)
在某年我和幾個(gè)小伙伴決定改變當(dāng)時(shí)用xen的模式哗讥,換成用一種輕量級(jí)的“虛擬機(jī)”方式來做,從而提升單機(jī)跑的應(yīng)用數(shù)量的密度私植,在做這件事時(shí)忌栅,我們決定自己做一個(gè)輕量級(jí)的類虛擬機(jī)的方案,當(dāng)時(shí)決定的做法是在一個(gè)機(jī)器上直接跑進(jìn)程曲稼,然后碰到一堆的問題索绪,例如從運(yùn)維體系上來講,希望ssh到“機(jī)器”贫悄、獨(dú)立的ip瑞驱、看到自己的系統(tǒng)指標(biāo)等等,為了解決這些問題窄坦,用了N多的黑科技唤反,搞得很悲催,更悲催的是當(dāng)時(shí)覺得這個(gè)問題不多鸭津,于是用一些機(jī)器跑了這個(gè)模式彤侍,結(jié)果最后發(fā)現(xiàn)這里面要黑科技解決的問題實(shí)在太多了,后來突然有個(gè)小伙伴提出我們?cè)囉胠xc吧逆趋,才發(fā)現(xiàn)我們之前用黑科技解的很多問題都沒了共屈,哎副编,然后就是決定切換到這個(gè)模式揍鸟,結(jié)果就是線上的那堆機(jī)器重來涝影。
這個(gè)設(shè)計(jì)的主要錯(cuò)誤在于知識(shí)面不夠廣,導(dǎo)致做了個(gè)不正確的決定魄眉,而且推倒重來砰盐。
第12個(gè)錯(cuò)
還是上面這個(gè)技術(shù)產(chǎn)品,這個(gè)東西有一個(gè)需求是磁盤空間的限額坑律,當(dāng)時(shí)的做法是用image的方式來占磁盤空間限額岩梳,這個(gè)方式跑了一段時(shí)間覺得沒什么問題,于是就更大程度的鋪開了,但鋪開跑了一段時(shí)間后蒋腮,出現(xiàn)了一個(gè)問題淘捡,就是經(jīng)常出現(xiàn)物理機(jī)磁盤空間不足的報(bào)警藕各,而且刪掉了lxc容器里的文件也還是不行池摧,因?yàn)閕mage方式只要占用了就會(huì)一直占著這個(gè)大小,只會(huì)擴(kuò)大不會(huì)縮小激况。
當(dāng)時(shí)對(duì)這個(gè)問題極度的頭疼作彤,只能是刪掉文件后,重建image乌逐,但這個(gè)會(huì)有個(gè)要求是物理機(jī)上有足夠的空間竭讳,即使有足夠的空間,這個(gè)操作也是很折騰人的浙踢,因?yàn)榈孟韧5羧萜骶盥琧p文件到新創(chuàng)建的容器,這個(gè)如果東西多的話洛波,還是要耗掉一定時(shí)間的胰舆。
后來覺得這個(gè)模式實(shí)在是沒法玩,于是尋找新的解決方法蹬挤,來滿足磁盤空間限額缚窿,最后我們也是折騰了比較長一段時(shí)間后終于找到了更靠譜的解決方案。
這個(gè)設(shè)計(jì)的主要錯(cuò)誤還是在選擇技術(shù)方案時(shí)沒考慮清楚焰扳,對(duì)細(xì)節(jié)掌握不夠倦零,考慮的面不夠全,導(dǎo)致了后面為了換掉image這個(gè)方案吨悍,用了極大的代價(jià)扫茅,我印象中是一堆的人熬了多次通宵來解決。
第13個(gè)錯(cuò)
仍然是上面的這個(gè)技術(shù)產(chǎn)品育瓜,在運(yùn)行的過程中葫隙,突然碰到了一個(gè)虛擬機(jī)中線程數(shù)創(chuàng)建太多,導(dǎo)致其他的虛擬機(jī)也創(chuàng)建不了線程的現(xiàn)象(不是因?yàn)槲锢碣Y源不夠的問題)爆雹,排查發(fā)現(xiàn)是由于盡管lxc支持各個(gè)容器里跑相同名字的賬號(hào)停蕉,但相同名字的賬號(hào)的uid是相同的,而max processes是限制在UID上的钙态,所以當(dāng)一個(gè)虛擬機(jī)創(chuàng)建的線程數(shù)超過時(shí)慧起,就同樣影響到了其他相同賬號(hào)的容器。
這個(gè)問題我覺得一定程度也可以算是設(shè)計(jì)問題册倒,設(shè)計(jì)的時(shí)候確實(shí)由于對(duì)細(xì)節(jié)掌握的不夠蚓挤,考慮的不全導(dǎo)致忽略了這個(gè)點(diǎn)。
第14個(gè)錯(cuò)
在三年前做一個(gè)非常大的項(xiàng)目時(shí),項(xiàng)目即將到上線時(shí)間時(shí)灿意,突然發(fā)現(xiàn)一個(gè)問題是估灿,有一個(gè)關(guān)鍵的點(diǎn)遺漏掉了,只好趕緊臨時(shí)討論方案決定怎么做缤剧,這個(gè)的改動(dòng)動(dòng)作是非常大的馅袁,于是項(xiàng)目的上線時(shí)間只能推遲,我記得那個(gè)時(shí)候緊急周末加班等搞這件事荒辕,最后帶著比較高的風(fēng)險(xiǎn)上了汗销。
這個(gè)問題主要原因是在做整體設(shè)計(jì)時(shí)遺漏掉了這個(gè)關(guān)鍵點(diǎn)的考慮,當(dāng)時(shí)倒不是完全忽略了這個(gè)點(diǎn)抵窒,而是在技術(shù)細(xì)節(jié)上判斷錯(cuò)誤弛针,導(dǎo)致以為不太要做改動(dòng)。
因此對(duì)于一個(gè)架構(gòu)師來說李皇,對(duì)技術(shù)細(xì)節(jié)的掌控是非常重要的削茁,這里要注意的是,其實(shí)不代表架構(gòu)師自己要完全什么都很懂掉房,但架構(gòu)師應(yīng)該清楚在某個(gè)點(diǎn)上靠譜的人是誰茧跋。