我在使用Reactor时发现了一些奇怪的行为。场景是这样的:
似乎发生的事情是onSubscribe(FluxMap.MapSubscriber)在第一次API调用时被调用两次,然后打开两个连接并产生两个结果。传递给第二个API调用的结果是非确定性的,取决于第二个API调用是在前两个调用中的第二个调用完成之前还是之后执行。
这是使用Kotlin和Springboot WebClient重现问题的代码示例。 API端点根据路径参数生成单个GUID或多个GUID。我使用第一次调用结果中的第一个数字作为第二个调用中的路径参数:
val api = "https://www.uuidgenerator.net/api/guid"
val client = WebClient.builder()
.baseUrl(api)
.build()
@Test
public fun reactorBug() {
val firstResult = callApi().doOnSuccess { r -> println("callApi returned: $r") }
val secondResult = callApi(firstResult).doOnSuccess { r -> println("callApi(result) returned: $r") }
println(Mono.zip(firstResult, secondResult, { first, second -> "First result is ${first}Second result is $second" }).block())
}
private fun callApi(): Mono<String> {
println("Calling Api")
return client.get().retrieve().bodyToMono()
}
private fun callApi(number: Int): Mono<String> {
println("Calling Api with $number")
return client.get().uri("/{number}", number).retrieve().bodyToMono()
}
private fun callApi(firstResult: Mono<String>): Mono<String> {
println("Extracting number from first result")
return firstResult
.map { guid -> guid.find { c -> c.isDigit() } }
.map { Character.getNumericValue(it!!) }
.flatMap { i -> callApi(i) }
}
这是一个说明问题的示例输出:
致电Api
从第一个结果中提取数字
callApi返回:12ec857b-e42c-42ab-a7a2-69beb9a377e3
callApi返回:5eedefa5-73b5-4995-aef3-8621e31b698d &lt; - 此结果不应该发生
用5 &lt; - 调用Api,这应该是1,而不是5 callApi(result)返回:
01c64488-6a8c-4400-9094-6729c64a4e1a
0179beae-d2b4-40b6-8489-52fa58deb25f
8f814b1d-594C-4392-a4f5-04d417367add
45891d71-61b2-4d5b-81ad-2cfd8e453377
08edf0c3-3614-402b-8b17-000fdedce1a0
第一个结果是12ec857b-e42c-42ab-a7a2-69beb9a377e3
第二个结果是 01c64488-6a8c-4400-9094-6729c64a4e1a
0179beae-d2b4-40b6-8489-52fa58deb25f
8f814b1d-594C-4392-a4f5-04d417367add
45891d71-61b2-4d5b-81ad-2cfd8e453377
08edf0c3-3614-402b-8b17-000fdedce1a0
编辑调试输出:
30-01-2018 22:36:11.889 [main] DEBUG o.s.web.reactive.function.client.debug - onSubscribe(FluxMap.MapSubscriber)
30-01-2018 22:36:11.920 [main] DEBUG o.s.web.reactive.function.client.debug - request(unbounded)
30-01-2018 22:36:11.924 [main] DEBUG io.netty.util.NetUtil.debug - -Djava.net.preferIPv4Stack:false
30-01-2018 22:36:11.925 [main] DEBUG io.netty.util.NetUtil.debug - -Djava.net.preferIPv6Addresses:false
30-01-2018 22:36:12.128 [main] DEBUG io.netty.util.NetUtil.debug - Loopback接口:lo(软件环回接口1,127.0.0.1)
30-01-2018 22:36:12.129 [main] DEBUG io.netty.util.NetUtil.debug - 无法从sysctl和文件\ proc \ sys \ net \ core \ somaxconn获取SOMAXCONN。默认值:200
30-01-2018 22:36:12.146 [main] DEBUG r.i.n.r.DefaultLoopEpollDetector.debug - 默认epoll支持:false
30-01-2018 22:36:12.156 [main] DEBUG r.i.n.resources.DefaultPoolResources.debug - www.uuidgenerator.net/173.255.225.224:443的新http客户端池
30-01-2018 22:36:12.190 [main] DEBUG io.netty.channel.DefaultChannelId.debug - -Dio.netty.processId:4232(自动检测)
30-01-2018 22:36:12.396 [main] DEBUG io.netty.channel.DefaultChannelId.debug - -Dio.netty.machineId:78:e4:00:ff:fe:bf:a5:cb(自动检测) )
30-01-2018 22:36:12.447 [main] DEBUG io.netty.buffer.ByteBufUtil.debug - -Dio.netty.allocator.type:pooled
30-01-2018 22:36:12.448 [main] DEBUG io.netty.buffer.ByteBufUtil.debug - -Dio.netty.threadLocalDirectBufferSize:65536
30-01-2018 22:36:12.448 [main] DEBUG io.netty.buffer.ByteBufUtil.debug - -Dio.netty.maxThreadLocalCharBufferSize:16384
30-01-2018 22:36:12.459 [main] DEBUG r.i.n.c.PooledClientContextHandler.debug - 从池中获取现有通道:DefaultPromise @ d23e4a(不完整)SimpleChannelPool {activeConnections = 1}
30-01-2018 22:36:12.461 [main] DEBUG o.s.web.reactive.function.client.debug - onSubscribe(FluxMap.MapSubscriber)
30-01-2018 22:36:12.462 [main] DEBUG o.s.web.reactive.function.client.debug - request(unbounded)
30-01-2018 22:36:12.463 [main] DEBUG r.i.n.c.PooledClientContextHandler.debug - 从池中获取现有通道:DefaultPromise @ c8295b(不完整)SimpleChannelPool {activeConnections = 1}
30-01-2018 22:36:12.520 [reactor-http-nio-2] DEBUG r.i.n.resources.DefaultPoolResources.debug - 创建[id:0x88225196],现在有2个活动连接
30-01-2018 22:36:12.520 [reactor-http-nio-4] DEBUG r.i.n.resources.DefaultPoolResources.debug - 创建[id:0x80971ff0],现在有2个活动连接
为什么第一次API调用会发生两次 - 这是一个错误还是Mono的预期行为?
答案 0 :(得分:2)
为什么第一次api调用会发生两次
zip
将直接订阅firstResult
两次,一次通过map-map-flatMap
链
在这种情况下你不需要zip
,但只需要flatMap
一遍又一遍:
val firstResult = callApi().doOnSuccess { r -> println("callApi returned: $r") }
val lastResult = firstResult
.flatMap { first ->
Mono.just(first)
.map { guid -> guid.find { c -> c.isDigit() } }
.map { Character.getNumericValue(it!!) }
.flatMap { i -> callApi(i) }
.map { second -> "First result is ${first}Second result is $second" }
}
lastResult.block()