Initial commit
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# 差别
|
||||
|
||||
Spring WebFlux 和传统的 Spring MVC (使用@Controller 和@RequestMapping 等注解定义的控制器)之间主要的区别在于它们对于处理请求的方式不同,这主要体现在响应式编程范式和阻塞式编程范式的差异上。下面我将通过几个方面来进行通俗说明。
|
||||
|
||||
## 编程模型
|
||||
|
||||
**Spring MVC**:
|
||||
- **阻塞式 I/O 操作**:每个 HTTP 请求通常在一个单独的线程中被处理,如果请求处理涉及到长时间的 I/O 操作(如数据库查询),那么该线程会在操作完成之前处于阻塞状态。
|
||||
- **注解驱动**:使用 `@Controller` 和 `@RequestMapping` 之类的注解来定义路由和处理方法,这些注解通常与同步操作一起使用。
|
||||
|
||||
**Spring WebFlux**:
|
||||
- **非阻塞式 I/O 操作**:使用响应式编程模型,即使操作是长时间运行的,也不会阻塞线程。而是在操作可以继续进行时,以回调的方式恢复执行。
|
||||
- **函数式端点定义**:虽然 WebFlux 也支持与 Spring MVC 相同的注解方式,但它引入了一个新的函数式编程模型,可以使用路由器函数(RouterFunction)和处理器函数(HandlerFunction)来声明路由和处理逻辑。
|
||||
|
||||
## 底层运行库
|
||||
|
||||
- **Spring MVC**:通常运行在阻塞式的 Web 服务器上,如 Tomcat。
|
||||
- **Spring WebFlux**:可以运行在支持非阻塞运行环境的服务器上,如 Netty、Undertow。
|
||||
|
||||
## 性能和资源利用
|
||||
|
||||
- **Spring MVC**:在高负载时,每个请求占用一个线程,可能会导致线程资源耗尽。
|
||||
- **Spring WebFlux**:响应式编程使得可以用较少的线程来处理大量并发请求,从而提高了线程的利用率。
|
||||
|
||||
## 应用场景
|
||||
|
||||
- **Spring MVC**:适用于传统的、请求 - 响应式的 Web 应用程序,特别是当你有很多的同步操作时。
|
||||
- **Spring WebFlux**:适用于需要处理大量并发连接,以及要求低延迟 & 高吞吐量的实时性应用,例如实时数据处理、在线游戏后端等。
|
||||
|
||||
## 影响
|
||||
|
||||
- **开发风格**:WebFlux 的函数式风格可能需要开发者学习新的编程范式,而 Spring MVC 的注解风格更符合大多数 Java 开发者的习惯。
|
||||
- **可维护性**:WebFlux 的函数式风格可以带来更好的可测试性和可维护性,但这取决于开发者对响应式编程的熟悉程度。
|
||||
- **响应性**:WebFlux 更适合做到背压和流式数据处理,可以更好地支持响应式系统的需求。
|
||||
|
||||
总结来说,Spring WebFlux 和 Spring MVC 提供了不同的编程模型来适应不同的应用场景。WebFlux 响应式编程模型适合处理大量并发请求和实时交互,而传统的 Spring MVC 更适合标准的、阻塞式的 Web 应用开发。选择使用哪个,取决于你的具体需求和开发团队的技术偏好。
|
||||
|
||||
# 非阻塞式 I/O 操作
|
||||
|
||||
非阻塞式 I/O 操作:使用响应式编程模型,即使操作是长时间运行的,也不会阻塞线程。而是在操作可以继续进行时,以回调的方式恢复执行。
|
||||
|
||||
这个特点。
|
||||
|
||||
下面是一个简单的 Spring WebFlux 的示例,展示了如何创建一个非阻塞的 RESTful 接口,该接口模拟了一个长时间运行的操作,比如查询数据库,但在这个示例中我们将使用 `Mono.delay` 来模拟这个耗时操作。
|
||||
|
||||
请注意,此代码需要在 Spring WebFlux 项目中运行,且 Spring Boot 版本应支持 WebFlux。
|
||||
|
||||
首先,我们需要一个模型,这里我们使用一个非常简单的 `Greeting` 类:
|
||||
|
||||
```java
|
||||
public class Greeting {
|
||||
private String message;
|
||||
|
||||
public Greeting(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
// getters and setters
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接着,我们创建一个处理器(handler)来处理 HTTP 请求:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class GreetingHandler {
|
||||
|
||||
public Mono<ServerResponse> hello(ServerRequest request) {
|
||||
// Mono.delay(Duration.ofSeconds(3)) 模拟一个长时间运行的操作
|
||||
Mono<Greeting> greetingMono = Mono.delay(Duration.ofSeconds(3))
|
||||
.map(l -> new Greeting("Hello, Spring WebFlux!"));
|
||||
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
|
||||
.body(greetingMono, Greeting.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在,我们需要定义路由来关联 URL 路径和处理器:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class GreetingRouter {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {
|
||||
return RouterFunctions
|
||||
.route(RequestPredicates.GET("/hello"), greetingHandler::hello);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后,这是 `SpringWebFluxDemoApplication` 的主类,用以启动 Spring Boot 应用:
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
public class SpringWebFluxDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringWebFluxDemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
要测试非阻塞行为,请在浏览器或使用 curl 访问 `http://localhost:8080/hello`。
|
||||
|
||||
在我们的 `GreetingHandler` 中,`Mono.delay(Duration.ofSeconds(3))` 模拟了一个长时间运行的 I/O 操作,这个操作在 3 秒后完成。这里的关键是,`Mono.delay` 创建了一个非阻塞的延迟操作,这意味着在这 3 秒内,执行这个操作的线程不会被阻塞,它可以去做其他的事情。
|
||||
|
||||
要测试非阻塞行为,请在浏览器或使用 curl 访问 `http://localhost:8080/hello`。即便我们在 `GreetingHandler` 中模拟了一个 3 秒的延迟,你会发现服务仍然可以接收和处理其他的请求。如果你并行地从多个终端或浏览器窗口发送请求,你会看到它们几乎是同时响应的,而不是一个接一个地阻塞等待。
|
||||
|
||||
为了完整起见,以下是使用 curl 命令行测试的方式:
|
||||
|
||||
```shell
|
||||
curl http://localhost:8080/hello
|
||||
```
|
||||
|
||||
如果你从多个终端窗口同时发出多个请求,你应该会发现响应时间几乎相同,这说明服务器没有因为模拟的延迟操作而阻塞其他请求。
|
||||
|
||||
这个简单的 demo 展示了 WebFlux 如何处理长时间运行的操作。在实际的生产环境中,类似的非阻塞操作可能包括数据库查询、远程服务调用等,这些操作可以利用响应式编程模型完成,而不会导致线程阻塞,从而提高了应用程序的性能和伸缩性。
|
||||
|
||||
# 真实场景
|
||||
|
||||
那真实的业务场景一般是怎么设计的?也是用 Mono.delay 吗?可以给我一个简答的真实场景的示例方案吗?比如一个数据查询接口,当收到请求后需要去大数据查询数据。查询大数据是比较耗时的。
|
||||
|
||||
当然可以。在许多微服务架构或分布式系统中,经常需要通过 HTTP 请求从其他服务或平台获取数据。如果你想在响应式编程模型中保持非阻塞式调用,你可以使用 Spring WebFlux 的 `WebClient`,这是一个响应式的 HTTP 客户端,用于执行 HTTP 请求并处理响应。
|
||||
|
||||
下面是如何使用 Spring WebFlux 的 `WebClient` 从另一个服务获取数据的示例:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class BigDataService {
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
public BigDataService(WebClient.Builder webClientBuilder) {
|
||||
this.webClient = webClientBuilder.baseUrl("http://bigdata-service").build();
|
||||
}
|
||||
|
||||
public Mono<BigDataResponse> queryBigData(String queryParam) {
|
||||
return webClient.get()
|
||||
.uri(uriBuilder -> uriBuilder.path("/query")
|
||||
.queryParam("param", queryParam)
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(BigDataResponse.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在这个服务中,我们创建了一个 `WebClient` 实例,它被配置为指向大数据平台的服务。`queryBigData` 方法通过 HTTP GET 请求发送查询参数,非阻塞地从大数据平台请求数据。返回的对象 `Mono<BigDataResponse>` 是一个响应式的单值流,它将在异步操作完成时发出一个 `BigDataResponse` 对象。
|
||||
|
||||
然后你可以在控制层调用这个服务:
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/data")
|
||||
public class DataController {
|
||||
|
||||
private final BigDataService bigDataService;
|
||||
|
||||
@Autowired
|
||||
public DataController(BigDataService bigDataService) {
|
||||
this.bigDataService = bigDataService;
|
||||
}
|
||||
|
||||
@GetMapping("/query")
|
||||
public Mono<ResponseEntity<BigDataResponse>> queryData(@RequestParam String param) {
|
||||
return bigDataService.queryBigData(param)
|
||||
.map(response -> ResponseEntity.ok(response))
|
||||
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里,`DataController` 调用 `BigDataService` 的 `queryBigData` 方法,并将返回的 `Mono` 直接作为响应返回。Spring 框架会处理这个 `Mono`,当大数据平台返回结果时,将其序列化并发送到客户端。
|
||||
|
||||
使用 `WebClient`,Spring WebFlux 保证了整个 HTTP 调用过程是非阻塞的,即使是在等待远程大数据平台的响应时,也不会占用线程资源,这使得应用能够高效处理其他并发请求。
|
||||
Reference in New Issue
Block a user