SpringWebflux与SpringMVC性能对比及适用场景
阅读数:258 评论数:0
跳转到新版页面分类
python/Java
正文
一、概述
webflux是spring5中引进的非阻塞编程模型,而与之相对应提SpringMVC是一种阻塞模式,比如Spring Cloud Gateway是用了SpringWebflux开发。
为什么说非阻塞要比阻塞性能好呢,是因为程序在做一些io操作时,例如发网络请求,从磁盘读写文件时,线程会进入阻塞状态,虽然不会再为其分配CPU时间直到阻塞结束,但是该线程就干不了别的事了。
那非阻塞的Webflux是怎么做的呢,在发起IO请求的时候,例如请求访问Redis时,会向Netty注册一个监听事件,然后发送Redis访问请求,这时不会阻塞等待结果而是处理其他任务(例如发送其他的Redis请求),当Redis返回了结果,刚才注册的事件就会触发并执行相应的响应方法。
1、webflux
webflux是spring5中引入的一种响应式框架,借助异步IO能,提高高并发情况下的处理能能力。
spring webflux基于reactor开发(Reactor只是让我们写回调的响应式代码更加方便、可读性更高,其本质和回调是没有区别的),支持接口直接返回 Mono或Flux。
(1)io密集型应用
(2)对并发性要求高
(3)延迟较高的网络请求
(4)支持响应式数据库驱动
二、spring webflux的使用
(1)依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
(2)实体类
public class User {
private String name;
private String gender;
private Integer age;
public User(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
(3)service接口
public interface UserService {
//根据 id 查询用户
Mono<User> getUserById(int id);
//查询所有用户
Flux<User> getAllUser();
//event流式数据
Flux<User> getUsersStream();
//添加用户
Mono<Void> saveUserInfo(Mono<User> user);
}
(4)service实现
@Service
public class UserServiceImpl implements UserService {
//创建 map 集合存储数据
private final Map<Integer,User> users = new HashMap<>();
public UserServiceImpl() {
this.users.put(1,new User("lucy","female",20));
this.users.put(2,new User("mary","female",25));
this.users.put(3,new User("jack","male",30));
}
//根据 id 查询
@Override
public Mono<User> getUserById(int id) {
return Mono.justOrEmpty(this.users.get(id));
}
//查询多个用户
@Override
public Flux<User> getAllUser() {
return Flux.fromIterable(this.users.values());
}
@Override
public Flux<User> getUsersStream() {
return Flux.fromIterable(this.users.values()).doOnNext(user -> {
try {
//模拟数据的获取需要消耗时间,一条一条的获取过来的
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
//添加用户
@Override
public Mono<Void> saveUserInfo(Mono<User> userMono) {
return userMono.doOnNext(person -> {
//向 map 集合里面放值
int id = users.size()+1;
users.put(id,person);
}).thenEmpty(Mono.empty());
}
}
(5)controller
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
//id 查询
@GetMapping("/user/{id}")
public Mono<User> geetUserId(@PathVariable int id) {
return userService.getUserById(id);
}
//查询所有
@GetMapping("/getusers")
public Flux<User> getUsers() {
return userService.getAllUser();
}
//查询所有,通过flux流的方式一条一条返回
@GetMapping(value ="/getusers/stream",produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> getUsersStream() {
return userService.getUsersStream();
}
//添加
@PostMapping("/saveuser")
public Mono<Void> saveUser(@RequestBody User user) {
Mono<User> userMono = Mono.just(user);
return userService.saveUserInfo(userMono);
}
}
(6)启动类
@SpringBootApplication
public class WebfluxdemoApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxdemoApplication.class,args);
}
}
主要是handler不再添加requestMapping。
2.1 Handler
类似于前面的Controller,只不过没有指定地址映射。
@Component
public class UserHandler {
private final UserService userService;
@Autowired
public setUserService(UserService userService) {
this.userService = userService;
}
//根据 id 查询
public Mono<ServerResponse> getUserById(ServerRequest request) {
//获取 id 值
int userId = Integer.valueOf(request.pathVariable("id"));
//空值处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
//调用 service 方法得到数据
Mono<User> userMono = this.userService.getUserById(userId);
//把 userMono 进行转换返回
//使用 Reactor 操作符 flatMap
return
userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person))
.switchIfEmpty(notFound);
}
//查询所有
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
//调用 service 得到结果
Flux<User> users = this.userService.getAllUser();
return
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(users);
}
//添加
public Mono<ServerResponse> saveUser(ServerRequest request) {
//得到 user 对象
Mono<User> userMono = request.bodyToMono(User.class);
return
ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}
}
2.2 创建Router路由,并初始化服务器做适配
2.2.1 普通方式启动
2.2.1.1 服务端
public class Server {
public static void main(String[] args) throws Exception{
Server server = new Server();
server.createReactorServer();
System.out.println("enter to exit");
System.in.read();
}
//1 创建Router路由
public RouterFunction<ServerResponse> routingFunction() {
//创建service和hanler对象
UserService userService = new UserServiceImpl();
UserHandler handler = new UserHandler();
handler.setUserService(userService);
//设置路由
return RouterFunctions.route(
RequestPredicates.GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
.andRoute( RequestPredicates.GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
}
//2 创建服务器完成适配
public void createReactorServer() {
//路由和handler适配
RouterFunction<ServerResponse> route = routingFunction();
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
//创建服务器
HttpServer httpServer = HttpServer.create();
httpServer.handle(adapter).bindNow();
}
}
2.2.1.2 客户端
public class Client {
public static void main(String[] args) {
//调用服务器地址
WebClient webClient = WebClient.create("http://127.0.0.1:5794");
//根据id查询
String id = "1";
User userresult = webClient.get().uri("/users/{id}", id)
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class)
.block();
System.out.println(userresult.getName());
//查询所有
Flux<User> results = webClient.get().uri("/users")
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
results.map(stu -> stu.getName())
.buffer().doOnNext(System.out::println).blockFirst();
}
}
2.2.2 springboot模式
@Configuration
public class FluxConfig {
private UserHandler userHandler;
@Autowired
public void setUserHandler(UserHandler userHandler) {
this.userHandler = userHandler;
}
@Bean
public RouterFunction<ServerResponse> routingFunction() {
//设置路由
return RouterFunctions.route(
RequestPredicates.GET("/users/{id}").and(accept(APPLICATION_JSON)),UserHandler::getUserById)
.andRoute( RequestPredicates.GET("/users").and(accept(APPLICATION_JSON)),UserHandler::getAllUsers);
}
}
// 主程序
@SpringBootApplication
public class WebfluxdemoApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxdemoApplication.class,args);
}
}
三、R2DBC
Spring WebFlux需要响应式的数据交互API,由于缺乏标准和驱动,Spring相关团队开始研究响应式关系数据库连接(R2DBC, Reactive Relational Database Connectivity),并提出了R2DBC规范API,现在支持的数据库包括PostgreSQL、H2、MSSQL、Mysql,除了驱动实现外,还提供了R2DBC连接池和R2DBC代理。
1、引入依赖
首先,在你的 pom.xml
文件中添加必要的依赖。你需要添加 Spring Data R2DBC 和具体的数据库驱动(例如 PostgreSQL)。
<dependencies>
<!-- Spring Boot Starter for WebFlux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Data R2DBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<!-- R2DBC Postgresql Driver -->
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-postgresql</artifactId>
</dependency>
<!-- R2DBC H2 Driver (for testing) -->
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、配置数据库连接
spring.r2dbc.url=r2dbc:postgresql://localhost:5432/mydatabase
spring.r2dbc.username=myuser
spring.r2dbc.password=mypassword
spring.r2dbc.pool.enabled=true
spring.r2dbc.pool.initial-size=5
spring.r2dbc.pool.max-size=20
spring.flyway.url=jdbc:postgresql://localhost:5432/mydatabase
spring.flyway.user=myuser
spring.flyway.password=mypassword
spring.flyway.locations=classpath:db/migration
3、创建实体类
创建一个实体类来表示数据库中的表。例如,创建一个 Person
实体类:
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
@Table("person")
public class Person {
@Id
private Long id;
private String name;
private int age;
// Getters and setters
}
4、创建Repository接口
创建一个 Repository 接口来定义数据库操作方法。Spring Data R2DBC 提供了类似于 Spring Data JPA 的 Repository 支持。
四、性能测试
//MVC
@GetMapping(value = "/hello")
public String hello() {
return "Hello!";
}
//Webflux
@GetMapping(value = "/hello")
public Mono<String> hello() {
return Mono.just("Hello!");
}
使用ab进行压测
ab -c 1000 -n 100000 http://172.16.65.146:8080/hello
测试结果
MVC /hello
Requests per second: 32393.74 [#/sec] (mean)
Time per request: 30.870 [ms] (mean)
Webflux /hello
Requests per second: 28907.11 [#/sec] (mean)
Time per request: 34.594 [ms] (mean)
从这个结果来说,两者性能不相上下,因为这里只是简单的返回一个字符串,并没有IO操作,因此Webflux不比MVC性能好也是应该的。
//MVC
@GetMapping(value = "/io")
public String redis() {
// 随机插入一个key, value
redisTemplate.opsForValue().set(RandomCodeGenerator.get(), "iotest");
return "Ok";
}
//Webflux
@GetMapping(value = "/io")
public Mono<String> redis() {
// 随机插入一个key, value
return reactiveRedisTemplate.opsForValue().set(RandomCodeGenerator.get(), "iotest")
.thenReturn("Ok");
}
使用ab进行测试
ab -c 1000 -n 100000 http://172.16.65.146:8080/io
测试结果如下
//Webflux
Requests per second: 27501.09 [#/sec] (mean)
Time per request: 36.362 [ms] (mean)
//MVC
Requests per second: 21461.32 [#/sec] (mean)
Time per request: 46.595 [ms] (mean)
因为这里测试的数据库是Redis,它实在是太快了,响应时间基本上都在2ms内。理论上来说,响应时间越长,阻塞IO的性能越差,非阻塞的优势就越明显。