提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、测试框架选取
- Spring Webflux VS Spring MVC
- Spring Data R2DBC VS MyBatis
- 二、测试代码编写
- 1. 项目1核心代码
- 1.1 引入依赖
- 1.2 接口代码
- 2. 项目2核心代码
- 2.1 引入依赖
- 2.2 接口代码
- 三、开始测试
- 总结
- 项目源码
前言
最近在学习kotlin,在与springboot整合的时候,觉得mybatis在kotlin中显得特别突兀,没有发挥kotlin协程的优势,于是接触到了r2dbc,在学习完r2dbc的基本用法后不禁在想,理论上r2dbc的响应式模式能提升系统资源的使用效率,但实际上与非响应式的mybatis比能有多大的提升呢?我们是否有必要花费一定的代价去生产项目中使用r2dbc呢?于是就有了这篇文章。
一、测试框架选取
Spring Webflux VS Spring MVC
web层使用Spring Webflux与Spring MVC进行对比,在前些年的文章中有对比测试过Webflux框架,当时Webflux刚问世不久,测试后果然其性能提升并不明显,现在SpringBoot已经迭代到3.X,2.X版本已经更新到2.7.11,而且Webflux对kotlin协程有API支持,所以这次Webflux仍然被选作了测试对象。
Spring Data R2DBC VS MyBatis
持久层使用Spring Data R2DBC与MyBatis进行对比,这两位是这次测试的重点,响应式和非响应式到底有多大的性能差距?我们拭目以待。
二、测试代码编写
本次测试一共需要搭建2个项目,分别是
- 项目1 Spring Webflux + Spring Data R2DBC
- 项目2 Spring Web + MyBatis
测试使用的是MySQL数据库,提前创建一张表my_test表
CREATE TABLE `my_test` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
为了简化代码与测试脚本,本次测试每个项目仅一个接口,接口中包括新增查询修改删除四个业务,每次数据库操作都会提交事务。
1. 项目1核心代码
本项目使用JDK1.8、kotlin 1.6.21 以及SpringBoot 2.7.11
1.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<version>0.8.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<!-- r2dbc 连接池 -->
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-pool</artifactId>
</dependency>
注意使用R2DBC需要搭配响应式数据库驱动,本次测试使用的是MySQL数据库,使用的驱动是dev.miku.r2dbc-mysql
1.2 接口代码
@RestController
@RequestMapping("/myTest")
class MyTestController {
private val logger = LoggerFactory.getLogger(this.javaClass)
@Resource
private lateinit var template: R2dbcEntityTemplate
@GetMapping("/start")
fun start(): Mono<AjaxResult> = mono(Unconfined) {
val test = MyTest()
test.name = "张三"
test.createTime = LocalDateTime.now()
logger.info("开始新增")
val res = template.insert(MyTest::class.java).into(MyTest.tableName).usingAndAwait(test)
val id = res.id
logger.info("开始查询id=$id")
val queryRes = template.select(MyTest::class.java).from(MyTest.tableName).matching(
Query.query(Criteria.where("id").`is`(id!!))
).one().awaitSingle()
logger.info("开始修改")
val update = Update.update("name","李四")
template.update(MyTest::class.java).inTable(MyTest.tableName).matching(
Query.query(Criteria.where("id").`is`(id))
).applyAndAwait(update)
logger.info("开始删除")
template.delete(MyTest::class.java).from(MyTest.tableName).matching(
Query.query(Criteria.where("id").`is`(id))
).allAndAwait()
AjaxResult.success()
}
}
为了模拟真实业务场景,每一步数据库操作都会同步等待到协程执行完毕。
2. 项目2核心代码
本项目使用JDK1.8、kotlin 1.6.21 以及SpringBoot 2.7.11
2.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringBoot集成mybatis框架 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
MyBatis使用非响应式驱动即可。虽然引入的是mybatis-plus依赖,但实际使用没有用到mybatis-plus的功能。
2.2 接口代码
@RestController
@RequestMapping("/myTest")
class MyTestController {
private val logger = LoggerFactory.getLogger(this.javaClass)
@Autowired
private lateinit var mapper: MyTestMapper
@GetMapping("/start")
fun start(): Mono<AjaxResult> = mono(Dispatchers.Unconfined) {
val test = MyTest()
test.name = "张三"
test.createTime = LocalDateTime.now()
logger.info("开始新增")
mapper.insertMyTest(test)
val id = test.id
logger.info("开始查询id=$id")
mapper.selectMyTestById(id)
logger.info("开始修改")
val update = MyTest()
update.name = "李四"
update.id = id
mapper.updateMyTest(update)
logger.info("开始删除")
mapper.deleteMyTestById(id)
AjaxResult.success()
}
}
这个是测试Webflux的接口。
@RestController
@RequestMapping("/myTest2")
class MyTest2Controller {
private val logger = LoggerFactory.getLogger(this.javaClass)
@Autowired
private lateinit var mapper: MyTestMapper
@GetMapping("/start")
fun start(): AjaxResult {
val test = MyTest()
test.name = "张三"
test.createTime = LocalDateTime.now()
logger.info("开始新增")
mapper.insertMyTest(test)
val id = test.id
logger.info("开始查询id=$id")
mapper.selectMyTestById(id)
logger.info("开始修改")
val update = MyTest()
update.name = "李四"
update.id = id
mapper.updateMyTest(update)
logger.info("开始删除")
mapper.deleteMyTestById(id)
return AjaxResult.success()
}
}
这个是测试Spring MVC的接口。
这里偷了个懒没有写Service层,直接Controller调用Dao层。实体类和Dao层代码相信大家都非常熟悉了,就不放了,需要的同学可以去文末源码中查看。
三、开始测试
测试使用的是jmeter,直接在1秒内暴力启动线程组调用接口,分别测试200、500、1000、2000线程。
测试过程比较枯燥就不放了,我们直接上测试结果。
先上TPS:
再上平均响应时间:
服务器以及网络环境性能有限,大家不要去看具体数据,只用参考不同框架间的差距即可。
根据不同线程数的测试结果取平均值后计算发现,在都使用Wbflux的前提下R2DBC比MyBatis的吞吐量大了18.8%,但平均响应时间多了26.7%。在都使用MyBatis的前提下,使用MVC和Webflux的性能差距可以忽略不计。
总结
在实际生产中,大部分项目查询的数量往往是最多的,并且大部分查询都需要走缓存,因此数据库IO的时间占比肯定会比我们的测试项目中低,因此持久层框架对项目整体性能的影响将会更小。
经过对比测试,我们可以得到以下结论:
- 当项目用户较少,TPS较低的项目使用传统的Spring MVC和MyBatis组合即可
- 当项目用户很多,TPS较高时可以考虑使用响应式的R2DBC提升性能,但比起学习成本并不推荐
- Spring MVC与Webflux性能相近,所以喜欢哪种编程方式就选哪种吧
- 如果项目使用Kotlin开发,那么Webflux和R2DBC的API更加契合一些
项目源码
项目1 Webflux + R2DBC
项目2 Web + MyBatis