我正在使用Spring Boot 2.1.1和WebFlux,Reactor 3.2.3,Mongo 3.8.2和Netty 4.1.31运行的简单聊天服务。
每个聊天室都有2个集合-消息存档和带有当前事件(例如,新消息事件,用户键入指示符等)的加盖集合。上限为100个元素,我正在使用ReactiveMongoTemplate的tail()方法来检索最新事件。
该服务公开了两种用于检索最近事件的终结点:SSE和轮询。我已经对2000个并发用户进行了压力测试,这些用户除了听聊天以外,还发送大量事件。
观察结果是:
观察似乎很明显,因为当我在测试过程中通过SSE连接时,它在新事件到来时几乎立即更新了我的信息-基本上,SSE的响应速度比每2秒轮询一次要高数百倍。
问题是:
鉴于客户端最终是订户(或者至少我认为是有限的知识),我可以通过ReactiveMongoTemplate限制发布消息的速度吗?还是以某种方式减少了对新事件的需求,而不必这样做在客户端?
我一直在尝试通过Flux缓冲和缓存来实现运气,但这带来了更大的压力...
代码:
// ChatRepository.java
private static final Query chatEventsQuery = new Query();
public Flux<ChatEvent> getChatEventsStream(String chatId) {
return reactiveMongoTemplate.tail(
chatEventsQuery,
ChatEvent.class,
chatId
);
}
,
// ChatHandler.java
public Mono<ServerResponse> getChatStream(ServerRequest request) {
String chatId = request.pathVariable(CHAT_ID_PATH_VARIABLE);
String username = getUsername(request);
Flux<ServerSentEvent> chatEventsStream = chatRepository
.getChatEventsStream(chatId)
.map(addUserSpecificPropsToChatEvent(username))
.map(event -> ServerSentEvent.<ChatEvent>builder()
.event(event.getType().getEventName())
.data(event)
.build());
log.debug("\nExposing chat stream\nchat: {}\nuser: {}", chatId, username);
return ServerResponse.ok().body(
chatEventsStream,
ServerSentEvent.class
);
}
,
// ChatRouter.java
RouterFunction<ServerResponse> routes(ChatHandler handler) {
return route(GET("/api/chat/{chatId}/stream"), handler::getChatStream);
}
答案 0 :(得分:0)
答案是:
您可以使用Flux.buffer
方法来完成。然后,流量将以定义的速率将事件批量发送给订户。
我发布的代码有2个主要问题
鉴于多个用户通常在听一个聊天,我重构了ChatRepository以利用“热”可重播的流量(现在我每个聊天有1个流,而不是每个用户1个流)在Caffeine缓存中。 此外,我会以较短的时间间隔对其进行缓冲,以避免在繁忙的聊天中将事件推送给客户端时浪费大量资源。
我在ChatRepository中使用的new Query()
是多余的。一世
已经查看了ReactiveMongoTemplate的代码,以及是否为非null
提供查询,逻辑有点复杂。最好通过null
改为ReactiveMongoTemplate的tail()
方法。
代码后重构
// ChatRepository.java
public Flux<List<ChatEvent>> getChatEventsStream(String chatId) {
return Optional.ofNullable(chatStreamsCache.getIfPresent(chatId))
.orElseGet(newCachedChatEventsStream(chatId))
.autoConnect();
}
private Supplier<ConnectableFlux<List<ChatEvent>>> newCachedChatEventsStream(String chatId) {
return () -> {
ConnectableFlux<List<ChatEvent>> chatEventsStream = reactiveMongoTemplate.tail(
null,
ChatEvent.class,
chatId
).buffer(Duration.ofMillis(chatEventsBufferInterval))
.replay(chatEventsReplayCount);
chatStreamsCache.put(chatId, chatEventsStream);
return chatEventsStream;
};
}
,
// ChatHandler.java
public Mono<ServerResponse> getChatStream(ServerRequest request) {
String chatId = request.pathVariable(CHAT_ID_PATH_VARIABLE);
String username = getUsername(request);
Flux<ServerSentEvent> chatEventsStream = chatRepository
.getChatEventsStream(chatId)
.map(addUserSpecificPropsToChatEvents(username))
.map(event -> ServerSentEvent.<List<ChatEvent>>builder()
.event(CHAT_SSE_NAME)
.data(event)
.build());
log.debug("\nExposing chat stream\nchat: {}\nuser: {}", chatId, username);
return ServerResponse.ok().body(
chatEventsStream,
ServerSentEvent.class
);
}
,
应用这些更改后,该服务即使在3000个活动用户中也能正常运行(JVM使用约50%的CPU,Mongo约7%,这主要是由于大量的插入-流现在还不那么明显)