一行 log 日誌,引發 P1 級線上事故!

来源:https://www.cnblogs.com/javastack/archive/2023/08/15/17631326.html
-Advertisement-
Play Games

作者:老鷹湯 \ 鏈接:https://juejin.cn/post/7156439842958606349 ## 線上事故回顧 前段時間新增一個特別簡單的功能,晚上上線前`review`代碼時想到公司拼搏進取的價值觀臨時加一行log日誌,覺得就一行簡單的日誌基本上沒啥問題,結果剛上完線後一堆報警, ...


作者:老鷹湯
鏈接:https://juejin.cn/post/7156439842958606349

線上事故回顧

前段時間新增一個特別簡單的功能,晚上上線前review代碼時想到公司拼搏進取的價值觀臨時加一行log日誌,覺得就一行簡單的日誌基本上沒啥問題,結果剛上完線後一堆報警,趕緊回滾了代碼,找到問題刪除了添加日誌的代碼,重新上線完畢。

情景還原

定義了一個 CountryDTO

public class CountryDTO {
    private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return this.country;
    }

    public Boolean isChinaName() {
        return this.country.equals("中國");
    }
}

定義測試類 FastJonTest

public class FastJonTest {
    @Test
    public void testSerialize() {
        CountryDTO countryDTO = new CountryDTO();
        String str = JSON.toJSONString(countryDTO);
        System.out.println(str);
    }
}

運行時報空指針錯誤:

通過報錯信息可以看出來是 序列化的過程中執行了 isChinaName()方法,這時候this.country變數為空, 那麼問題來了:

  • 序列化為什麼會執行isChinaName()呢?
  • 引申一下,序列化過程中會執行那些方法呢?

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

源碼分析

通過debug觀察調用鏈路的堆棧信息

調用鏈中的ASMSerializer_1_CountryDTO.writeFastJson使用asm技術動態生成了一個類ASMSerializer_1_CountryDTO,

asm技術其中一項使用場景就是通過到動態生成類用來代替java反射,從而避免重覆執行時的反射開銷

JavaBeanSerizlier序列化原理

通過下圖看出序列化的過程中,主要是調用JavaBeanSerializer類的write()方法。

JavaBeanSerializer 主要是通過 getObjectWriter()方法獲取,通過對getObjectWriter()執行過程的調試,找到比較關鍵的com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer方法,進而找到 com.alibaba.fastjson.util.TypeUtils#computeGetters

public static List<FieldInfo> computeGetters(Class<?> clazz, //
                                                 JSONType jsonType, //
                                                 Map<String,String> aliasMap, //
                                                 Map<String,Field> fieldCacheMap, //
                                                 boolean sorted, //
                                                 PropertyNamingStrategy propertyNamingStrategy //
    ){
        //省略部分代碼....
        Method[] methods = clazz.getMethods();
        for(Method method : methods){
            //省略部分代碼...
            if(method.getReturnType().equals(Void.TYPE)){
                continue;
            }
            if(method.getParameterTypes().length != 0){
                continue;
            }
        	//省略部分代碼...
            JSONField annotation = TypeUtils.getAnnotation(method, JSONField.class);
            //省略部分代碼...
            if(annotation != null){
                if(!annotation.serialize()){
                    continue;
                }
                if(annotation.name().length() != 0){
                    //省略部分代碼...
                }
            }
            if(methodName.startsWith("get")){
             //省略部分代碼...
            }
            if(methodName.startsWith("is")){
             //省略部分代碼...
            }
        }
}

從代碼中大致分為三種情況:

  • @JSONField(.serialize = false, name = "xxx")註解
  • getXxx() : get開頭的方法
  • isXxx():is開頭的方法

序列化流程圖

序列化.png

示例代碼

/**
 * case1: @JSONField(serialize = false)
 * case2: getXxx()返回值為void
 * case3: isXxx()返回值不等於布爾類型
 * case4: @JSONType(ignores = "xxx")
 */
@JSONType(ignores = "otherName")
public class CountryDTO {
    private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return this.country;
    }

    public static void queryCountryList() {
        System.out.println("queryCountryList()執行!!");
    }

    public Boolean isChinaName() {
        System.out.println("isChinaName()執行!!");
        return true;
    }

    public String getEnglishName() {
        System.out.println("getEnglishName()執行!!");
        return "lucy";
    }

    public String getOtherName() {
        System.out.println("getOtherName()執行!!");
        return "lucy";
    }

    /**
     * case1: @JSONField(serialize = false)
     */
    @JSONField(serialize = false)
    public String getEnglishName2() {
        System.out.println("getEnglishName2()執行!!");
        return "lucy";
    }

    /**
     * case2: getXxx()返回值為void
     */
    public void getEnglishName3() {
        System.out.println("getEnglishName3()執行!!");
    }

    /**
     * case3: isXxx()返回值不等於布爾類型
     */
    public String isChinaName2() {
        System.out.println("isChinaName2()執行!!");
        return "isChinaName2";
    }
}

運行結果為:

isChinaName()執行!!
getEnglishName()執行!!
{"chinaName":true,"englishName":"lucy"}

代碼規範

可以看出來序列化的規則還是很多的,比如有時需要關註返回值,有時需要關註參數個數,有時需要關註@JSONType註解,有時需要關註@JSONField註解;當一個事物的判別方式有多種的時候,由於團隊人員掌握知識點的程度不一樣,這個方差很容易導致代碼問題,所以儘量有一種推薦方案。 這裡推薦使用@JSONField(serialize = false)來顯式的標註方法不參與序列化,下麵是使用推薦方案後的代碼,是不是一眼就能看出來哪些方法不需要參與序列化了。

public class CountryDTO {
    private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return this.country;
    }

    @JSONField(serialize = false)
    public static void queryCountryList() {
        System.out.println("queryCountryList()執行!!");
    }

    public Boolean isChinaName() {
        System.out.println("isChinaName()執行!!");
        return true;
    }

    public String getEnglishName() {
        System.out.println("getEnglishName()執行!!");
        return "lucy";
    }

    @JSONField(serialize = false)
    public String getOtherName() {
        System.out.println("getOtherName()執行!!");
        return "lucy";
    }

    @JSONField(serialize = false)
    public String getEnglishName2() {
        System.out.println("getEnglishName2()執行!!");
        return "lucy";
    }

    @JSONField(serialize = false)
    public void getEnglishName3() {
        System.out.println("getEnglishName3()執行!!");
    }

    @JSONField(serialize = false)
    public String isChinaName2() {
        System.out.println("isChinaName2()執行!!");
        return "isChinaName2";
    }
}

三個頻率高的序列化的情況

以上流程基本遵循 發現問題 --> 原理分析 --> 解決問題 --> 升華(編程規範)。

  • 圍繞業務上:解決問題 -> 如何選擇一種好的額解決方案 -> 好的解決方式如何擴展n個系統應用;
  • 圍繞技術上:解決單個問題,順著單個問題掌握這條線上的原理。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [TOC] ## 類型的基本歸類 **整形家族:** ```c char unsigned char signed char short unsigned short [int] signed short [int] int unsigned int signed int long unsigned ...
  • 在Python中,列表(list)是一種有序、可變的數據結構,用於存儲多個元素。列表可以包含不同類型的元素,包括整數、浮點數、字元串等。實際上列表有點類似C++語言中的數組,但僅僅只是類似,和數組還是有點不一樣的。列表非常適合利用順序和位置定位某一元素,尤其是當元素的順序或內容經常發生改變時。 在P ...
  • # 1. 回顧 > 1. java實現多線程: [1]繼承Thread類並重寫run方法 [2]實現Runnable介面 > > 2. 線程Thread中常用的方法: setName(): Thread.currentThread().getName(): > > ​ static void sle ...
  • [TOC] # 簡介 ImGui 是一個用於C++的用戶界面庫,跨平臺、無依賴,支持OpenGL、DirectX等多種渲染API,是一種即時UI(Immediate Mode User Interface)庫,保留模式與即時模式的區別參考[**保留模式與即時模式**](https://learn.m ...
  • 利用AI幫你讀文章、利用AI幫你分析非結構化數據,這些最為潮流的AI輔助工具,相信很多讀者都在各種媒體上看到過了。但還是有不少人並沒有真正的使用過,這裡有很多原因導致,具體就不細說了,懂的都懂。 今天TJ就給大家推薦一個你可以線上使用,也可以自己搭建的AI輔助工具:[**Quivr**](https ...
  • Sun公司以及其他虛擬機提供商發佈了許多可以運行在各種不同平臺上的虛擬機,這些虛擬機都可以載入和執行同一種平臺無關的的程式存儲格式——位元組碼(ByteCode),從而實現了程式的“一次編寫,到處運行”。“Class文件”這種特定的二進位文件格式所關聯,Class文件中包含了Java虛擬機指令集和符號... ...
  • [TOC] ## 1. 我以為 我以為 [GoPool](https://github.com/devchat-ai/gopool) 這個項目會曇花一現,從此在 GitHub 上封塵。 > 關於 GoPool 項目誕生的故事:[《僅三天,我用 GPT-4 生成了性能全網第一的 Golang Work ...
  • 最近小組在開展讀書角活動,我們小組選的是《深入理解JVM虛擬機》,相信這本書對於各位程式猿們都不陌生,我也是之前在學校準備面試期間大致讀過一遍,emm時隔多日,對裡面的知識也就模糊了。這次開始的時候從前面的JDK發展史和JVM虛擬機家族著手,之前都是粗略讀過,這次通過查閱相關資料並收集在每一個JDK... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...