У меня приложение, карточная игра. Фронтед next.js, Бэкэенд spring webflux. Между ними ngnix. На каждый клик UI обращается к серверу, что бы проверить ход игрока, получить новое состояние игры. Все вроде бы работало до сих пор. Могло свободно 4-5 игроков обслуживать. Больше игроков и нету. Я решил серверу добавить server side events, что бы UI могло с помощью eventSource получать сообщения от сервера и отображать их. Для начала добавил только два эвента - "начало игры" и "конец игры". Вообщем мелочи.
Для этого добавил такой эндпоинт
@GetMapping(path = "/events/{gamer}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamFlux(@PathVariable String gamer) {
log.info("subscribe for {}", gamer);
return service.subscribe(gamer);
}
Ну и источник эвентов
final Sinks.Many<CloudEvent> sink = Sinks.many().replay().limit(0);
@EventListener
@SneakyThrows
public void eventListener(GameEvent gameEvent) {
log.info("occured an event {} {}", gameEvent, mapper.writeValueAsString(gameEvent));
var event = CloudEventBuilder.v1()
.withId(UUID.randomUUID().toString())
.withType(gameEvent.getClass().getSimpleName())
.withSource(URI.create("sol://some/uri"))
.withSubject(gameEvent.getGamer())
.withTime(OffsetDateTime.now(ZoneOffset.UTC))
.withData ("application/json", mapper.writeValueAsString(gameEvent).getBytes(StandardCharsets.UTF_8))
.build();
sink.emitNext(event, Sinks.EmitFailureHandler.FAIL_FAST);
}
public Flux<String> subscribe(String gamer) {
return sink.asFlux()
.doOnNext((e) -> log.info("sent next event {}", e))
.filter(event -> "*".equals(event.getSubject()) || event.getSubject().equals(gamer))
.map(e -> new String(EventFormatProvider
.getInstance()
.resolveFormat(JsonFormat.CONTENT_TYPE)
.serialize(e)));
}
По сути сервис реагирует на ApplicationEvent спринга, заворачивает их в CloudEvent, конвертирует к строке и пихает в синк. А с другой стороны синка эти эвенты получают подписчики, кто запустил /api/events/{gamer}.
У меня на локальной машине все вроде бы работает, никаких проблем. А вот на продуктивном сервере стали часто появляться 502 BadRequest ошибки. Их возвращает ngnix прокси. В бэкенде я никаких ошибок не вижу. Даже если никто не играет, просто открыт сайт и подключен eventSource.
Все запросы к /api периодически начинают возвращать 502, чуть ли не каждую минуту случается, потом секунды через три опять начинает само работать.
Пробовал запускать с трассировкой, ничего подозрительного не увидел. 502 и раньше случались, я просто не обращал внимания. Ну 100 в день, не больше. А после этого каждую минуту практически.
Вот конфигурация ngnix. Раньше было только /api, добавил /api/events. Добавил всяких опций, все, что нашел в интернетах
location /api/events {
#limit_req zone=mylimit;
proxy_pass http://localhost:9494;
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
}
location /api {
#limit_req zone=mylimit;
proxy_pass http://localhost:9494;
}
netty специально не настраивал, только порт через server.port установлен. На сервере у меня есть всякие процессы, которые по Scheduled с разной периодичностью запускаются. Вот я на них грешу. Могут они как то netty блокировать? Или у netty connection pool как то забивается.
Спасибо за внимание
update
Я вообщем в UI клиенте закомментировал создание EventSource к точке /api/events/{gamer} и ошибки ушли. Почему запросы к это точке всё api блокируют, ума не приложу?
Вот так создаю EventSource
useEffect(() => {
if(!account) {
return;
}
console.log("read events", account, timestamp);
const eventSource = new EventSource(sol_api + `/events/${account}`);
eventSource.onerror = (e) => {console.log("Event Source Error!", e);eventSource.close();setError(true);};
eventSource.onmessage = (e) => addEvent(JSON.parse(e.data));
return () => {
console.log("close eventSource", eventSource);
eventSource.close();
};
}, [account, timestamp]);