一行log日志,引发了P1的线上事故

若依ruoyi框架实现单点登录或者接入统一认证 log.info("单点登录用户[{}]不存在, 需要创建.", loginName);log.info("单点登录用户[{}]已存在.", loginName);jsonObjectData.put("nickName","单点1");if (code.equals("0")) {//验证成功需要自动登录。jsonObject.put("msg","验证成功");ajax.put("msg", "登录成功"); 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:juejin.cn/post/

7156439842958606349


线上事故回顾

前段时间同事新增了一个特别简单的功能,晚上上线前review代码时想到公司拼搏进取的价值观临时他加一行 log 日志,觉得就一行简单的日志基本上没啥问题,结果刚上完线后一堆报警,赶紧回滚了代码,找到问题删除了添加日志的代码,重新上线完毕。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

情景还原

❝

定义了一个 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);
    }
}

运行时报空指针错误:

0e8b302df3ca49cf57991ce697d4d948.png
空指针

通过报错信息可以看出来是序列化的过程中执行了isChinaName()方法,这时候this.country变量为空,那么问题来了:

  • 序列化为什么会执行isChinaName()呢?

  • 引申一下,序列化过程中会执行那些方法呢?

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

源码分析

通过 debug 观察调用链路的堆栈信息

c4b3b1bf4702e959dd47bf814ef37eaa.png 90b1531b97e8ee5605348ab4e3a494db.jpeg

调用链中的ASMSerializer_1_CountryDTO.write是FastJson使用asm技术动态生成了一个类ASMSerializer_1_CountryDTO。

❝

asm技术其中一项使用场景就是通过到动态生成类用来代替java反射,从而避免重复执行时的反射开销

❞

JavaBeanSerizlier序列化原理

通过下图看出序列化的过程中,主要是调用JavaBeanSerializer类的write()方法。

50dfb515fd255549fc8d21a6cd82ff39.png
ObjectSerializer实现类JavaBeanSerializer

而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开头的方法

序列化流程图

4769c3d550d3a627a904f82da48a7185.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)来显式的标注方法不参与序列化,下面是使用@JSONField注解后的代码,是不是一眼就能看出来哪些方法不需要参与序列化了。

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";
    }
}

三个频率高的序列化的情况

b95d893258a8e7cce2e95a85287a4974.png
三个频率高的序列化的情况

以上流程基本遵循,发现问题 --> 原理分析 --> 解决问题 --> 升华(编程规范)。

  • 围绕业务上:解决问题 -> 如何选择一种好的额解决方案 -> 好的解决方式如何扩展 n 个系统应用;

  • 围绕技术上:解决单个问题,顺着单个问题掌握这条线上的原理。

但其实这段代码我并不满意,原因是和 FastJson 依赖太高了。我想要的效果是,不依赖任何特定的 JSON 序列化框架。当我需要替换掉它的时候,随时可以替换掉。

并且在写代码时,不要过于依赖日志。打日志只需要打紧要且关键的信息即可,不要什么日志都打,我曾见过一个系统,一个小时,把 128G 磁盘跑满的管理系统。几乎没啥并发,但几乎每个请求都输出几 M 的日志,这件事我后面会单独拿出来讲讲。

关于@JSONField和@JSONType等特性注解,后面我会在团队内规范并给出新的解耦方案,把它们移除掉。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

109af52bc5b69de134a10673811dc14a.png

已在知识星球更新源码解析如下:

163f03898907760b77d5e858eac0d7ad.jpeg

6aff21e64690a606022f31f7cfa9283a.jpeg

e649a0cd1fefa67943d2d22bd6e25225.jpeg

7d28b4ad3fb44c7ad71fb7126aecc758.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
Python爬虫实战:高效爬取法律法规数据库的完整指南 本文将详细介绍如何使用Python最新技术栈构建一个高效、稳定的法律法规数据库爬虫。我们将从爬虫的基本原理讲起,逐步深入到反爬机制应对、数据解析与存储、性能优化等高级话题。文章包含完整的代码实现,使用requests-html、aiohttp等现代库,以及应对法律数据库特殊结构的处理技巧。通过本文,您将掌握构建专业级法律数据采集系统的全套技能。关键词:Python爬虫、法律法规、数据采集、异步爬虫、反反爬本文详细介绍了使用Python爬取法律法规数据库的完整技术方案。 阅读详情

相关推荐

介绍七种常见还危险的病毒(带样本)

简介:能够感染用户计算机bai系统中的.exe、.dll和.html文件,将自身加密以后缀加到目标文件中。样本(源文件):https://wwx.lanzoux.com/iJx4njrpz1c / https://wwx.lanzoui.com/iYRNLwd8ldc。英文名称:Virus.Win32.Viking.A(标准) / Worm.Win32.Fujack.A(标准)英文名称:Net-Worm.Win32.Blaster.A(标准)/ Lovesan.A/ Msblast.A。

2301_78858007的博客 8530

我用一行log日志,搞崩公司线上系统,直接当着全公司人被批评!

往期热门文章: 1、这几个已经淘汰的Java技术,请不要在继续学了!!! 2、Spring 6.0 正式发布,新一代框架的开始!! 3、我用了10年kafka,踩过的几十个坑都在这里了! 4、责任链模式,一门甩锅的技术 5、又一巨头从 Java 迁移到 Kotlin !线上事故回顾前段时间新增一个特别简单的功能,晚上上线前review代码时想到公司拼搏进取的价值观临时加一行log日志,觉得就一行简...

Java后端技术 197

weblogic 集群搭建,双机部署,相关问题记录

4.5.集群搭建1 参考网址https://wenku.baidu.com/view/76365129cd1755270722192e453610661ed95aa4.html IP地址 端口 角色 192.168.1.160 9001 AdminServer ...

zl386119974的专栏 1670

【Exception】fastjson的toJSONString()导致OOM

现象 线上代码跑一段时间后出现了OOM的问题,经过日志的检查,发现异常出现在fastjson的toJSONString()导致的OOM 解决 将toJSONString()方法改成jackson的转字符串方法 ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(obj); 原因 fastjson在转成json字符串的时候会分配一个byte[],当数据量很大的时候,需要分配

非正常人研究室 3489

STM32 RTC_WaitForSynchro()死循环问题分析与LSI时钟优化实践

本文深入分析了STM32使用LSI时钟源时,调用RTC_WaitForSynchro()函数陷入死循环的根本原因。问题核心在于RTC时钟域未正确使能,导致硬件同步标志RSF无法置位。文章提供了通过显式调用RCC_RTCCLKCmd(ENABLE)函数启用RTC时钟总开关的解决方案,并分享了编写健壮RTC/BKP初始化代码、增加超时机制等最佳实践,确保系统稳定运行。

weixin_29185493的博客 512

C/C++_五大常规算法_分治算法

分治法 一个装有 16 枚硬币的袋子,16 枚硬币中有一个是伪造的,伪造的硬币和普通硬币从表面上看不出有任何差别,但是那 个伪造的硬币比真的硬币要轻。现有给你一台天平,请你在尽可能最短的时间内找出那枚伪造的硬币。 常规思维: 每次从待比较的硬币中取两枚进行计较,如果天平平衡(相等)就继续取剩下的硬币进行比较 继续以上过程,直到找到硬币。 强者思维: 我们先将 16 枚硬币分为左右两个部分,各为 8 个硬币,分别称重,必然会有一半轻一半重,而我们要的就是轻的那组,重 的舍去。接下来我们继续对轻的进行五五分

weifc-wei 的博客 2726

P1-12:集成日志组件 logback 1基本配置

第一步,控制台看到打印信息 1.maven项目新增组件,就需要在pom.xml中引入依赖。但是logback不需要,因为他已经被包含在spring-boot-starter-web中了,所以如果引入了spring-boot-starter-web,就不需要再单独引用logback依赖。 如下图所示: Spring Boot默认的日志级别为INFO,所以在没有任何配置的情况下,会...

weixin_30709929的博客 148

CTF-Web习题:2019强网杯 UPLOAD

本题涉及知识点:代码审计、文件上传漏洞、反序列化漏洞

LJW123487的博客 1903

一行 log 日志,结果引发了 P1 的线上事故

线上事故回顾情景还原源码分析JavaBeanSerizlier序列化原理序列化流程图示例代码代码规范三个频率高的序列化的情况前段时间同事新增了一个特别简单的功能,晚上上线前review代码时想到公司拼搏进取的价值观临时他加一行 log 日志,觉得就一行简单的日志基本上没啥问题,结果刚上完线后一堆报警,赶紧回滚了代码,找到问题删除了添加日志的代码,重新上线完毕。❝定义了一个CountryDTO❞❝定义测试类❞运行时报空指针错误:空指针通过报错信息可以看出来是序列化的过程中执行了。

程序员高级码农的博客 1041

SI案例分享--考虑ESD保护二极管的差分线阻抗仿真

PCB设计中的cut out参考面早已是SI评估中常用的阻抗优化方式,通过合理的仿真建模,可以提供合适的设计参考,并保持与测试结果的高度一致性。

一只豌豆象的博客 1438

Oracle 从入门到精通系列(入门篇)1.1-Oracle 数据库发展史

Oracle 从入门到精通系列(入门篇)1.1-Oracle 数据库发展史

LuciferLiu_DBA 2191

一行log日志,结果引发了P1的线上事故

前段时间同事新增了一个特别简单的功能,晚上上线前review代码时想到公司拼搏进取的价值观临时他加一行 log 日志,觉得就一行简单的日志基本上没啥问题,结果刚上完线后一堆报警,赶紧回滚了代码,找到问题删除了添加日志的代码,重新上线完毕。❝定义了一个CountryDTO❞❝定义测试类❞运行时报空指针错误:空指针通过报错信息可以看出来是序列化的过程中执行了方法,这时候变量为空,那么问题来了:序列化为什么会执行呢?引申一下,序列化过程中会执行那些方法呢?通过 debug 观察调用链路的堆栈信息。

m0_73735578的博客 372

高级技能-高可用和容灾-两地三中心、异地多活架构

两地三中心和异地多活是当前主流的跨地域高可用架构方案。两地三中心通过生产中心、同城灾备和异地灾备的组合,提供多级容灾能力,适合对数据安全要求高的行业。异地多活则采用单元化设计,多个站点同时服务,提升资源利用率,但需解决网络延迟、数据冲突等挑战。两种架构在RTO/RPO、一致性、改造成本等方面各具特点,企业应根据业务规模、一致性要求和预算进行选择,可考虑从同城双活起步,逐步向异地多活演进。混合架构可能是平衡容灾与多活的务实方案。

zhongjianfeng100的博客 169

一行log日志,结果引发了P1的线上事故!

前段时间新增一个特别简单的功能,晚上上线前review代码时想到公司拼搏进取的价值观临时加一行log日志,觉得就一行简单的日志基本上没啥问题,结果刚上完线后一堆报警,赶紧回滚了代码,找到问题删除了添加日志的代码,重新上线完毕。当一个事物的判别方式有多种的时候,由于团队人员掌握知识点的程度不一样,这个方差很容易导致代码问题,所以尽量有一种推荐方案。来显式的标注方法不参与序列化,下面是使用推荐方案后的代码,是不是一眼就能看出来哪些方法不需要参与序列化了。执行过程的调试,找到比较关键的。

m0_71777195的博客 512

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

一行 log 日志,引发 P1 级线上事故!

作者:老鹰汤。

Java技术栈,分享最主流的Java技术 302

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

一行log日志,引发了P1的线上事故!

线上事故回顾前段时间同事新增了一个特别简单的功能,晚上上线前review代码时想到公司拼搏进取的价值观临时他加一行 log 日志,觉得就一行简单的日志基本上没啥问题,结果刚上完线后一堆报警,赶紧回滚了代码,找到问题删除了添加日志的代码,重新上线完毕。情景还原定义了一个 CountryDTOpublicclassCountryDTO{ privateStringcountry; ...

weixin_45727359的博客 211

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

上一篇: 消息队列原理和选型:Kafka、RocketMQ 、RabbitMQ 和 ActiveMQ
下一篇: 聊聊微服务中的 BFF 架构
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值