繁体   English   中英

spring 引导应用程序(在 k8s 中运行)中不断增加的堆大小问题

[英]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();
    }

}

而不是这里提到的https://docs.spring.io/spring-security/reference/servlet/integrations/websocket.html#websocket-configuration

因此,如果站点连接并且出现错误,例如 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 无关。测试后的堆转储(至少目前)不是很有帮助。 也试过模拟错误,没有可见的结果。

我能够找出原因,所以这里的调查结果是:

  • 如果您在 spring 引导应用程序中使用 websocket 处理程序的默认配置,它作为底层提供程序从 tomcat 提供
  • 我们使用了默认的@EnableWebSecurity => 这意味着对于每个 websocket session 也创建了一个经典的 StandardHttpSession 用于身份验证部分
  • StandartHttpSession 的默认超时为 60 秒(server.servlet.session.timeout=60)在 tomcat 的情况下,这些总是 60 的倍数 => 60,120,180...。但是在生产中(在我不知情的情况下)有人将此属性设置为-1 => 这意味着 session 永不过期(这解决了丢失 web 套接字连接的第一个可见问题,但导致了一年后(现在)由于负载增加而出现的新问题)
  • 所以问题的机制是,在 StandartHttpSession 超时后,“底层”websocket session 被关闭,即使是可见通信。
  • 问题是 websocket session 中的“活动”未传播到上述 StandardHttpSession => 这意味着 http session 超时并关闭底层 websocket 88349481872116

现在的解决方案(我需要测试它)是以某种方式克服这个 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.

 
粤ICP备18138465号  © 2020-2023 STACKOOM.COM