甲小蛙戰記:PHP2Java 排雷指南


(馬蜂窩技術原創內容,申請轉載請在公眾后后台留言,ID:mfwtech )

大家好,我是來自馬蜂窩電商旅游平台的甲小蛙,從前是一名 PHP 工程師,現在可能是一名 PHJ 工程師,以后......

前陣子,我從大道消息聽說公司商品訂單技術棧要推 Java。我是一個喜歡走在時代前列線上的人,凡是要做到領先。我對 Java 也是仰慕已久,於是花了兩天時間學習 Java,並調研各種框架和解決方案,決心要把商品和訂單的主要功能用 Java 重構掉。

在經歷了 798 難后現在這些東西都踉蹌上線了,我也成了馬蜂窩的頂梁柱。雖然表面看來風光無限,但是這一路走來相當不容易,累到有上覺沒下覺,踩坑把腿踩斷,才有了今天這篇戰記。希望大家看完后不要吸取任何教訓,抱着不撞南牆不回頭的心態,繼續從頭踩坑。

風險提示:文章會先帶大家入坑,然后出坑,請保持秩序不要擁擠;如果文章看了一半就去實踐,有被隊友打死的風險!

 

Part.1 准備篇  

終於要開始學習了!

9 月 1 號,趁着開學季,買了《兩天精通 Java》、《三天精通 SpringBoot》兩本書,看到書名仿佛感覺勝利在向我招手。

9 月 2 號書就到了,兩天沒睡覺把書看完了。原來 Java 這么簡單,也是各種 class,interface,abstract class。Java 還有一個響亮的口號——「萬事萬物皆對象」。

OK,可以開始編程了。隔壁甲小白湊過來說:「IntelliJ 寫 Java 很爽,買一個吧,才 1000+ RMB~」。

…… 

咬咬牙!

環境搞好了,來,寫個 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 項目進行構建、依賴管理】

到此,我已經順利完成學業,准備出師開始搬磚了!

 

Part.2 實戰挖坑篇

環境和項目已經搭建好,要開始寫邏輯部分了。

首先我有這么個需求:保存商品的時候流程走的比較多,很多流程都要用到該商品的基本信息,但我又不想通過一層層 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 包的注解】

好了,遇到的問題基本都解決了,自測通過。提測。測試環境也部好了!

 

Part.3 邊挖邊填篇

甲小美同學開始測試了。我跟她講解了目前的服務調用情況是這樣的: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......」

 

Part.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,沒有反饋問題,結束了!

當馬蜂窩的頂梁柱真是不容易啊!

本文作者:馬蜂窩旅游網旅游平台研發團隊。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM