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。

2、webflux适应场景

(1)io密集型应用

(2)对并发性要求高

(3)延迟较高的网络请求

(4)支持响应式数据库驱动

二、spring webflux的使用

1、基于注解模式使用

(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);
    }
}

2、基于函数式编程

主要是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 支持。

 

四、性能测试

1、基准性能测试

//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性能好也是应该的。

2、简单IO性能测试

//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的性能越差,非阻塞的优势就越明显。

 




相关推荐

(1)Spring MVC是一个基于DispatcherServlet的MVC框架,DispatcherServlet是继承自HttpServlet的。Spring的IOC和AOP主要就用了java的

当我们进行web开发的时候总是看到配置的log4j日志在控制台无法显示mybatis的主要是指sql、参数、结果,出现这种问题有两种情况: 情部1:</strong

一、spring5之前的springMVC异常处理机制 1、使用@ExceptionHandler 用于局部方法捕获,与抛出异常的方法处于同一个Controller类。 @Controller pub

一、简介 1、背压 可以理解为,生产者可以感受到消费者反馈的消费压力,并根据压力进行动态调整生产速率。 2、响应流 (1)必须是无阻塞

一、简介 Spring Cache 提供了 @Cacheable 、@CachePut 、@CacheEvict 、@Caching 等注解,在方法上使用。 核心