spring webflux自定義netty 參數解析
自定義 webflux 容器配置
配置代碼
@Component public class ContainerConfig extends ReactiveWebServerFactoryCustomizer { public ContainerConfig(ServerProperties serverProperties) { super(serverProperties); } @Override public void customize(ConfigurableReactiveWebServerFactory factory) { super.customize(factory); NettyReactiveWebServerFactory nettyFactory = (NettyReactiveWebServerFactory) factory; nettyFactory.setResourceFactory(null); nettyFactory.addServerCustomizers(server -> server.tcpConfiguration(tcpServer -> tcpServer.runOn(LoopResources.create("mfilesvc", Runtime.getRuntime().availableProcessors() * 4, Runtime.getRuntime().availableProcessors() * 8, true)) .selectorOption(CONNECT_TIMEOUT_MILLIS, 200) ).channelGroup(new ChannelGroup()) ); } @Override public int getOrder() { return -10; } }
服務重啟時 報錯
SpringContextShutdownHook Socket couldn’t be stopped within 3000ms
解決方案
初識Spring WebFlux
在我的認識中,大部分人都在用SpringMVC(包括我自己)。在最近的學習中,發現spring5中有一個和SpringMVC平級的東西Spring WebFlux,接下來初步認識一下這是個什麼東東?
Spring Web新的改變
眾所周知Spring MVC是同步阻塞的IO模型,當我們在處理一個耗時的任務時,如上傳文件,服務器的處理線程會一直處於等待狀態,等待文件的上傳,這期間什麼也做不瞭,等到文件上傳完畢後可能需要寫入,寫入的過程線程又隻能在那等待,非常浪費資源。為瞭避免這類資源的浪費,Spring WebFlux應運而生,在Spring WebFlux中若文件還沒上傳完畢,線程可以先去做其他事情,當文件上傳完畢後會通知線程,線程再來處理,後續寫入也是類似的,通過異步非阻塞機制節省瞭系統資源,極大的提高瞭系統的並發量。這兩種形式,是不是像極瞭BIO和NIO這兩種形式,實際上,SpringMVC和Spring WebFlux也就是這兩種IO特點的體現。
以下為官網的介紹:
Spring WebFlux的特性
1.異步非阻塞
如上文所說,線程不需要一直處於等待狀態,Spring WebFlux很好的體現瞭NIO的異步非阻塞思想。
2.響應式(reactive)編程
響應式編程是一種新的編程風格,其特點是異步和並發、事件驅動、推送PUSH機制一級觀察者模式的衍生。reactive引用允許開發者構建事件驅動,可擴展性,彈性的反應系統:提供高度敏感的實時用戶體驗感覺,可伸縮性和彈性的引用程序棧的支持,隨時可以部署在多核和雲計算架構。
Reactive的主要接口:
- Publisher:發佈者,數據的生產端
- Subscriber:消費者,此處可以定義獲取到數據後響應的操作
- Processor:消費者與發佈者之間的數據處理
- back pressure:背壓,消費者告訴發佈者自己能處理多少數據
消費者的回調方法:
- onSubscribe:訂閱關系處理,用它來響應發佈者
- onNext:接收到數據後會響應的方法
- onError:出現錯誤時處理的方法
- onComplete:任務完成後響應的方法
3.適配多種web容器
既然Spring WebFlux很好的體現瞭NIO的異步非阻塞思想。作為首屈一指的NIO框架netty,便是Spring WebFlux默認的運行容器。此外,大傢熟悉的Tomcat、Jetty等Servlet容器,也能運行Spring WebFlux,前提是容器需要支持Servlet3.1,因為非阻塞IO是使用瞭Servlet3.1的特性。
Spring WebFlux簡單實踐
本文默認開發環境是JDK8,開發工具是IDEA。實踐分兩部分內容,第一部分與SpringMVC對比開發中的不一樣的地方;第二部分為Spring WebFlux獨有的響應式編程的簡單實踐。
在webflux中,Mono代表返回0或1個元素(相當於一個對象)。Flux代表返回0-n個元素(相當於集合)
1.工程創建
新建springboot工程。
選擇Web -> Spring Reactive Web 創建
或者在springboot工程中pom文件添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
2.Controller中與SpringMVC的對比
在controller中,webflux的寫法可以和springMVC的寫法類似
@RestController @Slf4j public class UserController { @RequestMapping("/index") public String index(){ log.info("springmvc index begin"); String result="cc666"; log.info("springmvc index end"); return result; } @RequestMapping("/index2") public Mono<String> index2(){ log.info("webflux index begin"); Mono<String> result=Mono.just("666cc"); log.info("webflux index end"); return result; } }
3.異步非阻塞的體現
上面已經實現瞭初步的數據返回,不過webflux和springmvc目前來看沒有什麼區別,已知springmvc線程執行時會阻塞的,webflux線程是異步非阻塞的。下面修改一下代碼,在獲取數據的時候加一些額外的耗時操作,看看webflux是否是真的異步非阻塞
@RestController @Slf4j @AllArgsConstructor public class UserController { /** * 模擬耗時查詢操作 */ public String createStr(){ try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } return "cc666cc"; } @RequestMapping("/index") public String index(){ log.info("springmvc index begin"); String result=this.createStr(); log.info("springmvc index end"); return result; } @RequestMapping("/index2") public Mono<String> index2(){ log.info("webflux index begin"); Mono<String> result=Mono.fromSupplier(()->this.createStr()); log.info("webflux index end"); return result; } }
通過日志結果,可以很明顯的發現,雖然前端頁面展示的效果是一樣的,但springmvc是等待後返回結果;而webflux是先執行,等有結果後,再返回結果。由此體現瞭webflux異步非阻塞的特性
springmvc
2020-08-04 21:28:57.430 INFO 14156 — [ctor-http-nio-2] c.w.webflux.controller.UserController : springmvc index begin
2020-08-04 21:29:00.430 INFO 14156 — [ctor-http-nio-2] c.w.webflux.controller.UserController : springmvc index end
webflux
2020-08-04 21:29:09.640 INFO 14156 — [ctor-http-nio-2] c.w.webflux.controller.UserController : webflux index begin
2020-08-04 21:29:09.641 INFO 14156 — [ctor-http-nio-2] c.w.webflux.controller.UserController : webflux index end
4.添加數據庫支持
對於數據庫的支持,webflux用到的是r2dbc這樣一個東西。
R2DBC(Reactive Relational Database Connectivity)是一個使用反應式驅動集成關系數據庫的孵化器。Spring Data R2DBC運用熟悉的Spring抽象和repository 支持R2DBC。基於此,在響應式程序棧上使用關系數據訪問技術,構建由Spring驅動的程序將變得非常簡單。
在pom中引入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <dependency> <groupId>com.github.jasync-sql</groupId> <artifactId>jasync-r2dbc-mysql</artifactId> <version>1.1.3</version> </dependency>
在application.yml中加入數據源:
spring: r2dbc: url: r2dbc:mysql://127.0.0.1:3306/study username: xxx password: xxx
5.Dao的編寫
Dao的編寫和springmvc中類似,本文中繼承瞭ReactiveCrudRepository類,是Repository的一個實現類,其中實現瞭簡單的crud操作,model和dao的實現:
@Table("user") @Data @AllArgsConstructor @NoArgsConstructor public class User { @Id private Long id; private String username; private String password; }
public interface UserDao extends ReactiveCrudRepository<User,Long> { }
6.Controller的編寫
controller的編寫還是和springmvc類似,這裡采用RESTful的方式
@RestController @AllArgsConstructor public class UserController { private final UserDao userDao; @GetMapping("/findAll") public Flux<User> findAll(){ return userDao.findAll(); } @PostMapping("/save") public Mono save(@RequestBody User user){ return this.userDao.save(user); } @DeleteMapping("/delete/{id}") public Mono delete(@PathVariable Long id){ return this.userDao.deleteById(id); } @GetMapping("/get/{id}") public Mono get(@PathVariable Long id){ return this.userDao.findById(id); } }
7.響應式編程Handler的編寫
在使用上,webflux可以和springmvc類似,不過webflux也有自己的一套響應式編程的寫法,先定義handler,類似於controller,不過隻有業務處理的代碼,其中就用到瞭reactive模擬的request(ServerRequest )和response(ServerResponse),同樣的實現簡單的crud功能:
@Component @AllArgsConstructor public class UserHandler { private final UserDao userDao; public Mono<ServerResponse> saveUser(ServerRequest request){ Mono<User> mono=request.bodyToMono(User.class); User user = mono.block(); return ServerResponse.ok().build(this.userDao.save(user).then()); } public Mono<ServerResponse> deleteById(ServerRequest request){ Long id=Long.parseLong(request.pathVariable("id")); return ServerResponse.ok().build(this.userDao.deleteById(id).then()); } public Mono<ServerResponse> getByid(ServerRequest request){ Long id=Long.parseLong(request.pathVariable("id")); Mono<User> mono = this.userDao.findById(id); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(mono,User.class); } public Mono<ServerResponse> findAll(ServerRequest request){ Flux<User> all = this.userDao.findAll(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(all,User.class); } }
8.響應式編程Route的編寫
在handler中我們編寫瞭處理代碼,但是怎麼通過請求地址訪問呢?
學習過vue的老鐵們應該知道,vue是通過路由定義地址和頁面的對應關系的,vue也是響應式編程的一種體現。webflux中也是通過類似的方式來實現的,在Route中定義規則:
@Configuration public class UserRoute { @Bean public RouterFunction<ServerResponse> routeUser(UserHandler userHandler){ return RouterFunctions .route(RequestPredicates.GET("findAll2") .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),userHandler::findAll) .andRoute(RequestPredicates.GET("/get2/{id}") .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),userHandler::getByid) .andRoute(RequestPredicates.DELETE("/delete2/{id}") .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),userHandler::deleteById) .andRoute(RequestPredicates.POST("/save2") .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),userHandler::saveUser); } }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- SpringBoot深入分析webmvc和webflux的區別
- Spring WebFlux實現參數校驗的示例代碼
- spring單例如何改多例
- Spring WebFlux的使用指南
- 詳細瞭解MVC+proxy