用JMeter调试api并行测试

由于后端服务创建issue采用的是synchronized同步方法(防止出现相同的issue编号,最大的编码会存在projectInfo中),由于service是单例的导致并行创建issue时需要等待同步,从而出现创建issue缓慢的问题,这里采用原子类AtomicLong来控制并发,提升并行时的创建速度

构建工具类,采用AtomicLong控制并发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class IssueNumUtil {
//每个项目缓存一个AtomicLong对象
private static final Map<Long, AtomicLong> issueNumMap = new HashMap<>();

/**
* 获取最新的issueNum
*
* @param projeceId
* @return
*/
public static Long getNewIssueNum(Long projeceId) {
AtomicLong atomicLong = issueNumMap.get(projeceId);
if (atomicLong == null) {
//每个项目第一次获取atomicLong值时,需要用synchronized进行类级同步锁,以此保证之后每次获取的都不重复
synchronized (IssueNumUtil.class) {
atomicLong = issueNumMap.get(projeceId);
if (atomicLong == null) {
ProjectInfoMapper projectInfoMapper = ApplicationContextHelper.getSpringFactory().getBean(ProjectInfoMapper.class);
Long issueNum = projectInfoMapper.queryByProjectId(projeceId).getIssueMaxNum();
atomicLong = new AtomicLong(issueNum);
issueNumMap.put(projeceId, atomicLong);
}
}
}
//增加并获取
return atomicLong.incrementAndGet();
}
}

更新projectInfo的issueMaxNum值

1
2
3
4
5
6
7
8
9
10
/**
* 更新MaxNum方法,在高并发的情况下,可能更新的maxNum已经不是最大的maxNum,因此不需要判断是否更新成功
*
* @param projectId projectId
* @param issueMaxNum issueMaxNum
*/
@Override
public void updateIssueMaxNum(Long projectId, String issueMaxNum) {
projectInfoMapper.updateIssueMaxNum(projectId, issueMaxNum);
}
1
2
3
4
5
6
7
<update id="updateIssueMaxNum">
UPDATE agile_project_info api
SET api.issue_max_num = #{issueMaxNum}
WHERE
api.project_id = #{projectId}
and #{issueMaxNum} > api.issue_max_num
</update>

下载与安装JMeter

由于Postman只能测api的并发(按顺序调用api),而我们需要的是并行(同时调api)来测试我们的工具类,因此采用压力测试工具JMeter

  • 官网下载最新版本的JMeter

image

  • 解压压缩包
1
tar -xzvf apache-jmeter-5.1.1.tgz
  • 进入bin目录,更改语言为中文
1
2
cd apache-jmeter-5.1.1/bin
vim jmeter.properties

image

  • 进入bin目录,启动JMeter
1
sh jmeter

image

用JMeter进行并行测试

  • 设置线程组

image

  • 设置HTTP请求

image

image

  • 设置HTTP请求头

image

image

  • 设置同步定时器来实现并行

image

10个线程等待1s并行

image

  • 观察结果树

image

image

  • 运行结果

image

结论

最后证明可以用原子类可以替代基于全局单例的synchronized,使用AtomicLong在不同项目间控制并行,以此来提升createIssue的并行速度

结合RedisAtomicLong

在微服务环境下多pod的情况中,AtomicLong缓存在一个服务中,则会出现issueNum重复的情况,因此要采用外部缓存,这里采用的是RedisAtomicLong

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class IssueNumUtil {
public static final String REDIS_ISSUE_NUM_FLAG = "issueNum:";

public static Long getNewIssueNum(Long projeceId) {
RedisTemplate<String, Object> redisTemplate = ApplicationContextHelper.getSpringFactory().getBean(RedisUtil.class).getRedisTemplate();
RedisAtomicLong atomicLong = new RedisAtomicLong(REDIS_ISSUE_NUM_FLAG + projeceId, redisTemplate.getConnectionFactory());
if (atomicLong.get() == 0) {
synchronized (IssueNumUtil.class) {
atomicLong = new RedisAtomicLong(REDIS_ISSUE_NUM_FLAG + projeceId, redisTemplate.getConnectionFactory());
if (atomicLong.get() == 0) {
ProjectInfoMapper projectInfoMapper = ApplicationContextHelper.getSpringFactory().getBean(ProjectInfoMapper.class);
Long issueNum = projectInfoMapper.queryByProjectId(projeceId).getIssueMaxNum();
atomicLong = new RedisAtomicLong(REDIS_ISSUE_NUM_FLAG + projeceId, redisTemplate.getConnectionFactory(), issueNum);
}
}
}
return atomicLong.incrementAndGet();
}
}

延伸:并行与并发

  • 并发:并发的关键是你有处理多个任务的能力,不一定要同时

  • 并行:并行的关键是你有同时处理多个任务的能力

参考文献