赵宇博的技术博客 赵宇博的技术博客
首页
前端
后端
数据库专栏
k8s专栏
分布式专栏
Linux网络专栏
手写系列专栏
随笔
关于
GitHub (opens new window)
首页
前端
后端
数据库专栏
k8s专栏
分布式专栏
Linux网络专栏
手写系列专栏
随笔
关于
GitHub (opens new window)
  • 分享一次项目压测导致的并发问题
    • 1、前言:
    • 2、分析过程
    • 3、解决方案
    • 4、关于@Scope扩展
  • 基于WireShark分析TCP三次握手
  • Why Spring ?
  • 随笔
zhaoyb
2023-12-06
目录

分享一次项目压测导致的并发问题

# 分享一次项目压测导致的并发问题


2023年11月份左右,开发的项目进入压测阶段,压测第一阶段的单据保存,直接40%~60%的线程都提示测试失败,具体失败日志全是数据库层面抛出的主键冲突异常,时间紧,任务重,因为这一块的业务全是我写的,所以整个排查工作就落在我的身上。

# 1、前言:

排查之前先大致了解一下这个项目结构和业务流程,整体项目以SpringBoot为底层框架、以DDD思想为核心搭建的。DDD主推的领域模型的概念也在项目中有所体现,下图简单展示了保存单据这个业务主要经历的流程

image-20231206192404165

大致分为四个阶段:

1、根据前端参数billType来判断需要当前业务需要执行哪个Service,负责的领域对象是哪个,分别获取其实例

2、把前端参数注入到BO实例对象中,进行对象属性赋值

3、把BO对象当做参数去调用Service的add方法

4、Service出口统一把BO转换成PO进行数据库操作


伪代码如下:

领域服务实例:

@Service("USER_SERVICE")
public class UserServiceImpl implements AbstractService{
    
    public void add(UserBO userBO){
        //do something
    };
}
1
2
3
4
5
6
7

领域对象实例:

@Component("USER_BO")
public class UserBO extends AbstractBO {
    private String name;
    private String id;
}
1
2
3
4
5

业务伪代码:

public class UserAction{
    
    @Autowire
    private Map<String,AbstractService> serviceMap;
    
    @Autowire
    private Map<String,AbstractBO> BOMap;
    
    public void add(String billType,Object param){
        //1、获取Service实例
        AbstractService service = serviceMap.get(billType+"_SERVICE");
        // 2、获取BO实例
        AbstractBO BO = BOMap.get(billType+"_BO");
        // 3、工具类进行属性赋值 param -> BO
        BeanUtil.Transform(param,BO);
        // 4、执行业务
        service.add(BO);
        // 5、转换成数据库对象
        User po = new User();
        BeanUtil.Transform(BO,PO);
        // 6、调用入库接口
        dao.insert(po);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 2、分析过程

最开始我把目标定位在第三步BeanUtil.Transform(param,BO);我初步是怀疑这个自己写得转换类有线程安全问题,导致了压测失败

然后开始了debug阶段,本地使用ApiFox进行接口压测,在IDEA中进行多线程debug,断点打在了伪代码的17行,程序启动之后,在17行获取的BO对象确实存在问题,其中线程1和线程2的BO对象中的ID属性出现了重复(数据库表的主键是前端生成的)

然后就开始分析BeanUtil工具类,这个转换类中的Transform是静态方法,所有线程共享一个实例,内部可变化的私有属性都是线程不安全的,但是在开发过程中我也都提前预料到了这些问题,这些可以变化的属性我都用ThreadLocal进行的包装,整个工具类分析了一遍,没有发现问题,然后我重新看了一下debug工作台里面的所有变量,哦吼~,发现了问题所在,如下图所示:

image-20231207095102244

image-20231207095135460

所有线程获取的BO实例对象都是同一个,我才忽然想起来,对于Spring来说,每个Bean的作用域默认都是单例的,这就导致了这次的压测事故,因为线程1获取BO实例,把前端传入的ID赋值给实例对象,但是线程2获取的依旧是当前实例就会把线程1已经赋值的属性给覆盖掉~~~

# 3、解决方案

时间紧迫,我选择了最快能解决问题的方案

1、获取到BO实例之后,因为这是Spring代理对象,需要编写一个工具类获取当前代理对象的原始对象实例

2、获取到原始对象实例之后,使用反射重新获取新实例

3、使用新实例对象进行后续的业务操作


伪代码如下:

BO = (AbstractBO)BeanUtil.getTarget(BO);
BO = BO.getClass().newInstance();

//getTarget方法简写
public static Object getTarget(Object proxy) throws Exception {
    if (!AopUtils.isAopProxy(proxy)) {
        return proxy;
    }
    // 判断是jdk还是cglib代理
    if (AopUtils.isJdkDynamicProxy(proxy)) {
        proxy = getJdkDynamicProxyTargetObject(proxy);
    } else {
        proxy = getCglibProxyTargetObject(proxy);
    }
    return getTarget(proxy);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4、关于@Scope扩展

问题解决之后,想了一下,还是需要对Spring的作用域做一个深入的了解,关于作用域的基本概念,其实都能说的上来,但是真实的在项目中使用还是比较少的,那这个@Scope注解是不是存在一些坑呢?上面的问题,我如果直接在BO对象上面加@Scope("prototype")是否能解决问题呢? 我做了一个测试

1、原先代码逻辑不变,在UserBO上增加注解@Scope("prototype")

2、进行本地压测,观察debug控制台参数

image-20231207101037277

image-20231207101057757

测试表明没啥用,那就继续深入了解@Scope的原理,Spring会在当前实例触发构造方法的时候,根据@Scope来判断是返回单例还是新实例,那在当前测试的场景下,虽然BO增加了@Scope注解,但是Controller并没有增加@Scope,多个线程获取的Controller是一个单实例对象,没办法去触发BO的构造方法,所以造成了@Scope的失效


那依据这个原理,如果在Controller也加入一个@Scope("prototype")是不是就可以了? 再做一个测试

1、在Controller上增加注解@Scope("prototype")

2、进行本地压测,观察debug控制台参数

image-20231207102020911

image-20231207102034450

测试表明可以了,是自己想要的效果,BO实例已经是多实例了


那再换个思路,如果只给Controller增加@Scope("prototype"),BO实例不增加,那BO还是单例吗?再做一个测试

image-20231207102511395

image-20231207102527932

测试表明BO依然是单实例


总结:@Scope("prototype")是在构造方法的时候进行触发的,如果当前对象是多实例,但是引用这个对象的对象是单例的,就不会触发构造方法,也就是造成@Scope注解失效


至此,分享结束,与君共勉~

#压测#并发
上次更新: 2023/12/07, 10:39:41
基于WireShark分析TCP三次握手

基于WireShark分析TCP三次握手→

最近更新
01
Activiti6-业务实现
12-06
02
Activiti6-API详解
11-28
03
SpringBoot集成Activiti和UI
11-21
更多文章>
Theme by Vdoing | Copyright © 2022-2024 赵宇博 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式