Java 如何在SpringWebFlux中正确地从多个通量(WebsocketSession::receive)向接收器发出值?
在我的简化示例中,我希望将WebSocket客户端发送的消息广播给所有其他客户端。该应用程序使用带弹簧的反应式WebSocket构建 我的想法是用单曲Java 如何在SpringWebFlux中正确地从多个通量(WebsocketSession::receive)向接收器发出值?,java,kotlin,spring-webflux,project-reactor,spring-websocket,Java,Kotlin,Spring Webflux,Project Reactor,Spring Websocket,在我的简化示例中,我希望将WebSocket客户端发送的消息广播给所有其他客户端。该应用程序使用带弹簧的反应式WebSocket构建 我的想法是用单曲 接收器如果从客户端接收到消息,则在该接收器上发出该消息WebsocketSession::send仅将此接收器发出的事件转发给连接的客户端 @Component class ReactiveWebSocketHandler(private val sink: Sinks.Many<Message>,
接收器
如果从客户端接收到消息,则在该接收器上发出该消息WebsocketSession::send
仅将此接收器发出的事件转发给连接的客户端
@Component
class ReactiveWebSocketHandler(private val sink: Sinks.Many<Message>,
private val objectMapper : ObjectMapper) : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
val input = session.receive()
.doOnNext {
sink.emitNext(fromJson(it.payloadAsText, Message::class.java), Sinks.EmitFailureHandler.FAIL_FAST)
}
.then()
val output = session.send(sink.asFlux().map { message -> session.textMessage(toJson(message)) })
return Mono.zip(input, output).then()
}
fun toJson(obj : Any) : String = objectMapper.writeValueAsString(obj)
fun <T> fromJson(json : String, clazz : Class<T>) : T{
return objectMapper.readValue(json, clazz)
}
}
我可以看到的另一个选项是在一些常用锁上进行同步
,以便发射是线程安全的:
@Component
class ReactiveWebSocketHandler(private val sink: Sinks.Many<Message>,
private val objectMapper : ObjectMapper) : WebSocketHandler {
private val lock = Any()
override fun handle(session: WebSocketSession): Mono<Void> {
val input = session.receive()
.doOnNext {
synchronized(lock) {
sink.emitNext(fromJson(it.payloadAsText, Message::class.java), Sinks.EmitFailureHandler.FAIL_FAST)
}
}
.then()
...
}
}
@组件
类ReactiveWebSocketHandler(私有val sink:Sinks.Many,
私有val对象映射器:对象映射器):WebSocketHandler{
private val lock=Any()
覆盖乐趣句柄(会话:WebSocketSession):Mono{
val input=session.receive()
doOnNext先生{
已同步(锁定){
sink.emitNext(fromJson(it.payloadAsText,Message::class.java),Sinks.EmitFailureHandler.FAIL\u FAST)
}
}
.然后()
...
}
}
然而,我不确定是否应该这样做
问题是
在这种情况下,是否可以使用publishOn
,以确保发射是线程安全的,如果没有,那么这个问题的其他解决方案是什么(除了像我使用synchronized
关键字那样使用同步之外)。而不是使用synchronized
选项进行悲观锁定,您可以创建一个与FAIL\u FAST
相当的EmitFailureHandler
,除了它为EmitResult.NON\u SERIALIZED\u ACCESS
返回true
这将导致立即重试并发的发射尝试,就像在忙循环中一样
乐观地说,这最终会成功。如果您想对无限循环进行额外防御,甚至可以让自定义处理程序引入延迟或限制其返回的次数。而不是使用synchronized
选项进行悲观锁定,您可以创建一个与FAIL\u FAST
相当的EmitFailureHandler
,除了它为EmitResult.NON\u SERIALIZED\u ACCESS
返回true
这将导致立即重试并发的发射尝试,就像在忙循环中一样
乐观地说,这最终会成功。如果您想对无限循环进行额外的防御,您甚至可以让自定义处理程序引入延迟或限制其返回的次数。单线程调度程序方法应该可以工作,但您需要为每个ReactiveWebSocketHandler
使用相同的调度程序实例
您是否可以使用平面图而不是水槽来组合所有receive()通量
我自己解决这个问题的方法是“忙转法”
请参阅我的答案。关于单线程调度程序方法的出版物应该可以工作,但您需要为每个ReactiveWebSocketHandler
使用相同的调度程序实例
您是否可以使用平面图而不是水槽来组合所有receive()通量
我自己解决这个问题的方法是“忙转法”
请参阅我的答案。除了@simon baslé答案之外,这里是示例代码(用于srping webflux)。它将请求下游发送到订阅服务器,如果出现接收器,EmitResult.FAIL\u非序列化的响应将重试。
这是Sinks.EmitFailureHandler的定义:
private final Sinks.EmitFailureHandler emitFailureHandler = (signalType, emitResult) -> emitResult
.equals(Sinks.EmitResult.FAIL_NON_SERIALIZED) ? true : false;
以下是将处理请求的控制器:
@org.springframework.web.bind.annotation.RestController
public class RestController {
private final Many<String> sink = Sinks.many().multicast().directBestEffort();
private final Sinks.EmitFailureHandler emitFailureHandler = (signalType, emitResult) -> emitResult
.equals(Sinks.EmitResult.FAIL_NON_SERIALIZED) ? true : false;
@Autowired
public RestController(ServiceSubscriber serviceSubscriber) {
sink.asFlux().subscribe(serviceSubscriber);
}
@GetMapping(path = "/{id}")
public Mono<ResponseEntity<Void>> getData(@PathVariable String id) {
return Mono.fromCallable(() -> {
sink.emitNext(id, emitFailureHandler);
return ResponseEntity.ok().<Void>build();
});
}
}
@org.springframework.web.bind.annotation.RestController
公共类RestController{
private final Many sink=Sinks.Many().multicast().directBestEffort();
private final Sinks.EmitFailureHandler EmitFailureHandler=(信号类型,emitResult)->emitResult
.equals(Sinks.EmitResult.FAIL\未序列化)?true:false;
@自动连线
公共RestController(ServiceSubscriber ServiceSubscriber){
sink.asFlux().subscribe(serviceSubscriber);
}
@GetMapping(path=“/{id}”)
公共Mono getData(@PathVariable字符串id){
返回Mono.fromCallable(()->{
sink.emitNext(id,emitFailureHandler);
返回ResponseEntity.ok().build();
});
}
}
除了@simon baslé答案之外,这里还有示例代码(用于srping webflux)。它将请求下游发送到订阅服务器,如果出现接收器,EmitResult.FAIL\u非序列化的响应将重试。
这是Sinks.EmitFailureHandler的定义:
private final Sinks.EmitFailureHandler emitFailureHandler = (signalType, emitResult) -> emitResult
.equals(Sinks.EmitResult.FAIL_NON_SERIALIZED) ? true : false;
以下是将处理请求的控制器:
@org.springframework.web.bind.annotation.RestController
public class RestController {
private final Many<String> sink = Sinks.many().multicast().directBestEffort();
private final Sinks.EmitFailureHandler emitFailureHandler = (signalType, emitResult) -> emitResult
.equals(Sinks.EmitResult.FAIL_NON_SERIALIZED) ? true : false;
@Autowired
public RestController(ServiceSubscriber serviceSubscriber) {
sink.asFlux().subscribe(serviceSubscriber);
}
@GetMapping(path = "/{id}")
public Mono<ResponseEntity<Void>> getData(@PathVariable String id) {
return Mono.fromCallable(() -> {
sink.emitNext(id, emitFailureHandler);
return ResponseEntity.ok().<Void>build();
});
}
}
@org.springframework.web.bind.annotation.RestController
公共类RestController{
private final Many sink=Sinks.Many().multicast().directBestEffort();
private final Sinks.EmitFailureHandler EmitFailureHandler=(信号类型,emitResult)->emitResult
.equals(Sinks.EmitResult.FAIL\未序列化)?true:false;
@自动连线
公共RestController(ServiceSubscriber ServiceSubscriber){
sink.asFlux().subscribe(serviceSubscriber);
}
@GetMapping(path=“/{id}”)
公共Mono getData(@PathVariable字符串id){
返回Mono.fromCallable(()->{
sink.emitNext(id,emitFailureHandler);
返回ResponseEntity.ok().build();
});
}
}
太好了,我会试试看。你知道为什么我在onNext
之前将publishOn
与单线程调度程序一起使用时,会向Sink
发送一条消息,然后所有后续websocket连接立即接收onClose事件吗?在一个简单的独立应用程序中,这样的设置也解决了这个问题,但是在WebSocket的情况下,它失败了。@michalk emitNext硬编码为t