(馬蜂窩技術原創內容,申請轉載請在公衆後後臺留言,ID:mfwtech )前端
你們好,我是來自馬蜂窩電商旅遊平臺的甲小蛙,從前是一名 PHP 工程師,如今多是一名 PHJ 工程師,之後......數據庫
前陣子,我從大道消息據說公司商品訂單技術棧要推 Java。我是一個喜歡走在時代前列線上的人,凡是要作到領先。我對 Java 也是仰慕已久,因而花了兩天時間學習 Java,並調研各類框架和解決方案,決心要把商品和訂單的主要功能用 Java 重構掉。編程
在經歷了 798 難後如今這些東西都踉蹌上線了,我也成了馬蜂窩的頂樑柱。雖然表面看來風光無限,可是這一路走來至關不容易,累到有上覺沒下覺,踩坑把腿踩斷,纔有了今天這篇戰記。但願你們看完後不要吸收任何教訓,抱着不撞南牆不回頭的心態,繼續從頭踩坑。json
風險提示:文章會先帶你們入坑,而後出坑,請保持秩序不要擁擠;若是文章看了一半就去實踐,有被隊友打死的風險!api
終於要開始學習了!瀏覽器
9 月 1 號,趁着開學季,買了《兩天精通 Java》、《三天精通 SpringBoot》兩本書,看到書名彷彿感受勝利在向我招手。緩存
9 月 2 號書就到了,兩天沒睡覺把書看完了。原來 Java 這麼簡單,也是各類 class,interface,abstract class。Java 還有一個響亮的口號——「萬事萬物皆對象」。session
OK,能夠開始編程了。隔壁甲小白湊過來講:「IntelliJ 寫 Java 很爽,買一個吧,才 1000+ RMB~」。app
…… 框架
咬咬牙!
環境搞好了,來,寫個 Demo:
SpringBoot 果真名不虛傳,必定有一個很懂用戶的 PM,爲咱們省去了原來須要在 Spring 中配置的一堆 XML 文件,上手真的灰常簡單。比較奇怪的是 Java 竟然沒有 echo,想對 Java 世界說聲「你好」,竟然還得寫 System.out.println("哈嘍,窩的")。切!【劃重點:Spring Boot 是由 Pivotal 團隊提供的全新框架,其設計目的是用來簡化新 Spring 應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員再也不須要定義樣板化的配置。經過這種方式,Spring Boot 致力於在蓬勃發展的快速應用開發領域 (rapid application development) 成爲領導者】
感受 Java 對於無所不能的我來說真是太簡單了,就是有點繁瑣。萬事萬物皆對象,還建議把對象屬性設置爲 private。偶買嘎!但是對象屬性也太多了吧!商品對象有近 100 個屬性,你讓我給他們挨個寫 getter,setter 方法?別鬧了!
爲了顯得專業點,我開始給這 100 個屬性寫 getter、setter 方法。花了一個小時寫完了,可累死我了。一旁的甲小白看着個人代碼說,「你在刷代碼量麼,爲何不用 @Getter @Setter 註解?」這什麼東西?別跑!爲何不早說!【劃重點:@Getter @Setter 是 Lombok 中的兩個編譯期註解】
話說,自從用了這個叫作註解的東西,手指也不疼了,腰也不酸了,好評!
好了,看來 Java 和 SpringBoot 已經被我研究通透了。忽然想起來個事兒,商品要調用好多部門的 PHP 接口,但是他們沒有 Java 版的接口,這可腫麼辦!然而機智的隊友早已看穿一切,作了一個叫作 PHP 網關的東西,能夠把 PHP 接口所有包裝成 HTTP 請求。
萬事俱備只欠南風,其餘也都調研的差很少了,該動真格的了。試試鏈接數據庫吧。據說 Java 有個連庫的好東西,叫 Druid,整!【劃重點:Druid 是阿里巴巴開源平臺上一個數據庫鏈接池實現,它結合了 C3P0、DBCP、PROXOOL 等 DB 池的優勢,同時加入了日誌監控,能夠很好的監控 DB 池鏈接和 SQL 的執行狀況,能夠說是針對監控而生的 DB 鏈接池,聽說是目前最好的鏈接池】
調試沒問題,繼續。我要連線上數據庫了,可是沒有帳號密碼,跟 DBA 要吧,結果 DBA 給我扣了個盜取數據庫密碼「叛窩」的罪名。最後扔下一張卡片,「健身游泳瞭解下」的上面赫然寫着一行小字:SkipperClient。這是什麼東東,咱也不知道咱也不敢問,Gitlab 一下吧,原來用這個就能夠根據服務名換取對應的 DB 庫的帳號密碼,懂了(還有更方便的用法,能夠直接用微服務的配置中心+Spring 原生配置便可完成)。可是怎麼連接到咱們的內部 maven-repository 呢?找身邊的同窗 Copy 一份 Maven 的配置文件就好啦!【劃重點:Maven 是一個項目管理工具,能夠對 Java 項目進行構建、依賴管理】
到此,我已經順利完成學業,準備出師開始搬磚了!
環境和項目已經搭建好,要開始寫邏輯部分了。
首先我有這麼個需求:保存商品的時候流程走的比較多,不少流程都要用到該商品的基本信息,但我又不想經過一層層 set 值進行傳遞。So 來個單例吧,Spring 有一個強大的東西叫作容器,配合 IOC 能夠完美知足個人需求。【劃重點:@Service 把須要用來承載商品關鍵信息的類註冊爲 Bean,由 Spring 容器管理其生命週期;@Resource 註解下要進行注入的變量便可完成依賴注入,不再須要本身手動 set 了】
是的,就這麼幾行簡單的代碼就能夠知足個人需求了,跑起來。
咦,怎麼會有 NullPointerException 呢?一頓搜索以後才知道,原來想要使用這個 Bean,那使用這個 Bean 的類也要註冊在 Spring 容器中,由 Spring 建立才行……就沒別的辦法嗎?又一頓搜,找到了解決方案,能夠手動獲取 Spring 容器的上下文,終於讓我絕不費神,很是費力地完成了邏輯部分的開發。
由於項目用到了不少反射(跟 PHP 反射相似),因此我很機智地給每個可能跑異常的方法增長了 try catch 代碼塊,以體現我對異常的敏銳嗅覺,報出異常後能夠第一時間鎖定異常代碼,同時加上了給用戶的提示「服務出錯」。
好了,測試下,沒問題,能夠看到異常日誌。等等,好像哪裏不對勁,爲何最內層的報異常後,記錄了 4 條異常 log 呢?定睛一看,發現內層給用戶返回的異常提示又被外層捕獲了,致使屢次日誌記錄。嗯…我認可這個問題有點傻了~可是我確實是想記錄日誌並提示用戶,怎麼辦?請教了身邊的甲小白。小白一把把我推開,在個人青軸鍵盤上一頓動次打次後,說:改好了,只須要把異常拋出,讓最外層捕獲就行了。【劃重點:若是選擇了讓方法 throws Exception(指 Java 讓強制拋的,非自定義的 throw new XXXException),那調用該方法的方法只要不處理異常,就須要繼續 throws Exception,因此儘可能不要嵌套 100 層方法】
還有一個頭疼的問題,PHP 裏字段大多使用的是蛇形字段(goods_info),而 Java 裏好像更經常使用駝峯(goodsInfo)。我總不能讓蛇形字段類來接收參數,而後再轉成個人內部駝峯類吧。我本能地搜了一下,竟然真的有解決方案,只須要用 @JSONField @JsonProperty 註解就能夠搞定這個問題了。不得不說 Java 的生態仍是比較完善的,提供了各類問題經常使用的解決方案。【劃重點:@JSONFIeld @JsonProperty 是兩個運行時註解,前者是 阿里巴巴 的 fastjson 包的註解,後者是 jackson 包的註解】
好了,遇到的問題基本都解決了,自測經過。提測。測試環境也部好了!
甲小美同窗開始測試了。我跟她講解了目前的服務調用狀況是這樣的:PHP->Java->PHP(即 PHP 接收用戶端請求,而後封裝參數調用 Java 微服務,Java 微服務再調用一些 PHP 的接口進行數據校驗)。
剛開始測試,就遇到個小問題。因爲部署的 Java 微服務沒有部署爲上線狀態,而是「內測中」,該怎麼訪問呢?這個時候想到了咱們的瀏覽器插件。裝插件,選分支,搞定!可是測試同窗仍是說沒訪問到,怎麼辦?這時候想到了瀏覽器插件的工做原理,帶 Cookie。【劃重點:PHP 若是要訪問內測中 Java 微服務,PHP 中訪問 Java 微服務時必定要把 Cookie 攜帶上】
甲小美同窗埋頭測了很久,我心想,看來是基本沒什麼問題,明天上線!
「甲小蛙,爲何我新建立了一個商品,列表頁裏沒有新增呢,並且好像是把我剛纔建立好的商品給改掉啦!」
聽完趕忙把本地程序運行了起來,完美復現!瞬間背後一涼。我意識到多是 @Service 的問題,發現註解後的類全局單例,也就是無數個用戶會共享這一個對象。習慣了 PHP 進程內單例的我有些沒法接受,這可咋整,頓時有點懵,其餘同窗也是剛剛入坑,好像沒有好的辦法,怎麼辦…實力不夠,體力來湊,仍是乖乖改形成了一層層傳遞對象的方式。(這個方式一直被沿用到線上,後來才發現還有個叫作 @Scope 的註解,能夠控制 Bean 的做用域,一股悲涼襲上心頭)【劃重點:@Scope 能夠指定 4 中 SpringBean 的做用域,有:單例(singleton)、原型(prototype)、會話(session)、請求(request)】
剛修好沒多久,一樣的問題又來了,真是一 Bug 未平一 Bug 又起。切換到開發環境,獲取和保存了商品,咦,沒問題啊……
我:你是否是打開方式不對啊?
甲小美:打開方式確定沒問題!
只好把小美同窗拉到屏幕前,一次又一次得刷着連接:「你看,沒問題吧!你看,你看,你看,崩了吧......」
報錯信息:想不起來了。大體意思就是說鏈接無效,連接丟了。問了下 DBA,說咱們 MySQL 的連接空閒斷開時間是 30s,也就是說連接到數據庫後,30s 內並無再執行 SQL,這個連接就會被斷開。搜了解決方案,以下:【劃重點:setTestWhileIdle = true,是否在得到連接後檢測其可用性;setTestOnBorrow = true,是否在鏈接放回鏈接池後檢測其可用性】
果真好了,連接通暢,又能愉快地測試了。
但帥不過 3 分鐘...唉!
按照場景復現了下,發現基本都是一個問題,也是 PHP 轉 Java 比較頭疼的問題。因爲 PHP 的弱類型以及和沒有前端同窗約定好數據格式,致使前端能夠傳來各類各樣的數據類型。本來覺得的 Integer(如:10),前端能夠傳 Float(如:10.5);本來覺得的 Float(如:999.9),前端能夠傳 String(如:999.9 元/人)。總之,這是一個比較大的坑,仍是且行且珍惜啊! Java 應該是世界上最嚴謹的語言。【劃重點:使用 Java 的好處之一,是在設計數據格式時,可讓咱們的數據更加規範和嚴謹】
「爲何開發環境商品下線拋了事件,可是對應的行爲沒有執行呢?」我強忍住心中的痛楚,先是檢查了事件的監聽,又確認了 PHP 的訂閱確實被執行了,可是發現 PHP 調用 Java 返回了錯誤。
怎麼會 404?本地跑得好好的啊,明明有這個 Action,怎麼回事?首先,這個 Action 是 Java 微服務中新增的,404 意味着沒找到,有多是訪問到「已上線」的服務中去了,是否是沒有帶上 Cookie?帶着疑問去驗證,果真。(劃重點:消息總線訂閱者在訪問 Java 微服務時是不會攜帶 Cookie 的,默認會走「服務中」的服務;若是 Java 服務在此過程當中還須要訪問 PHP,還須要在 Java 微服務中指定要訪問哪一個 Docker,要否則會迷路的)
「爲何商品編輯後,詳情頁的內容沒有更新啊?」
「是否是更新詳情頁的事件沒執行啊,你多保存幾回,更新下數據」
「不行......」
好吧,來到最熟悉的 PHP 環境,各類 Debug,發現 PHP 裏好多接口都加了一層緩存,忽然間恍然大悟,在保存完商品後更新了下這個接口的緩存。(劃重點:默認狀況下 Ko 會在 aGet,aMultiGetList 等接口中增長一層 memcache 的主鍵緩存,若是用 Java 服務更新了數據,記得來清下 Ko 的主鍵緩存)
甲小美:「爲何1......」
甲小美:「爲何2......」
甲小美:「爲何3......」
終於熬到這一天,我自信地站在鏡子前,笨拙繫上紅色領帶的結,將頭髮梳成大人模樣,穿上一身帥氣西裝,等會兒上線必定比想像順~
「喂,小美,上線成功了,速度迴歸~」。天降大任於斯人也,必先苦其心志,勞其筋骨,餓其體膚,空乏其身...... 激動!痛快!爲了表達此刻的心情,我要用表情包建立一個商品,放滿了各類 Emoji,就是任性 !
我擦,怎麼返回服務異常了?
「小美,你是否是大流程沒覆蓋到啊?」
「你給我冷靜點~先看日誌」,「哦」。我以迅雷不及掩耳盜鈴之勢把流量開關給關掉了,線上又走回了 PHP 的流程。日誌顯示以下:【劃重點:對於一些大流程的改造,建議加上一個 switch 開關,方便線上出問題後立刻切流量,比修改代碼提 MR 再發布要高效和準確】
果真在線上也找到了解決方案,緣由是對應的數據表的編碼方式是 utf8,而 Emoji 存入數據庫的編碼是 utf8mb4,因此異常了【劃重點:MySQL 在 5.5.3 版本之後增長了 utf8mb4 編碼,其中 mb4 是 most bytes 4 的含義,用來兼容四個字節的 Unicode(萬國碼)。utf8mb4 是 utf8 的一個擴展,能夠給數據表換個編碼方式來解決這個問題】。可是爲何 PHP 能夠把 Emoji 存入 utf-8 的表中,而 Java 不能?這個問題還在困擾着我...... 最好的語言,名不虛傳。
1 個小時後......
2 個小時後......
4 個小時後......
8 個小時後......
Yeah,沒有反饋問題,結束了!
當馬蜂窩的頂樑柱真是不容易啊!
本文做者:馬蜂窩旅遊網旅遊平臺研發團隊。