10

MySQL驱动扯后腿?Spring Boot用虚拟线程可能比用物理线程还差

 1 year ago
source link: https://www.didispace.com/article/spring-boot/spring-boot-virtual-threads-mysql.html#%E5%88%86%E6%9E%90%E6%80%BB%E7%BB%93
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

之前已经分享过多篇关于Spring Boot中使用Java 21新特性虚拟线程open in new window的性能测试案例:

早上看到群友问到一个关于虚拟线程遇到MySQL连接不兼容导致的性能问题:

f0228c9843d19482ff72719fabf0f29f.png

这个问题确实之前就有看到过相关的评测,顺着个这个问题,重新把相关评测找出来,给大家分享一下。

以下内容主要参考文章:https://medium.com/deno-the-complete-reference/springboot-physical-vs-virtual-threads-vs-webflux-performance-comparison-for-jwt-verify-and-mysql-23d773b41ffd

# 评测案例

评测采用现实场景中的处理流程,具体如下:

  1. 从HTTP授权标头(authorization header)中提取 JWT
  2. 验证 JWT 并从中提取用户的电子邮件
  3. 使用提取到的电子邮件执行 MySQL 查询用户
  4. 返回用户记录

这个场景其实是Spring Boot 虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较open in new window测试的后续。前文主要对比了虚拟线程和WebFlux的,但没有对比虚拟线程与物理线程的区别。所以,接下来的内容就是本文关心的重点:在物理线程和虚拟线程下,MySQL驱动是否有性能优化。

# 测试环境

  • Java 20(使用预览模式,开启虚拟线程)
  • Spring Boot 3.1.3
  • 依赖的第三方库:jjwt、mysql-connector-java

测试工具:Bombardier

采用了开源负载测试工具:Bombardier。在测试场景中预先创建 100,000 个 JWT 列表。

在测试期间,Bombardier 从该池中随机选择了JWT,并将它们包含在HTTP请求的Authorization标头中。

MySQL表结构与数据准备

User表结构如下:

mysql> desc users;
+--------+--------------+------+-----+---------+-------+
| Field  | Type         | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| email  | varchar(255) | NO   | PRI | NULL    |       |
| first  | varchar(255) | YES  |     | NULL    |       |
| last   | varchar(255) | YES  |     | NULL    |       |
| city   | varchar(255) | YES  |     | NULL    |       |
| county | varchar(255) | YES  |     | NULL    |       |
| age    | int          | YES  |     | NULL    |       |
+--------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

准备大约10w条数据:

mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
|    99999 |
+----------+
1 row in set (0.01 sec)

# 测试代码:使用物理线程

配置文件:

server.port=3000
spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username= dbuser
spring.datasource.password= dbpwd
spring.jpa.hibernate.ddl-auto= update
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

User实体定义:

@Entity
@Table(name = "users")
public class User {
  @Id
  private String email;

  private String first;

  private String last;

  private String city;

  private String county;

  private int age;

  // 省略了getter和setter
}

数据访问实现:

public interface UserRepository extends CrudRepository<User, String> {

}

API实现:

@RestController
public class UserController {

    @Autowired
    UserRepository userRepository;

    private SignatureAlgorithm sa = SignatureAlgorithm.HS256;
    private String jwtSecret = System.getenv("JWT_SECRET");

    @GetMapping("/")
    public User handleRequest(@RequestHeader(HttpHeaders.AUTHORIZATION) String authHdr) {
        String jwtString = authHdr.replace("Bearer","");
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret.getBytes())
            .parseClaimsJws(jwtString).getBody();

        Optional<User> user = userRepository.findById((String)claims.get("email"));
        return user.get();
    }
}

应用主类:

@SpringBootApplication
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

# 测试代码:使用虚拟线程

主要调整应用主类,其他一样,具体修改如下:

@SpringBootApplication
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }

    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

# 测试代码:使用WebFlux

server.port=3000
spring.r2dbc.url=r2dbc:mysql://localhost:3306/testdb?allowPublicKeyRetrieval=true&ssl=false
spring.r2dbc.username=dbuser
spring.r2dbc.password=dbpwd
spring.r2dbc.pool.initial-size=10
spring.r2dbc.pool.max-size=10
@Table(name = "users")
public class User {
  @Id
  private String email;

  private String first;

  private String last;

  private String city;

  private String county;

  private int age;

  // 省略getter、setter和构造函数
}

数据访问实现:

public interface UserRepository extends R2dbcRepository<User, String> {

}

业务逻辑实现:

@Service
public class UserService {

  @Autowired
  UserRepository userRepository;

  public Mono<User> findById(String id) {
    return userRepository.findById(id);
  }
}

API实现:

@RestController
@RequestMapping("/")
public class UserController {
  @Autowired
  UserService userService;

  private SignatureAlgorithm sa = SignatureAlgorithm.HS256;
  private String jwtSecret = System.getenv("JWT_SECRET");

  @GetMapping("/")
  @ResponseStatus(HttpStatus.OK)
  public Mono<User> getUserById(@RequestHeader(HttpHeaders.AUTHORIZATION) String authHdr) {
    String jwtString = authHdr.replace("Bearer","");
    Claims claims = Jwts.parser()
        .setSigningKey(jwtSecret.getBytes())
        .parseClaimsJws(jwtString).getBody();
    return userService.findById((String)claims.get("email"));
  }
}

应用主类:

@EnableWebFlux
@SpringBootApplication
public class UserApplication {

  public static void main(String[] args) {
    SpringApplication.run(UserApplication.class, args);
  }

}

# 测试结果

每次测试都包含 100 万个请求,分别评估了它们在不同并发(50、100、300)水平下的性能。下面是结果展示:

53bf3395b0718c39394f50124c89b270.png

b34ff77fb1c93168661c1f4f31dd9830.png

61eb78352afb34eecc3c784b893aeb91.png

2ccb3b6ea6c66b5eff45ae9eb52640cd.png

a0499fed887ed50e45f3821c9422b732.png

2228582ef2dd31c597c12b3d6ba4428b.png

96255019e85e43a7bdccf6f6a985c370.png

b9ed40b83e4b88b594a8dfe941566b53.png

28c3bdc1f0fe4d12cf55af1b567876b5.png

6b6c01737f7ce90a4b26b7bc63149b9d.png

01f176418b3db51d88926902a96fecd9.png

ff4a6b8d83f7d3fa3680bb7a68ac4aca.png

e936a9f6e2507615c2dfc40a86603af4.png

# 分析总结

在这个测试案例中使用了MySQL驱动,虚拟线程的实现方式性能最差,WebFlux依然保持领先。所以,主要原因在于这个MySQL的驱动对虚拟线程不友好。如果涉及到数据库访问的情况下,需要寻找对虚拟线程支持最佳的驱动程序。另外,该测试使用的是Java 20和Spring Boot 3.1。对于Java 21和Spring Boot 3.2建议读者在使用的时候自行评估。

最后,对于MySQL驱动对虚拟线程支持好的,欢迎留言区推荐一下。如果您学习过程中如遇困难?可以加入我们超高质量的Spring技术交流群open in new window,参与交流与讨论,更好的学习与进步!更多Spring Boot教程可以点击直达!open in new window,欢迎收藏与转发支持!

</div


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK