你的程式設計能力從什麼時候開始突飛猛進? 我的回答:2013年,我開始喜歡程式設計了。

勇哥程式設計遊記發表於2022-04-25

知乎上有一個熱門問題:你的程式設計能力從什麼時候開始突飛猛進

初看到這個問題,我的嘴角微微上揚。記憶閃回到了2013年,那一年,命運給我了一點點正反饋,我有點喜歡程式設計了。

這篇文章,我想和大家聊聊勇哥讀書,看原始碼,重構,解決線上問題的那些事。

1. 初心

2011年,我服務於一家網際網路彩票公司。坦率的講,選擇程式設計師這個職業,僅僅是為了生存。

那個時候,我對快取訊息佇列分散式JVM 一知半解 ,背了一些八股文,只是能非常熟練的使用 ibatis ,velocity ,編寫簡單的業務程式碼 。

我負責的是使用者中心繫統,提供使用者註冊,查詢,修改等基礎功能。所有的服務都以 HTTP 介面形式提供,資料傳輸格式是 XML 。

雖然工作看起來簡單,我那個時候也不懂設計模式,寫的業務程式碼非常臃腫,難以維護。

也發生了我的人生第一次重大 BUG ,我負責的使用者中心在上線後隔一段時間變會記憶體溢位。我站在運維同學那裡,看著他調整 tomcat jvm 引數 ,不知所措 。

後來發現我在使用 ibatis 的時候 ,使用類似的 SQLMap,前端又沒有驗證,資料庫執行了全表查詢,從而導致 JVM OOM 。

select
* 
from t_lottery_user t 
where 1 = 1
<isNotEmpty prepend="AND" property="userName">  
  user_name = #userName#
</isNotEmpty>  
<isNotEmpty prepend="AND" property="id">  
  id = #id#
</isNotEmpty> 
....

這次生產環境事故之後,內心一直有一個聲音折磨著我:“ 遇到技術問題的時候,能不能從容一點,不慌亂。其他人可以做到的事,為什麼我做不到。

於是我開始瘋狂的買書讀書 ,畢玄老師的《分散式Java應用》這本書對我影響至深。通過這本書,我深入學習了 JVM 記憶體模型 ,核心集合類原理, 併發包等知識點, 特別是 jstack , jmap 等 JVM 命令,邊看書,邊動手實踐。

同時為了擴充套件視野,我在 javaeye 和 開源中國兩大社群裡面瘋狂找熱門帖子學習,一個帖子上下文都我都會反反覆覆的看幾十遍以上 。

也時不時找運維同學,或者 DBA 聊,因為和他們聊,可以從另外一個視角審視公司系統存在的問題,他們的一句話有時候可以給我一些靈感。

2. 第一次重構

經過2011-2012兩年的學習,2013年彩票業務迎來了小爆發,我也迎來了技術人生第一次重構

算獎服務是非常核心的服務,算獎服務包含若干子服務,其中競彩算獎是用 C# 版本開發的系統。原來彩票訂單量少的時候,算獎服務還算穩定,但一旦量級增大,C#版算獎服務就會 hang 住,算獎時間從半個小時變成兩三小時,嚴重影響訂單的返獎。

我當時滿腦子都是想著去爭一口氣,去證明自己已不是兩年前的弱雞,於是主動請纓重構算獎系統。但領導說實話也是半信半疑,當時情況比較緊急,於是也就同意了。

技術團隊比較草莽,沒有統一的基礎框架,每次新建專案都是按照研發的喜好搭架子。因為原來有接觸過京東的基礎框架,於是我參考京東框架第一次搭建自己的作品。

算獎整體邏輯比較簡單,服務接收到競彩賽事編號,查詢彩票子訂單,通過賽事結果判斷彩票是否中獎,並修改子訂單中獎資訊,最後傳送中獎資訊到訊息佇列,最後排程中心來返獎。

至今都還記得開始寫程式碼是雙休日,兩天沒日沒夜不知疲倦的編碼,指尖敲擊鍵盤,程式碼顯示在螢幕上,行雲流水,這種感覺美好而又奇妙

兩天就重構完了,怎麼驗證正確 ?在測試環境簡單跑了一遍,發現沒有任何問題。領導也覺得不可思議,但這個就能上線嗎 ?我心裡面也直打鼓,每天的競彩算獎涉及到大幾十萬人民幣,要是算錯了,那影響也很大,我也要承擔相應的責任。

領導也沒有給我指導建議,於是我突發奇想:"生產環境不是有一兩年的訂單算獎歷史資料嗎 ?重構版本計算的結果和生產環境計算的結果做個對比,不就可以驗證正確率嗎?"。

於是,我將程式碼做了一些微調,將最後對資料的寫操作去掉,對比重構版本計算的金額和 c# 版本計算的金額,若金額有差異,訂單資料寫入到文字中,傳送郵件告警。

讓我驚喜的是:在近千萬的歷史訂單裡,重構版本的計算結果非常精準,只出現了兩例計算異常,並且計算速度非常快(快接近10倍)。修復完 BUG 後,和 C# 版本並行執行二十天左右後,計算結果都精準無誤。於是,領導同意了下線老系統 ,上線算獎重構版本。

這一次成功重構帶給我自信:在這個行業,我是可以生存下去的。

技術層面:我自己搭建了專案,接觸了訊息佇列做為訊息匯流排的架構模式,也認識到應用基礎框架的重要性。同時,我也隱約覺得:“程式碼寫出來是相對容易的,驗證程式碼的正確性同樣非常考驗工程能力 ”。

3. 閱讀原始碼

2013年,是我閱讀原始碼的起點 , 閱讀了 Druid , Cobar , Xmemcached 等原始碼。

3.1 資料來源連線池 Druid

算獎系統重構之後,有一個小插曲。我發現每天的第一次請求,資料庫連線有問題,於是我向 Druid 的作者溫少寫了一封郵件。

溫少給我回復了郵件,我馬上翻開原始碼,發現我配置資料庫連線池的心跳有問題。核心點在於需要連線池每隔一段時間傳送心跳包到 oracle 伺服器,因為資料庫為了節省資源, 每隔一段時間會關閉掉長期沒有讀寫的連線。所以客戶端必須每隔一段時間傳送心跳包到服務端。

這次簡單的探尋原始碼給了我長久的激勵,也讓我更加關注技術背後的原理。

  • 精神層面:向別人請教問題是會上癮的;
  • 技能層面:理解連線池的實現原理;
  • 架構層面:客戶端和服務端請求需要考慮心跳。

3.2 資料庫中介軟體 Cobar

還是在2013年,接觸到 cobar 帶給我的震撼簡直無以復加。

當時網際網路大潮奔湧而來 , 各大網際網路公司的資料爆炸般的增長 , 我曾在 javaeye 上看到淘寶訂單技術人員分享分庫分表的帖子 , 如獲至寶 , 想從字裡行間探尋分庫分表的解決方案 , 可惜受限於篇幅 , 文字總歸是文字 , 總感覺隔靴搔癢。沒曾想到 , cobar開源了, 我至今都還記得用 navicat 配置 cobar 的資訊,就可用像連單個 mysql 一樣,而且資料會均勻的分佈到多個資料庫中 ,這簡直像魔法一樣。對於我當時孱弱的技術思維來講 , 簡直就像是三體裡水滴遇到人類艦隊般 ,降維打擊 。

因為對分庫分表原理的渴求 , 我沒有好的學習方法 , 大約花了3個月的時間,我把整個 cobar 的核心程式碼抄了一次。真的是智商不夠 , 體力來湊。但光有體力是真的不夠的,經常會陷入懷疑,怎麼這也看不懂,那也看不懂。邊抄程式碼邊學習好像進步得沒有那麼明顯。那好 , 總得找一個突破口吧。網路通訊是非常重要的一環。

當時的我做了一個決定,我要把 cobar 的網路通訊層剝離出來 , 去深刻理解使用 原生 nio 實現通訊的模式。剝離的過程同樣很痛苦 , 但我有目標了 , 不至於像沒頭的蒼蠅,後來也就有了人生第一個 github 專案。

追 cobar 的過程中, 好像我和阿里的大牛面對面交流,雖然我資質駑鈍, 但這位大牛諄諄教誨 , 對我耐心解答, 打通我的任督二脈。由是感激。說是我生命中最重要的開源專案也不為過。

你想學啊?我教你"。

4. 實戰:知行合一

2013年下半年,我參與了很多系統的重構,解決了很多生產環境的問題。我經常思考:怎麼將我學到東西用到真實場景中,也做了很多有趣的嘗試。

4.1 多級快取

彩票系統裡有一個賽事分析服務,頁面非常複雜,採取的方案是 nginx 頁面靜態化的策略,通過定時任務每天將賽事分析資料寫入到 磁碟中 NFS 共享目錄 ,配置 Nginx 訪問。

nginx 頁面靜態化的優缺點都非常明顯。

  1. 靜態頁面訪問速度極快,效能非常好;
  2. 維護成本高,定時任務定期生成相關的頁面,更新經常延遲,而且程式碼是N年前的古董,是用 Php寫的。

領導決定用 Java 重構,核心的指標是:效能。

看過紅薯哥寫了一篇文章:oschina 上的一種雙快取思路。

https://www.oschina.net/question/12_26514

我大膽的採用了 Ehcache + memcached 的雙快取架構。重構過程也很順利,上線後效能也還不錯,維護起來也相對簡單。

4.2 救火隊員

彩票系統的業務量增長極快, 生產環境經常遇到一些莫名其妙的問題 。每次遇到問題, 我把每個問題當作我自己的問題,全力以赴的去解決,變成了救火隊員。

發生了很多的故事,舉兩個例子。

▍ 排程中心消費不了

某一天雙色球投注截止,排程中心無法從訊息佇列中消費資料。訊息匯流排處於只能發,不能收的狀態下。 整個技術團隊都處於極度的焦慮狀態,“要是出不了票,那可是幾百萬的損失呀,要是使用者中了兩個雙色球?那可是千萬呀”。大家急得像熱鍋上的螞蟻。

這也是整個技術團隊第一次遇到消費堆積的情況,大家都沒有經驗。

首先想到的是多部署幾臺排程中心服務,部署完成之後,排程中心消費了幾千條訊息後還是Hang住了。 這時,架構師只能採用重啟的策略。你沒有看錯,就是重啟大法。說起來真的很慚愧,但當時真的只能採用這種方式。

排程中心重啟後,消費了一兩萬後又Hang住了。只能又重啟一次。來來回回持續20多次,像擠牙膏一樣。而且隨著出票截止時間的臨近,這種思想上的緊張和恐懼感更加強烈。終於,通過1小時的手工不斷重啟,訊息終於消費完了。

我當時正好在讀畢玄老師的《分散式java應用基礎與實踐》,猜想是不是執行緒阻塞了,於是我用 Jstack 命令檢視堆疊情況。 果然不出所料,執行緒都阻塞在提交資料的方法上。

我們馬上和 DBA 溝通,發現 oracle 資料庫執行了非常多的大事務,每次大的事務執行都需要30分鐘以上,導致排程中心的排程出票執行緒阻塞了。

技術部後來採取瞭如下的方案規避堆積問題:

  1. 生產者傳送訊息的時候,將超大的訊息拆分成多批次的訊息,減少排程中心執行大事務的機率;
  2. 資料來源配置引數,假如事務執行超過一定時長,自動拋異常,回滾。

▍比分直播頁面卡頓

同事開發了比分直播的系統,所有的請求都是從快取中獲取後直接響應。常規情況下,從快取中查詢資料非常快,但線上使用者稍微多一點,整個系統就會特別卡。

通過 jstat 命令發現 GC 頻率極高,幾次請求就將新生代佔滿了,而且 CPU 的消耗都在 GC 執行緒上。初步判斷是快取值過大導致的,果不其然,快取大小在300k 到500k左右。

解決過程還比較波折,分為兩個步驟:

  1. 修改新生代大小,從原來的 2G 修改成 4G,並精簡快取資料大小 (從平均 300k 左右降為 80k 左右);
  2. 把快取拆成兩個部分,第一部分是全量資料,第二部分是增量資料(資料量很小)。頁面第一次請求拉取全量資料,當比分有變化的時候,通過 websocket 推送增量資料。

經過這次優化,我理解到:快取雖然可以提升整體速度,但是在高併發場景下,快取物件大小依然是需要關注的點,稍不留神就會產生事故。另外我們也需要合理地控制讀取策略,最大程度減少 GC 的頻率 , 從而提升整體效能。

5. 寫到最後

我特別喜歡畢淑敏關於命運的解釋:

漸漸地,我終於發現,命運是我怯懦時的盾牌,每當我叫嚷命運不公最響的時候,正式我預備逃遁的前奏。命運就像是一隻筐,我把自己對自己的姑息,原諒以及所有的懶惰都一股腦兒地塞進去然後蒙上一塊宿命的輕紗,我揹著它慢慢往前走,心裡有一份自欺欺人的坦然。

當我選擇程式設計師這個職業,最開始是為了生存,我並不聰明,學什麼都很慢,但我偏偏想證明自己,只能不斷去學習,命運貌似給我一丟丟回饋,讓我有了一點點滿足感。

於是,2013年,那個青年找到了屬於他的世界。

喜歡一個人,眼神是藏不住的,喜歡程式設計,眼神同樣是藏不住的。

相關文章