當我接到一個DAU幾十w的項目,並開始維護它

最近,我著手維護一個項目,在原來代碼的基礎上去做開發、維護的工作。這個項目曾經承載著日活幾十萬,月流水一個多億的業務,所以它裡面一定會有很多的功能和優秀的思想。

我拿到源代碼的時候給我的是一個zip包,它就像一個黑盒子,承載著你所有的期待和不確定性,沒有任何人去告訴我這個項目裡都有什麼業務,它是什麼樣子的一個架構,它應該怎麼啟動,啟動之後應該怎麼訪問,都有哪些接口,更沒有任何項目文檔,連數據庫裡的表結構都沒有註釋。

當我把它解壓之後,我發現這是一個單體maven項目,看起來應該不錯,至少不是一個老古董(還記得在lib目錄下手動添加三方依賴jar包解決衝突的時代嗎?)。

我需要快速去熟悉它,並在此基礎上開發新的功能。

實際工作中,你們遇到過這種項目嗎?你是如何進入並逐步熟悉它呢?以下分享我的一些方法。

1、項目概覽

當我用idea打開它的時候,光是依賴我就下載了一個多小時,可能是我的網絡不好,但至少可以確定的是,內網的maven私服倉庫裡沒有這些版本的jar包。

我在下載依賴的過程中,掃了一眼pom.xml文件,竟然發現了struts2的依賴,是被註釋掉的:

當我接到一個DAU幾十w的項目,並開始維護它

還記得這些struts2的依賴包都是幹嘛的嗎?

繼續去查看關於spring框架的依賴,我想它一定是有的,否則離開了spring框架,我肯定玩不轉它,看到了spring的依賴,是基於4.2.1.RELEASE版本的:

<code>
  org.springframework
spring-core
${spring.version}


  org.springframework
spring-aop
${spring.version}


  org.springframework
spring-beans
${spring.version}


  org.springframework
spring-context
${spring.version}
compile


  org.springframework
spring-orm
${spring.version}


  org.springframework
spring-context-support
${spring.version}


  org.springframework
spring-jdbc
${spring.version}


  org.springframework
spring-web
${spring.version}


  org.springframework
spring-webmvc
${spring.version}


  org.springframework
spring-test
${spring.version}


  org.springframework
spring-tx
${spring.version}



  UTF-8
4.2.1.RELEASE
/<code>

可以知道的是,這不是一個springboot項目,更不可能是springcloud項目了,這個項目曾經來自於SSM或者SSH的架構,這裡的第一個S可不是SpringMVC,是Struts2,相信沒有五年工作經驗的人,大多數沒有用過這個框架,除非你和我一樣接手了一個古董項目。

終於下載完項目依賴,可以看到這個項目的全貌了。

它是一個標準的java web項目,有src/main/java、resources、webapp,webapp下面還有WEB-INF目錄,以及web.xml。

大家都知道,一個標準的基於springboot前後端分離的項目是沒有webapp和web.xml文件的,這些都被springboot通過註解給幹掉了。

當我接到一個DAU幾十w的項目,並開始維護它

還記得web的三大組件嗎?

Servlet、Listener、Filter,這些都屬於web基礎,一定要會的。

用springboot的同學也要知道springboot裡是怎麼玩這三大組件的。

到這裡我開始感到驚訝了,一個月流水過億、日活幾十萬、研發團隊幾十號人,項目竟會如此老舊?還是傳統的單體應用,以war包方式部署?你們的技術追求呢?

在微服務滿天飛的今天,大家是不是覺得不可思議?

是的,可是他就這樣存在著,而無視單體應用帶來的部署風險、代碼衝突導致的效率低下。

2、先把它運行起來

顯然它不是一個springboot項目,也沒有看到啟動類,所以我需要一個web容器來把這個war包部署運行起來。

如果他能夠在本地tomcat下運行起來,說明他是完整的、健康的,反之我需要解決導致無法啟動的問題,在一切還沒有熟悉的時候。

顯然,我沒有把它運行起來,根據啟動日誌發現是無法連接memcached,可是我並不準備使用memcached,這就需要我去了解是否能夠使用redis去替換掉它。從技術角度來看是可以的,比較memcached只有字符串類型,只要保證序列化和反序列化工具不變就行。

開始進入這個項目的代碼世界了,果不其然,代碼是15年的代碼,有點老舊,和我的工作年限差不多。

發現memcached的使用被封裝成了一個類,這還不錯,至少不用到處修改,此時我只需要搞清楚一個問題,有沒有一些初始化的數據放在memcached中,如果沒有就可以放心的替換掉memcached了,如果有,那麼就需要拿過來放到redis一份。

但是這個問題是沒有人告訴我的,也沒有任何文檔,原來的阿里雲memcached服務器也被釋放了,所以忽略這個問題。

開始更改這個類,使用RedisTemplate來替換原來的MemcachedClient,因為這個類裡的方法都是static修飾的,所以我想使用@Resource的方式注入RedisTemplate行不通,只能定義一個全局靜態變量,來拿到RedisTemplate這個bean。

所以我不得不寫一個工具類來獲取自己想要的任何bean:

當我接到一個DAU幾十w的項目,並開始維護它

還記得Spring中的接口類ApplicationContextAware的作用嗎?

XxxAware就是對xxx感知的意思,這裡就是對ApplicationContext感知的意思,實現這個接口可以拿到ApplicationContext,然後通過它可以獲取到很多我們想要的東西,比如容器中加載的bean,environment等。

當然這個類我還需要加載到容器中去,因為框架的原因,我不得不使用xml的bean定義方式:

<code>/<code>

知道這裡為什麼是lazy-init=false嗎?

這個時候我就可以通過這個SpringUtil工具類來獲取我們想要的redisTemplate實例bean對象了:

<code>private static RedisTemplate redisTemplate = SpringUtil.get("redisTemplate",RedisTemplate.class);/<code>

第一個麻煩解決了,繼續使用tomcat來運行它,這個時候發現了一個詭異的問題,日誌沒有任何問題,就是運行到一半卡住不動了,我以為是我剛剛修改的代碼有問題,會不會是SpringUtil這個類加載的時候有死循環之類的問題,使用top命令查看,沒有吃cpu很厲害的線程,java相關的線程cpu也很穩定,那就一定是阻塞在什麼地方了。

重新啟動一下試試,還是這個問題,卡住不動,但是我發現了一個問題,每次日誌都在一個地方卡住就不再輸出了,找到這行日誌在代碼中的位置,發現是阿里雲的OSSClient連接問題,進而發現開發環境連接地址竟然是阿里雲oss的內網地址。

換成外網地址繼續啟動,這次終於啟動成功。

3、去找一個業務點作為突破口

當項目成功啟動之後,我試圖去找一個controller作為入口逐步跟進去,看都做了什麼事情以及怎麼做的,可是我並沒有找到這個項目的任何Controller,而是發現了一個servlet的package,裡面有大量的子package和servlet,我開始有預感,這是一個用servlet來開發API層的項目,而不是@Controller、@RestController類型的,當我打開其中一個HomeServlet,我看到代碼註釋說這是一個商城首頁的Servlet,並且開發時間是15年3月:

當我接到一個DAU幾十w的項目,並開始維護它

看這段代碼,是基於@WebServlet註解來實現的,注入的方式是用servlet的init()方法來注入相關的service,數據傳參處理也沒有SpringMVC的@RequestParam、@RequestBody這樣的註解來封裝,所以傳參、參數的校驗、數據的返回處理都需要自己寫代碼來處理。

繼續跟進到service層,發現了一個巨大的方法,244行代碼:

當我接到一個DAU幾十w的項目,並開始維護它

且返回也沒用對象封裝,而是一個萬能的HashMap,哎,一群沒有技術追求的同學。。。

dao層發現用的是ibatis,dao層就不用跟了,沒有什麼業務,無非就是一些sql,能支持事務就行。

項目的整體脈絡已經摸的差不多了,接下來就是梳理這裡的業務,將項目整體分成幾大模塊,不求甚解,整體把握即可。

4、後面要做的事情

基於這樣的一個項目,大家有沒有一些改造的想法?如果給你這個項目,在保證業務ok的情況下,你有哪些想做的事情?

秉持做事的態度,為了以後開發維護方便,我覺得我還是需要去做一些事情的,例如這些:

  1. 將SpringMVC集成進來,使得SpringMVC和當前的Servlet兩種可以共存,新的用SpringMVC開發,舊的逐步改造過來,集成的時候考慮版本的兼容問題;
  2. 抽一個公共返回類,比如ResultResp,並提供一些success(),success(T data),fail(),fail(int code, String msg)方法,用於接口統一返回;
  3. 統一的異常攔截處理,不吐服務端異常信息給前端;
  4. 重構一些業務模塊,使得每個方法不超過80行,方法的複雜度不超過18,多加註釋;
  5. 一些重複的代碼功能採用註解的形式抽取出來;
  6. 將service層的sql語句下沉到dao層;
  7. 後期擇機將一些相對獨立的模塊抽取出來形成特定的服務;
  8. 不再以war包的形式部署,整包部署風險太高,降低粒度到class文件,採用Git版本控制增量部署、指定tag回滾;
  9. 因為是單體應用,目前還不需要鏈路跟蹤,但是類似於ELK的日誌收集、監控工具還是需要的;
  10. 寫一個alarm報警微服務,一旦產生error級別的日誌,或者產生RuntimeException則實時報警到釘釘群,誰讓我維護的是一個日活幾十萬、月流水過億的項目呢?

暫且就這些吧,後續根據項目的進展情況再逐步迭代。


分享到:


相關文章: