0. 為什么需要找到Blocking call
我們使用reactor編程時吆鹤,其目的就是希望我們的程序符合異步非阻塞的模型,為了達到這個目的巍棱,我們希望我們程序中所有的方法都是非阻塞的方法(理想狀態(tài))雇寇,比如我們在處理JDBC鏈接時,會考慮使用Schedulers來包裹或是使用R2DBC胶果,那么在響應式編程中,我們會遇到形形色色的阻塞方法斤斧,此時早抠,我們就需要用合理的方式處理它們了.
1. 解決方案
BlockHound
2. Git 地址
https://github.com/reactor/BlockHound
3. 大致原理
類似于Java代理,再入口函數(shù)調(diào)用前被JVM加載撬讽,一旦BlockHound啟動蕊连,其將標記阻塞方法(例如: sleep()) .并改變其behaviour而拋出一個Error
4. 引入BlockHound
在自己的工程中引入BlockHound
4.1. maven
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>1.0.0.RC1</version>
<scope>test</scope>
</dependency>
4.2. Gradle
repositories {
mavenCentral()
// maven { url 'https://repo.spring.io/milestone' }
// maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
testCompile 'io.projectreactor.tools:blockhound:$LATEST_RELEASE'
// testCompile 'io.projectreactor.tools:blockhound:$LATEST_MILESTONE'
// testCompile 'io.projectreactor.tools:blockhound:$LATEST_SNAPSHOT'
}
5. 使用示例
public class DetectBlockingCall {
@BeforeEach
void setUp() {
// 1. 初始化BlockHound
BlockHound.install();
}
// 2. 定義一個阻塞方法
void blockingCall() {
Mono.delay(Duration.ofSeconds(1))
.doOnNext(it -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
})
.block();
}
@Test
void blockHoundSimpleTest() {
//3. 調(diào)用阻塞方法
Throwable throwable = Assertions.assertThrows(Throwable.class, this::blockingCall);
//4. 驗證阻塞方法是否拋出異常
Assertions.assertTrue(throwable.getMessage().contains("Blocking call!"));
}
}
在這個示例中,第一步加載BlockHound實際是可以省略的锐秦,因為我們引入BlockHound到junit 實際是已經(jīng)被預加載, 大家可以去除這一步再次執(zhí)行測試代碼嘗試
6. 構(gòu)建項目時自動執(zhí)行BlockHound
往往我們希望我們自身的項目可以自動執(zhí)行BlockHound咪奖,從而每次運行測試代碼便可以知道我們的代碼問題在哪里盗忱,那么這里提供一種思路酱床,即使用項目構(gòu)建工具來執(zhí)行BlockHound, 以Gradle為例.
6.1. 編寫定制化BlockHound模塊 (當然你可以不定制化)
在開發(fā)中,往往我們不可避免的使用部分部分阻塞方法趟佃,那么此時我們需要測試時排除這些方法. 此時我們可以定義一些定制化類扇谣,例如:
新建一個工程com.test.support昧捷, 新建一個模塊叫做blockhound-integration, 然后新建一個Log的忽略類
public class LogBlockHoundIntegration implements BlockHoundIntegration {
// 使用系統(tǒng)變量來達到開關(guān)的目的
private static final boolean ENABLED = Boolean.parseBoolean(System.getProperty("LogBlockHoundIntegration.enabled", Boolean.FALSE.toString()));
@Override
public void applyTo(BlockHound.Builder builder) {
if (!ENABLED) {
return;
}
// 加入要忽略的阻塞方法
builder.allowBlockingCallsInside(
"ch.qos.logback.classic.Logger",
"buildLoggingEventAndAppend");
}
}
6.2. 定義測試監(jiān)聽類
實現(xiàn)TestExecutionListener, 靜態(tài)加載BlockHound,使得所有測試方法都需要加載BlockHound
public class BlockHoundTestExecutionListener implements TestExecutionListener {
static {
BlockHound.install(builder -> {
builder.blockingMethodCallback(method -> {
Error error = new BlockingOperationError(method);
error.printStackTrace(System.err);
throw error;
});
});
}
}
6.3. 在自己模塊的gradle文件中定義方法罐寨,引入我們的定義及默認的junit平臺
ext {
// add helper to activate Reactor BlockHound, https://github.com/reactor/BlockHound
useReactorBlockHound = { ->
project.dependencies {
testRuntimeOnly 'com.test.support:blockhound-integration', 'org.junit.platform:junit-platform-launcher'
}
}
}
6.4. 定義執(zhí)行操作入口
build.gradle 中插入
subprojects { subproject ->
subproject.useReactorBlockHound()
}
// 打開我們自己定義的生效類
tasks.withType(Test) {
// ignore the blocking nature of Log
systemProperty 'LogBlockHoundIntegration.enabled', 'true'
}
至此靡挥,我們基本可以滿足gradle項目開發(fā)中所需要的自動化測試了。如果你在使用maven鸯绿,可以構(gòu)建自己的maven插件跋破,來實現(xiàn)自動化流程,具體邏輯與gradle是類似的
6. 結(jié)論
響應式編程是基于我們想充分利用異步非阻塞而產(chǎn)生的一種設(shè)計瓶蝴,但如今我理解技術(shù)正處于一個轉(zhuǎn)型期毒返,往往我們會遇到阻塞+非阻塞的囧境,為了解決這個問題舷手,今天引入BlockHound工具來探測我們程序中潛在的阻塞API拧簸,使我們更快的發(fā)現(xiàn)問題并做出調(diào)整.