javascript
MySQL驱动扯后腿?Spring Boot用虚拟线程可能比用物理线程还差
之前已經分享過多篇關于Spring Boot中使用Java 21新特性虛擬線程的性能測試案例:
- Spring Boot 3.2虛擬線程搭建靜態文件服務器有多快?
- Spring Boot 虛擬線程與Webflux在JWT驗證和MySQL查詢上的性能比較
早上看到群友問到一個關于虛擬線程遇到MySQL連接不兼容導致的性能問題:
這個問題確實之前就有看到過相關的評測,順著個這個問題,重新把相關評測找出來,給大家分享一下。
以下內容主要參考文章:https://medium.com/deno-the-complete-reference/springboot-physical-vs-virtual-threads-vs-webflux-performance-comparison-for-jwt-verify-and-mysql-23d773b41ffd
評測案例
評測采用現實場景中的處理流程,具體如下:
- 從HTTP授權標頭(authorization header)中提取 JWT
- 驗證 JWT 并從中提取用戶的電子郵件
- 使用提取到的電子郵件執行 MySQL 查詢用戶
- 返回用戶記錄
這個場景其實是Spring Boot 虛擬線程與Webflux在JWT驗證和MySQL查詢上的性能比較測試的后續。前文主要對比了虛擬線程和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)水平下的性能。下面是結果展示:
分析總結
在這個測試案例中使用了MySQL驅動,虛擬線程的實現方式性能最差,WebFlux依然保持領先。所以,主要原因在于這個MySQL的驅動對虛擬線程不友好。如果涉及到數據庫訪問的情況下,需要尋找對虛擬線程支持最佳的驅動程序。另外,該測試使用的是Java 20和Spring Boot 3.1。對于Java 21和Spring Boot 3.2建議讀者在使用的時候自行評估。
最后,對于MySQL驅動對虛擬線程支持好的,歡迎留言區推薦一下。如果您學習過程中如遇困難?可以加入我們超高質量的Spring技術交流群,參與交流與討論,更好的學習與進步!更多Spring Boot教程可以點擊直達!,歡迎收藏與轉發支持!
歡迎關注我的公眾號:程序猿DD。第一時間了解前沿行業消息、分享深度技術干貨、獲取優質學習資源
總結
以上是生活随笔為你收集整理的MySQL驱动扯后腿?Spring Boot用虚拟线程可能比用物理线程还差的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【驱动】I2C驱动分析(二)-驱动框架
- 下一篇: 【新手友好】用Pyspark和Graph