R2DBC與WebFlux

WebFlux基于Netty,其異步復(fù)用線程模型與非阻塞IO帶來(lái)的優(yōu)勢(shì)相比SpringMVC/Tomcat技術(shù)棧筷凤,可以帶來(lái)更小的線程切換開(kāi)銷,更重要的是可以避免應(yīng)用間的級(jí)聯(lián)故障。對(duì)于Tomcat模型經(jīng)常需要考慮的問(wèn)題是線程池的參數(shù)調(diào)優(yōu)悼凑,以及線程池隔離來(lái)做故障模塊的隔離熔斷摊聋。而異步非阻塞技術(shù)棧天然避免了這些問(wèn)題鸡捐。

以往由于JDBC是同步阻塞協(xié)議的原因,WebFlux技術(shù)棧一般應(yīng)用于純請(qǐng)求轉(zhuǎn)發(fā)類的場(chǎng)景麻裁,在涉及到關(guān)系型數(shù)據(jù)的場(chǎng)景受限箍镜。有了R2DBC之后則補(bǔ)上了這塊拼圖源祈。

使用的demo應(yīng)用代碼

搭建一個(gè)WebFlux應(yīng)用,包含/user/getAllUser//user/slow/ 兩個(gè)接口色迂,應(yīng)用使用r2dbc連接MySQL數(shù)據(jù)庫(kù)香缺,實(shí)現(xiàn)全鏈路異步非阻塞。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.wangan</groupId>
    <artifactId>springbootone</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootone</name>
    <description>springbootone</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.3</spring-boot.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>dev.miku</groupId>
            <artifactId>r2dbc-mysql</artifactId>
            <version>0.8.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.17.RELEASE</version>
                <configuration>
                    <mainClass>com.wangan.springbootone.SpringbootoneApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

配置文件:

spring.application.name=springbootone
server.port=8080
spring.r2dbc.driver-class-name=com.mysql.jdbc.Driver
spring.r2dbc.username=root
spring.r2dbc.password=root
spring.r2dbc.url=r2dbc:pool:mysql://xxx.xx.xxx.187:3306/db_core?useSSL=false&useUnicode=true&characterEncoding=UTF8&autoReconnect=true

repository

public interface UserRepository extends ReactiveCrudRepository<User, Integer> {

    @Query(value = "select sleep(10) ")
    public Mono<User> slowQuery();
}

entity

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("webflux_user")
public class User {
    @Id
    private int id;
    private String username;
    private String password;
}

service

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public Mono<User> addUser(User user) {
        return userRepository.save(user);
    }

    public Mono<ResponseEntity<Void>> delUser(int id) {
        return userRepository.findById(id)
                .flatMap(user -> userRepository.delete(user).then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))))
                .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
    }

    public Mono<ResponseEntity<User>> updateUser(User user) {
        return userRepository.findById(user.getId())
                .flatMap(user0 -> userRepository.save(user))
                .map(user0 -> new ResponseEntity<User>(user0, HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<User>(HttpStatus.NOT_FOUND));
    }

    public Flux<User> getAllUser() {
        return userRepository.findAll();
    }

    public Mono<ResponseEntity<User>>  slowFunction(){
        return userRepository.slowQuery()
                //.flatMap(user0 -> userRepository.save(user))
                .map(user0 -> new ResponseEntity<User>(user0, HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<User>(HttpStatus.NOT_FOUND));
    }

}

controller

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "getAllUser", method = RequestMethod.GET)
    public Flux<User> getAllUser() {
        return userService.getAllUser();
    }

    @RequestMapping(value = "slow", method = RequestMethod.GET)
    public Mono<ResponseEntity<User>>  slow() {
        return userService.slowFunction();
    }

}
異步非阻塞能力測(cè)試

為了實(shí)驗(yàn)方便歇僧,先把webflux的工作線程設(shè)置為1:

@Configuration
public  class ReactNettyConfiguration {

    @Bean
    public ReactorResourceFactory reactorClientResourceFactory() {
        System.setProperty("reactor.netty.ioSelectCount","1");

        // 這里工作線程數(shù)為2-4倍都可以图张。看具體情況
        //int ioWorkerCount = Math.max(Runtime.getRuntime().availableProcessors()*3, 4));
        System.setProperty("reactor.netty.ioWorkerCount",String.valueOf(1));
        return new ReactorResourceFactory();
    }
}

此時(shí)應(yīng)用內(nèi)的線程情況诈悍,只有一個(gè)工作線程來(lái)處理請(qǐng)求:

jvisualvm查看應(yīng)用線程

兩個(gè)接口

localhost:8080/user/slow

localhost:8080/user/getAllUser/

先調(diào)用slow接口(用select sleep(10)模擬的由慢SQL導(dǎo)致的慢接口)祸轮,再調(diào)用getAllUser,后者可以馬上返回侥钳。同樣的測(cè)試案例和配置适袜,如果使用SpringMVC + JDBC的組合就會(huì)發(fā)生slow接口10秒后返回,此時(shí)getAllUser才可以返回舷夺。

后續(xù)
  • 關(guān)于遠(yuǎn)程調(diào)用的異步非阻塞:上述應(yīng)用中使用r2dbc調(diào)用MySQL苦酱,如果該應(yīng)用需要調(diào)用遠(yuǎn)程的Http服務(wù),則需要使用WebClient替代RestTemplate冕房、Apache HttpClient之類的傳統(tǒng)Http客戶端組件躏啰,實(shí)現(xiàn)全鏈路異步非阻塞。如果是該應(yīng)用需要調(diào)用的后端服務(wù)是Dubbo服務(wù)耙册,理論上只要將Dubbo客戶端調(diào)用結(jié)果轉(zhuǎn)為Reactive Streams返回仍然可以實(shí)現(xiàn)上述非阻塞調(diào)用效果给僵。
  • (未完成)上述例子中,我們利用新的連接驅(qū)動(dòng)r2dbc代替了jdbc详拙,實(shí)現(xiàn)了異步非阻塞的與數(shù)據(jù)庫(kù)網(wǎng)絡(luò)通信帝际,然后這里其實(shí)也可以使用個(gè)連接池,見(jiàn):Data Access with R2DBC :: Spring Framework “When you use Spring’s R2DBC layer, you can configure your own with a connection pool implementation provided by a third party. A popular implementation is R2DBC Pool (). Implementations in the Spring distribution are meant only for testing purposes and do not provide pooling.”
參考

https://docs.spring.io/spring-framework/reference/data-access/r2dbc.html

SpringBoot之Webflux&R2DBC操作MySQL

Spring Cloud Gateway 雪崩了饶辙,我 TM 人傻了 - 知乎 (zhihu.com)

https://blog.csdn.net/weixin_42182797/article/details/117216371

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蹲诀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弃揽,更是在濱河造成了極大的恐慌脯爪,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矿微,死亡現(xiàn)場(chǎng)離奇詭異痕慢,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)涌矢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門掖举,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人娜庇,你說(shuō)我怎么就攤上這事塔次》嚼海” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵励负,是天一觀的道長(zhǎng)藕溅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)熄守,這世上最難降的妖魔是什么蜈垮? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮裕照,結(jié)果婚禮上攒发,老公的妹妹穿的比我還像新娘。我一直安慰自己晋南,他們只是感情好惠猿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著负间,像睡著了一般偶妖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上政溃,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天趾访,我揣著相機(jī)與錄音,去河邊找鬼董虱。 笑死扼鞋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愤诱。 我是一名探鬼主播云头,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淫半!你這毒婦竟也來(lái)了溃槐?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤科吭,失蹤者是張志新(化名)和其女友劉穎昏滴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體对人,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡影涉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了规伐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匣缘,死狀恐怖猖闪,靈堂內(nèi)的尸體忽然破棺而出鲜棠,到底是詐尸還是另有隱情,我是刑警寧澤培慌,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布豁陆,位于F島的核電站,受9級(jí)特大地震影響吵护,放射性物質(zhì)發(fā)生泄漏盒音。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一馅而、第九天 我趴在偏房一處隱蔽的房頂上張望祥诽。 院中可真熱鬧,春花似錦瓮恭、人聲如沸雄坪。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)维哈。三九已至,卻和暖如春登澜,著一層夾襖步出監(jiān)牢的瞬間阔挠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工脑蠕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留购撼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓空郊,卻偏偏與公主長(zhǎng)得像份招,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狞甚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容