[英]Issue with rising heap size in a spring boot app (running in k8s)
提示:本站为国内最大中英文翻译问答网站,提供中英文对照查看,鼠标放在中文字句上可显示英文原文。
服务说明:
这个 spring 启动应用程序本质上是一个 web 充电站套接字代理(使用 OCPP 协议通过 websocket)作为负载均衡器后面的多个节点运行,基本上保持/保持打开连接,以便其他微服务(消息的实际消费者)通过 rabbitmq 连接到此代理可以重新部署和维护。
堆转储:
这些设备(数以万计)往往会经常连接和断开连接。 问题是
org.apache.catalina.session.StandardManager 中的会话(属性)
不断添加会话(超过 700 000)但不删除它们(在 7 天内)。 看图(StandartManager)
我也调查过
org.apache.tomcat.websocket.server.WsServerContainer 中的会话(属性)
它持有 websocket 个会话(在转储时:523)是正确的。 看图(WsServerContainer)
该应用程序还出于自己的目的将活动会话(每个节点)保存在并发 map 中:
WebsocketServerService 中的会话(属性)
转储时:530(与上述会话相关,不要介意小差异,没关系)见图片(WebsocketServerService)
所以基本上期望是所有会话属性都将持有几乎相同数量的会话,在这种情况下大约是 530,而不是超过 700 000。
问题:
什么可能导致标准会话的不断上升? 我不熟悉 tomcat 的内部工作原理,如果 websocket 连接被标准 session“烘焙”(至少在连接开始时)并且没有正确关闭套接字(双方)可能会导致标准会话会粘在周围,一段时间后不会被收集。 这些只是假设。
可能原因 (WIP) 的一种版本是我们正在使用标准的WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final WebsocketServerProperties websocketServerProperties;
private final StationAuthenticationProvider stationAuthenticationProvider;
private final AuthenticationEntryPoint authEntryPoint;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(stationAuthenticationProvider);
}
@Override
@SuppressWarnings("squid:S4502")
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic()
.authenticationEntryPoint(authEntryPoint)
.and()
.antMatcher(websocketServerProperties.getWebsocketPathVpn() + "/**").anonymous()
.and()
.antMatcher(websocketServerProperties.getWebsocketPathInternet() + "/**")
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
因此,如果站点连接并且出现错误,例如 401,则套接字未正确关闭,并且会话增加而未被 GC 正确关闭和收集(假设)。
websocket controller 的实现是标准的(至少参考文档):
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
@EnableConfigurationProperties(WebsocketServerProperties.class)
public class WebsocketServerConfig implements WebSocketConfigurer {
public static final String[] SUPPORTED_PROTOCOLS = new String[]{.....};
private final WebsocketServerProperties websocketServerProperties;
private final WebsocketHandShakeInterceptor websocketHandShakeInterceptor;
private final StationSocketHandler stationSocketHandler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(stationSocketHandler,
websocketServerProperties.getWebsocketPathVpn() + "/*",
websocketServerProperties.getWebsocketPathInternet() + "/*")
.addInterceptors(websocketHandShakeInterceptor)
.setHandshakeHandler(handshakeHandler())
.setAllowedOrigins("*");
}
private HandshakeHandler handshakeHandler() {
DefaultHandshakeHandler handler = new DefaultHandshakeHandler();
handler.setSupportedProtocols(SUPPORTED_PROTOCOLS);
return handler;
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(websocketServerProperties.getMaxTextMessageBufferSize());
container.setMaxSessionIdleTimeout(websocketServerProperties.getMaxSessionIdleTimeout());
return container;
}
}
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__({@Autowired}))
public class StationSocketHandler extends TextWebSocketHandler implements SubProtocolCapable {
public static final String DEVICE_ID = "DEVICE_ID";
public static final String CONNECTION_ROUTE = "CONNECTION_ROUTE";
private final OcppMessageParser ocppMessageParser;
private final WebsocketServerService websocketServerService;
private final PingPongService pingPongService;
@Override
public void handleTextMessage(final WebSocketSession session, TextMessage textMessage) {
OcppMessage message = ocppMessageParser.parsePayloadWithSessionId(session.getId(), textMessage.getPayload());
websocketServerService.processMessageFromStation(message);
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {
Map<String, Object> attributes = session.getAttributes();
String deviceId = (String) attributes.get(DEVICE_ID);
String connectionRoute = (String) attributes.get(CONNECTION_ROUTE);
websocketServerService.connect(Route.toRoute(connectionRoute), deviceId, session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, @NonNull CloseStatus status) {
log.info("WebSocketSession[{}][{}] closed with status {} and attributes {}",
session.getId(), session.isOpen(), status, session.getAttributes());
websocketServerService.disconnectSessionId(session.getId());
}
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
pingPongService.handlePong(session, message);
}
@Override
public @NonNull
List<String> getSubProtocols() {
return Arrays.asList(WebsocketServerConfig.SUPPORTED_PROTOCOLS);
}
}
任何建议,想法可能会导致这种情况,我们将不胜感激。
我运行了一些负载测试以查看会话是否正在上升并且与真实的 state 无关。测试后的堆转储(至少目前)不是很有帮助。 也试过模拟错误,没有可见的结果。
我能够找出原因,所以这里的调查结果是:
现在的解决方案(我需要测试它)是以某种方式克服这个 2 session 星座,或使用不同的提供程序,如码头、undertow ....或黑客将 httpsession 的引用作为属性传递给 websocket session 和在 onConnected 事件上将超时设置为 -1,在 session 关闭后将其设置为 60s => 这样一分钟后 session 就消失了。
修复是将 session 策略强制执行为 NEVER!
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and().csrf().disable()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.