當前位置: 妍妍網 > 碼農

不好意思,Fastjson 真的該換了!

2024-05-18碼農

點選「 IT碼徒 」, 關註,置頂 公眾號

每日技術幹貨,第一時間送達!

FastJson 在國內的熱度還是挺高的,受到了很多開發者的喜歡。不過,我自己倒沒有在計畫中用過。我記得剛工作那會新做的一個計畫有明確規定禁止使用 FastJson。

昨天看到一篇關於 FastJson 的文章,這位 朋友分享了自己在使用 FastJson 遇到的一些問題。

下面是正文。

  • 記者:大爺您有什麽特長呀?

  • FastJson:我很快。

  • 記者:23423 乘以 4534 等於多少?

  • FastJson:等於 2343.

  • 記者:??

  • FastJson:你就說快不快吧!

  • 這個略顯馬麗蘇的標題,各位看官將就著看吧。主要是怕被噴。FastJson 真的很好,我用不用我喜不喜歡的,太不重要了,我只是覺得不適合我而已。

    話說以前 Gson 用得好好的,同事極力推薦我使用 FastJson,說很快雲雲。盡管我們的系統根本感知不出來這點速度差異。

    之前也聽說 FastJson 爆出來什麽重大漏洞,但對我們基本沒什麽影響,所以這一點倒是沒什麽偏見。

    然後在一個新計畫上,腦抽抽,把 Gson 換成了 FastJson,Spring Boot 預設支持的 Jackson 換成了 FastJson。

    然後就開始遇到了一些問題。先聲明,這真不是尬黑,為了文章效果,故意網上扒些黑料拼湊起來,本文所提到的問題,都來源於本人最近計畫的真實經歷。

    dateformat 優先級

    本來是一個風和日麗的下午,一個非常簡單的改動需求。介面返回的時間只需要年月日日期型別不需要分時秒。因為我配置全域時間格式化為 yyyy-MM-dd HH:mmss ,於是我愉快的在 javabean 的內容上加了個註解。

    本地測試一下,沒問題,送出到測試環境,搞定,完美。

    然後就接到產品的疑問,改動呢?

    我登上去看了一下,唉,沒改到啊,日期還是帶了分時秒。我大意了啊,這麽小的改動,又是在測試環境,就沒加驗證。

    那麽現在的直接問題是:FastJson 關於時間配置在局部的配置沒有生效,使用的還是全域配置。

    現象是,開發環境 Windows 上沒有問題,測試環境 Linux 上出現了問題。兩者有什麽區別呢?系統問題?

    既然懷疑是兩個系統導致的問題,那麽就在 idea 裏模擬一下 linux 系統。在 VM options 添加 -Dos.name=linux

    這不能完全模擬 linux 系統,只針對透過 System.getproperty("os.name") 來判斷當前系統做某些操作的時候有用。

    透過這種方式沒復現,我又想到了遠端偵錯。

    一陣操作猛如虎,遠端偵錯倒是能進斷點,只是斷點進不了第三方 jar 包的源碼。等於白搞。

    得,還是回到源碼吧。拉下源碼,斷點,觀察 JSONSerializer 類,主要是 writeWithFormat 方法。沒有發現問題。

    因為懷疑是系統導致的,在源碼中搜尋'linux''unix'關鍵字,沒有發現。斷點整個流程重點觀察了一下這部份也沒有發現問題。

    突然在 JSONSerializer.dateFormatPattern 上發現了這段註釋。

    這部份涉及到了調整 dateformat 的問題,重點在這個 #1868 ,這通常是 github 的問題編號。

    1.對於開源計畫來說,解決了 BUG,通常會把問題編號放到註釋裏面去。前提是註釋有必要。透過問題編號可以看到問題的前因後果。

    2.通常來說,對於 github 開源計畫都有 issue 區,拿著這個到編號直接到 issue 一搜就能搜到。

    3.但也有一些項級計畫,如 spark,flink 是沒有 issue 區的,它們的型別問題發現描述追蹤都使用 jira 平台。如: https://issues.apache.org/jira/browse/SPARK-38349

    在送出 PR 的時候標題也嚴格按照 [jira 編號][spark 子模組(如core/sql) title] 的規則來。

    所以拿著這個編號到 issue 區,不管有沒有 issue 區,也都可以直接到 pullrequest 區直接搜尋,就算 PR 標題裏沒有問題編號,PR 描述肯定也是有的,只要是有嚴格 PR 流程的開源計畫。

    所以這個問題在這裏: https://github.com/alibaba/fastjson/issues/1868

    相應的 PR 在這裏: https://github.com/alibaba/fastjson/pull/2706

    透過 ISSUES 描述的已知資訊,可以看出他遇到的問題跟我是一樣的,而這個問題早在 2018 年就提出了。但問題描述不太專業,沒有涉及到環境以及最重要的 FastJson 的版本問題。

    而透過 PR 可知,這個問題最終在 2020 才解決,期間僅在 ISSUES 區提出的相同問題就有 #1868 #1968 #2029 #2452 4 個。

    解決問題的版本為:1.2.72.

    這個資訊很關鍵。我對照了我開發環境的版本,是高於 1.2.72 的,所以沒有出現測試環境的問題。

    所以,柯南告訴我們,排除了所有可能性,剩下的哪怕再可笑,也是最終問題所在。

    那就是,測試環境所用的 FastJson 版本是低於 1.2.72 的。

    這種可能性是存在的,因為我們用的是 maven 打程式碼包,依賴包單獨存在。

    我最終在測試環境的依賴包目錄下發現了兩個 Fastjson 包,果然不出所料,有一個 1.2.53 的低版本,它就是罪魁禍首。

    所以,最終這個問題有相當大的程度是由於我們團隊自身問題引發的。但透過解決這個問題的過程也發現了一些有意思的情況。

    首先,FastJson 在某一個版本為什麽會引發這個問題。它肯定是某個 PR 改出問題的,rv,testcase 覆蓋沒有到位。

    其次,從試圖解決這個問題的 3 個 PR 的時間線,分別在 2018 年,2019 年,2020 年。說明, FastJson 這個計畫的 contributor 看起來有百來人,但其中過於依賴其中某 1 個或者某些主力人員。精力有限,某些優先級不那麽高的 BUG 只能放任。

    同時這個計畫的榮譽感並沒有那麽高(或者叫並沒有那麽吸引高手),它並不是 Apache 頂級計畫,要是其它諸如 Spark、Flink、Spring,哪怕是 Dubbo 呢,很想象這些計畫會有一個並不算復雜的 BUG 懸而未決長達 3 年時間。在這些頂級開源計畫,大家都是拼了老命的想找些 BUG 來送出 PR。

    當然,以上只是我個人的一點猜測。

    復盤,遇到 FastJson 的問題,一開始就應該奔著 github 的 issues 區,它大機率已經被前人踩坑了。

    $ref 迴圈參照問題

    以上測試介面返回前端什麽?

    我現在並不知道什麽迴圈參照檢測,這時候它是我的知識盲區。此時,我觀察到的現象是, young children 兩個 list 物件中均參照指向了 王麻子 這個物件。然後,在第 2 次 children 參照的時候它在序列化的時候直接指向了第 1 個 young 裏相應物件參照。當然遇到這個問題的時候,我在仔細觀察排除了非 fastjson 的問題以後,這次我學聰明了,我直接來到了 Github 的 issues 區,搜尋 $ref

    果然有很多同道中人,近 150 個問題,從時間上來看還挺新鮮。我點選了 closed,既然關閉了,那肯定解決了吧。

    我點進了 closed 區第一個問題,然後作者讓升級到 fastjson2。???

    如果我沒有理解錯,FastJson 和 FastJson2 可不是兩個版本的區別,是兩個計畫也!據說 API 也有相容性問題。直接這樣升級過去,談何容易!

    我覺得這也是個槽點,FastJson 好像並沒有一個穩定維護的版本,遇到問題總是在升級,升級的過程中也沒做好品質控制,又引入了新的問題。

    還是在當前計畫尋求解決方法吧,哪怕升版本也好啊。終於在另一個問題下面找到了問題所在以及解決方案。

    https://github.com/alibaba/fastjson/issues/3643

    我現在知道這是由於迴圈參照檢測引起的。透過設定 SerializerFeature.DisableCircularReferenceDetect 可以避免這個問題。

    但是,我的程式碼其實並沒有迴圈參照啊,只是兩個子物件參照了同一個物件而已。這算什麽?誤傷嗎?

    更重要的,一些控制權應該在使用者手裏?

    比如,當前這個迴圈參照在序列化會出的問題,應該是使用者手動去開啟,而不是預設給使用者開啟。在優先級上,全域應該關閉,在有迴圈參照的地方,讓使用者選擇局部開啟。

    現在我的前端並沒有使用 FastJson,面對 "$ref":"$.result.young[1]" 這種文本,它能解析嗎?它不能呀。

    我測試了一下,好像使用 FastJson 也並不能解析回來:

    :經提醒,這裏應使用完整報文解析,經測試,確實可以。感謝提醒!

    更可怕的問題是,剛好在測試環節有兩個子物件參照了同一個物件,被我提前發現了。如果測試環境沒有這樣的情況,在生產環境剛好遇到了呢?那就是生產事故了呀。

    本來是一個挺好的設計點,能起到錦上添花的作用,但它卻可能暴雷,這是好心辦壞事。

    同樣的,還有 SerializerFeature.WriteMapNullValue 。如果一個欄位值為 null ,fastjson 預設就不返回該欄位了。本來前後端約定好,如果為 null 就怎樣處理的邏輯,可能在生產環境中突然暴雷啊。

    就像 WriteNullListAsEmpty 就很好,不錯的設計點,如果返回的 list 為 null 的時候,使用者可以選擇讓它序列化為 [] ,但它也不是預設開啟的呀,給了使用者額外的選擇權,對吧。

    總結

    寫到這裏的時候,我是真心覺得 FastJson 有比競品有些特色的地方。這真不是為了所謂的客觀公正,非要負面寫多點,再搞點正面的。

    為了寫文章,那肯定要去試驗,得把競品也拿出來測試一下,一測試發現並不是 FastJson 獨有的,尷尬!

    但我還是那句話,不管你信不信,對於開源計畫,特別是這樣一個廣泛使用的開源計畫,肯定有非常值得學習的地方。一個開源計畫,如果整天拿著顯微鏡去觀察,那肯定能找出不少毛病。

    這裏稍微總結一下本文的資訊點。並不一定是某個具體 BUG,而是透過這個 BUG,解決這個 BUG 背後所展現出來的 FastJson 的資訊或趨勢。

    1. review,testcase 覆蓋不是很到位

    2. contributor 看起來很多,但嚴重依賴主力人員。而主力精力有限,某些優先級不那麽高的 BUG 只能放任。

    3. 這個計畫的榮譽感並沒有那麽高,或者叫並沒有那麽吸引高手)。

    4. 有些功能點應該把控制主動權交給使用者,如 DisableCircularReferenceDetect,WriteMapNullValue 等。預設開啟非常容易導致線上暴雷。

    5. 作者已經全面轉向 fastjosn2,而且哪怕在這之前,對於 fastjson 沒有一個穩定維護的版本,不斷升級,不斷引入新問題。

    祝願 fastjson2 越來越好,不要步 struts2 的後塵。

    來源:juejin.cn/post/7215886869199863869

    END

    PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。

    往期推薦