<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Jackson 序列化的隱性成本

      我們常以為接口的瓶頸在數據庫或業務邏輯,但在高并發、海量請求下,真正吞噬 CPU 的,可能是“把對象變成 JSON”的那一步。當監控把序列化時間單獨拆出來,你會驚訝它能讓賬單失控。這篇《The Hidden Cost of Jackson Serialization》對我啟發很大:默認好用的 Jackson,在某些場景可能成為熱路徑的成本中心。下面順手分享給大家參考,以下內容翻譯整理自 《The Hidden Cost of Jackson Serialization》

      Jackson 很強大,直到你看到它真正讓你付出了什么代價。我們的 REST API 正在大把大把的花錢。每個 JSON 響應要消耗 3–5ms 的 CPU 時間。把它乘以每天 5000 萬次請求,你就會得到一張能讓 CTO 掉眼淚的 AWS 賬單。罪魁禍首?Jackson。Java 生態里最流行的 JSON 庫,那個大家幾乎不假思索就會用的默認選項。

      事情是怎么開始的?

      我們有一個標準的 Spring Boot 微服務,很普通。

      @RestController
      public class UserController {
          
          @GetMapping("/users/{id}")
          public User getUser(@PathVariable Long id) {
              return userService.findById(id);
          }
      }
      

      干凈、簡單,跟每篇 Spring Boot 教程教你的幾乎一樣。

      Spring Boot 默認用 Jackson 把 Java 對象轉換成 JSON。你不用配置什么,它就能工作。

      直到你看了指標數據。

      當頭棒喝

      我們的監控面板顯示出一些奇怪的東西:

      • 數據庫查詢時間:8ms
      • 業務邏輯:2ms
      • JSON 序列化:47ms

      等等,什么?

      實際工作只花了 10ms。把結果轉換成 JSON 花了 47ms。就像你做飯用了 2 分鐘,裝盤卻花了 10 分鐘。

      我以為是測量誤差,于是跑了一個 profiler。

      Method                          Time    Calls
      -------------------------------- ------- -------
      Jackson.writeValueAsString()     47ms    1
      UserService.findById()           8ms     1
      

      不是。Jackson 確實在每次請求里,用 47ms 序列化一個簡單的 User 對象。

      排查開始

      我抓起我們的 User 實體,看了看:

      @Entity
      public class User {
          private Long id;
          private String email;
          private String firstName;
          private String lastName;
          
          @OneToMany(fetch = FetchType.EAGER)
          private List<Order> orders;
          
          @OneToMany(fetch = FetchType.EAGER)
          private List<Address> addresses;
          
          @ManyToMany(fetch = FetchType.EAGER)
          private List<Role> roles;
      }
      

      哦。我們把整張對象圖都返回出去了。每個用戶對象附帶:

      • 50+ 個訂單(每個訂單還有行項目)
      • 3–4 個地址
      • 多個角色

      Jackson 在每次請求中序列化上千個對象。難怪它慢。

      但關鍵是:我們只需要用戶的基本信息。郵箱和姓名,僅此而已。

      “用 DTO 就好”的論調

      每個資深開發看到這,都會大喊:“用 DTO 啊!”

      是的,我們本來就該從第一天起就用數據傳輸對象(DTO)。但我們沒有。

      為什么?因為 Spring Boot 返回實體太容易了。在快速迭代出功能時,你會走捷徑。

      這些捷徑會迅速累積。

      我們有 73 個 REST 接口。都直接返回 JPA 實體。把它們全部重構成 DTO 要花上幾周。

      我們需要一個更快的修復方式。

      快速優化一:@JsonView

      Jackson 有個叫 @JsonView 的特性,可以控制被序列化的字段:

      public class Views {
          public static class Basic {}
          public static class Detailed {}
      }
      
      @Entity
      public class User {
          @JsonView(Views.Basic.class)
          private Long id;
          
          @JsonView(Views.Basic.class)
          private String email;
          
          @JsonView(Views.Detailed.class)
          private List<Order> orders;
      }
      
      @RestController
      public class UserController {
          
          @JsonView(Views.Basic.class)
          @GetMapping("/users/{id}")
          public User getUser(@PathVariable Long id) {
              return userService.findById(id);
          }
      }
      

      結果:序列化時間從 47ms 降到 12ms。

      好一些,但對我們的規模仍然太慢。

      快速優化二:禁用用不到的功能

      Jackson 默認啟用了很多特性,其中不少你并不需要:

      @Configuration
      public class JacksonConfig {
          
          @Bean
          public ObjectMapper objectMapper() {
              ObjectMapper mapper = new ObjectMapper();
              
              // 禁用開銷較大的特性
              mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
              mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
              mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
              
              // 啟用/調整更快的行為
              mapper.disable(MapperFeature.USE_GETTERS_AS_SETTERS);
              mapper.disable(MapperFeature.AUTO_DETECT_GETTERS);
              mapper.disable(MapperFeature.AUTO_DETECT_IS_GETTERS);
              
              return mapper;
          }
      }
      

      結果:再省 3ms,降到 9ms。

      真正的問題:反射

      Jackson 用反射去檢查你的對象、決定如何序列化。

      反射很慢。非常慢。

      Jackson 每次序列化一個對象時:

      1. 檢查類結構(有哪些字段)
      2. 通過反射調用 getter
      3. 把值轉換成 JSON 字符串
      4. 處理空值和類型轉換

      對于一個簡單的 User 對象,這也許沒問題。但當你每天要序列化復雜的對象圖上千萬次,這些毫秒就會變成錢。

      核選項:手寫序列化

      如果我們……自己把 JSON 拼出來呢?

      @RestController
      public class UserController {
          
          @GetMapping("/users/{id}")
          public String getUser(@PathVariable Long id) {
              User user = userService.findById(id);
              
              return String.format(
                  "{\"id\":%d,\"email\":\"%s\",\"firstName\":\"%s\",\"lastName\":\"%s\"}",
                  user.getId(),
                  user.getEmail(),
                  user.getFirstName(),
                  user.getLastName()
              );
          }
      }
      

      結果:0.8ms。

      從 47ms 到 0.8ms,提升了 58 倍。

      但是……這是不是太瘋狂了?我們仿佛又回到了 1999 年的字符串拼接時代。

      被忽略的爭論

      這里會有點爭議。

      • A 隊:手寫序列化不可維護。用 Jackson + DTO 才對。
      • B 隊:Jackson 是性能瓶頸。寫自定義序列化器。

      兩邊都對,也都不完全對。

      真正的答案取決于你的規模:

      如果每天請求 < 100 萬

      用 Jackson。開發效率值得那點性能代價。

      如果每天請求 1000 萬+

      在熱路徑上考慮自定義序列化。維護成本能被 AWS 賬單的節省抵消。

      如果每天請求 1 億+

      你大概應該用 Protocol Buffers 或 FlatBuffers 了。

      我們的實際做法

      我們采用了混合方案:

      1. 對 90% 的接口仍用 Jackson(流量低、響應復雜)
      2. 對中等流量的接口使用 @JsonView(簡單優化)
      3. 對 5 個關鍵接口編寫自定義序列化器(高流量、響應簡單)

      這 5 個接口占了我們 80% 的流量。只優化這幾個就每月給我們省了約 4200 美元的 AWS 成本。

      你應該跑的基準測試

      別信我的數字。用你的代碼測試:

      @Test
      public void benchmarkSerialization() {
          ObjectMapper mapper = new ObjectMapper();
          User user = createComplexUser();
          
          long start = System.nanoTime();
          for (int i = 0; i < 10000; i++) {
              mapper.writeValueAsString(user);
          }
          long end = System.nanoTime();
          
          System.out.println("Time per serialization: " + 
              (end - start) / 10000 / 1_000 + "μs");
      }
      

      用你的真實領域對象跑。如果結果 > 100μs,那你就有問題需要關注。

      有幫助的工具

      • JProfiler:精確展示時間花在了哪里
      • Spring Boot Actuator 指標:按接口統計序列化時間
      • JMH(Java 微基準測試框架):更準確的性能測試
      • Jackson 的 @JsonView:不用大改就能有快速收益

      我們犯過的常見錯誤

      • 錯誤 1:過度信任默認
        Spring Boot 的默認值更偏向開發體驗,而非性能。多數應用這沒問題。但在規模化場景下,默認會“害人”。
      • 錯誤 2:不測量
        我們的 API 跑了 8 個月,沒人做過性能剖析。8 個月的冤枉錢,只因為我們以為“應該沒問題”。
      • 錯誤 3:直接返回實體
        JPA 實體用于持久化,DTO 用于 API。混用不僅有性能問題,還會帶來安全風險(不小心暴露敏感字段)。
      • 錯誤 4:過早優化
        問題解決后,團隊有人想“把所有地方都優化一下”。這是壞主意。先優化熱路徑,測量,再決定是否繼續。

      不那么舒服的真相

      Jackson 并不慢。

      Jackson 正在做它被設計要做的事:在零配置的情況下,處理任意結構的 Java 對象。

      這種靈活性是有代價的。反射、類型檢查、空值處理、循環引用檢測——這些都要時間。

      問題不在 Jackson,而在“把一切都交給 Jackson”。

      替代方案

      如果你遇到 Jackson 的瓶頸,這里是一些選擇:

      1. Protocol Buffers(protobuf)

        • 二進制格式,極快
        • 需要定義 schema
        • 不可讀
      2. MessagePack

        • 二進制 JSON,通常比文本 JSON 快
        • 很多場景可作為替代
      3. FastJSON

        • 號稱比 Jackson 更快
        • 但歷史上有過安全問題
      4. 自定義序列化器

        • 可能是最快的
        • 維護成本高
      5. 好好用 DTO

        • 認真點,這能解決 90% 的問題

      真正的教訓

      你沒有 Jackson 問題,你有架構問題。

      如果 Jackson 慢,那是因為你序列化了太多數據。修的是數據,不是庫。

      用 DTO、用投影、用 @JsonView,如果需要用戶自定義響應結構可以用 GraphQL。

      別把責任推給 Jackson,它只是忠實地序列化了你讓它序列化的龐大對象圖。

      行動計劃

      你應該這樣做:

      步驟 1: 加指標,跟蹤序列化時間

      @Around("@annotation(org.springframework.web.bind.annotation.GetMapping)")
      public Object measureSerialization(ProceedingJoinPoint joinPoint) throws Throwable {
          long start = System.nanoTime();
          Object result = joinPoint.proceed();
          long serializationTime = System.nanoTime() - start;
          
          metrics.recordSerializationTime(serializationTime);
          return result;
      }
      

      步驟 2: 剖析你最熱的 10 個接口

      步驟 3: 若序列化時間 > 響應時間的 20%,繼續調查

      步驟 4: 先修最嚴重的幾個

      步驟 5: 再次測量

      不要盲目優化。不要盲目相信框架。測量一切。

      我預期會看到的評論

      • “用 gRPC/GraphQL/REST 替代就好!”
        可以,如果你能重構整個 API。多數團隊做不到。
      • “DTO 能解決所有問題!”
        它能解決很多。但即便用了 DTO,如果你還在序列化巨大的列表,Jackson 仍會慢。
      • “手寫序列化是技術債!”
        50K 的 AWS 賬單也是。擇其輕。
      • “這是過早優化!”
        當你每月在無謂的 CPU 周期上花 4K 美元時,就不是了。

      尾聲

      Jackson 很好,Spring Boot 也很棒。

      但“好”不代表“適用于所有規模”。

      在某個時刻,你需要質疑默認值;在某個時刻,你需要測量;在某個時刻,你需要在開發效率與運行成本之間做艱難取舍。

      我們在每天 5000 萬請求時遇到了這個時刻。你可能更早、也可能更晚,甚至永遠不會遇到。

      但當你遇到時,希望你能記起這篇文章。

      posted @ 2025-10-23 12:02  程序猿DD  閱讀(19)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 黑人av无码一区| 伊人色综合久久天天| 日本道播放一区二区三区| 国产suv精品一区二区四| 亚洲欧美在线综合一区二区三区| 最近中文字幕国产精选| 自偷自拍亚洲综合精品| 色综合 图片区 小说区| 久久精品国产蜜臀av| 青青草一区在线观看视频| 在厨房拨开内裤进入在线视频| 国精偷拍一区二区三区| 国产午夜精品理论大片| 国产精品午夜福利资源| 日韩丝袜亚洲国产欧美一区| 泾阳县| 成人免费A级毛片无码片2022 | 在线看国产精品自拍内射| free性开放小少妇| julia无码中文字幕一区| 国内偷自第一区二区三区| 巨熟乳波霸若妻在线播放| 日韩精品一区二区三区vr| 国产区精品福利在线观看精品| 欧美性猛交xxxx乱大交极品| 国产无人区码一区二区| 日韩在线观看精品亚洲| 国产精品任我爽爆在线播放6080| 亚洲激情在线一区二区三区| 国产精品三级一区二区三区| 日韩区一区二区三区视频| 国产亚洲精品成人无码精品网站| 泰兴市| 无码av人片在线观看天堂| 在线看无码的免费网站| 2021av在线| 精品素人AV无码不卡在线观看| 国产高清亚洲一区亚洲二区| 精品国产粉嫩内射白浆内射双马尾| 国产亚洲精品第一综合另类无码无遮挡又大又爽又黄的视频 | 亚洲自拍偷拍一区二区三区|