Java-приложение Tomcat зависает, не может перезапустить Tomcat после остановки - PullRequest
0 голосов
/ 14 февраля 2019

Я развернул зрелое веб-приложение на новом сервере на tomcat 7. База данных, используемая системой, совершенно пуста, поскольку мы находимся на ранних стадиях ее настройки для использования.

Переход к приложению, вы получаете страницу входа.Войдите в систему, и обычно вы попадете на главную страницу приложения.

Но после прихода на следующее утро у нас всегда возникает одна и та же проблема:

  1. Мы поднимаем логинЭкран - без проблем
  2. Введите наше имя пользователя и пароль - система зависает
  3. Заходим в tomcat и, используя системный трей, останавливаем сервис.
  4. Индикатор выполнения службы остановки останавливается, а затем исчезает, но в диалоговом окне свойств tomcat по-прежнему отображается «Запущено», а кнопки «Пуск» и «Стоп» отключены.
  5. Мы проверяем логи tomcat и ошибок нет.
  6. Мы перезагружаем сервер, и он снова работает нормально

Ничего очевидного мы не видим.Запрос tomcat 'Find Leaks' ничего не показывает, а просмотр размеров кучи в VisualVM показывает непротиворечивую схему захвата кучи с последующим сбором мусора, приводящим его к тому же низкому уровню (так что никаких явных утечек)

Я думал, что это может быть тайм-аут подключения MySQL, но это не должно иметь место, потому что, если я войду в систему с неправильным паролем, система перейдет в базу данных, чтобы проверить пароль, и вернется, как ожидается, с «неправильным паролем».Единственный момент, когда он не срабатывает, это если вы вводите правильный пароль.

Единственная подсказка, которая у нас есть, - это ошибка при входе в систему, когда система использует некоторый пользовательский код для определения хоста пользователя.name:

2019-02-14 08:10:14,277 08:10:14.277 [http-bio-8080-exec-9] ERROR com.sw.app.ui.UserAuthenticatedWebSession - Unknown host!
java.net.UnknownHostException: null
            at java.net.Inet6AddressImpl.getHostByAddr(Native Method) ~[na:1.8.0_201]
            at java.net.InetAddress$2.getHostByAddr(Unknown Source) ~[na:1.8.0_201]
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
            at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_201]
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_201]
            at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_201]
            at com.sw.app.data.utils.NetUtilities.getHostName(NetUtilities.java:114) ~[app-data-4.0.15.1-SNAPSHOT.jar:na]

Это вызывается только в том случае, если пользователь успешно вошел в систему, чтобы сохранить данные о том, откуда он вошел, но исключение было обнаружено в коде, а затем просто зарегистрировано, а не распространено вверх, и тогда мыиспользовать имя хоста по умолчанию «неизвестно».Это код:

  public static String getHostName( InetAddress inaHost ) throws UnknownHostException
  {
    try {
      Class<? extends InetAddress> clazz = Class.forName( "java.net.InetAddress" ).asSubclass( InetAddress.class );
      Constructor<?>[] constructors = clazz.getDeclaredConstructors();
      constructors[0].setAccessible( true );
      InetAddress ina = (InetAddress)constructors[0].newInstance();
      Field[] fields = ina.getClass().getDeclaredFields();
      for( Field field : fields ) {
        // Depends on the version of java we are dealing with:
        // Older version - single nameservice
        if( field.getName().equals( "nameService" ) ) {
          return getHostName( field.get( null ), inaHost );
        } else if( field.getName().equals( "nameServices" ) ) {
          // newer version - multiple name services possible
          StringBuilder builder = new StringBuilder();
          field.setAccessible( true );
          // A list of nameservice objects
          @SuppressWarnings( "unchecked" )
          List<Object> nameServices = (List<Object>)field.get( null );
          for( Object nameService : nameServices ) {
            String hostName = getHostName( nameService, inaHost );
            if( builder.length() > 0 ) {
              builder.append( ", " );
            }
            builder.append( hostName );
          }
          return builder.toString();
        }
      }
    } catch( ClassNotFoundException cnfe ) {
      throw new InvalidOperationException( "Class not found when looking up host name", cnfe );
    } catch( IllegalAccessException iae ) {
      throw new InvalidOperationException( "Cannot access method/field", iae );
    } catch( InstantiationException ie ) {
      throw new InvalidOperationException( "Cannot instantiate class", ie );
    } catch( InvocationTargetException ite ) {
      throw (UnknownHostException)ite.getCause();
    }
    return null;
  }

  /**
   * Get the host name using reflection on the hidden class implementation of the InetAddress details.
   * @param p_nameService
   * @param p_address
   * @return
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  private static String getHostName( Object nameService, InetAddress address ) throws IllegalAccessException, InvocationTargetException {
    Method[] methods = nameService.getClass().getDeclaredMethods();
    for( Method method : methods ) {
      // The nameService is assumed to have a method, getHostByAddr, which takes the byte[] inet address
      if( method.getName().equals( "getHostByAddr" ) ) {
        method.setAccessible( true );
        return (String)method.invoke( nameService, address.getAddress() );
      }
    }
    return "";
  }

У кого-нибудь есть похожие проблемы?

- Правка -

Вот класс компонента конфигурации базы данных.

@Configuration
public class AppPersistence {

  private static final Logger LOGGER = LoggerFactory.getLogger( AppPersistence.class );

  protected static final String INTERNAL_IP_DOMAIN = "*******";
  protected static final String JDBC_PROTOCOL = "jdbc:mysql://";
  protected static final String DEFAULT_DATABASE_NAME = "*******";

  /** The path for context-based property lookups */
  protected static final String CONTEXT_LOOKUP_PATH = "java:comp/env";

  /** This is the default location for the database - on the same machine as the deployment */
  protected static final String DB_LOCAL    = JDBC_PROTOCOL + "localhost:3306/" + DEFAULT_DATABASE_NAME;

  @Bean
  public DataSource createDataSource() throws Exception {
    BasicDataSource source = new BasicDataSource();

    // allow for parameterised config
    source.setDriverClassName( Driver.class.getName() );
    source.setUrl( getProperty( "app.database.url", DB_LOCAL ) );
    source.setUsername( getProperty( "app.database.username", "*****" ) );
    source.setPassword( getProperty( "app.database.password", "****" ) );

    LOGGER.warn( "Connecting to: " + source.getUrl() );

    return source;
  }

  protected String getProperty( String name, String default ) {
    // first check system properties
    String val = System.getProperty( name );
    if( val != null ) {
      logLookup( "System Properties", name, val );
      return val;
    }
    // check environment variables
    val = System.getenv( name );
    if( val != null ) {
      logLookup( "System Environment Variables", name, val );
      return val;
    }
    // if we are deployed to a container, check the environment variables in that.
    try {
      Context context = InitialContext.doLookup( "java:comp/env" );
      if( context != null ) {
        Object valObj = context.lookup( name );
        if( valObj != null ) {
          logLookup( "Context", name, valObj.toString() );
          return valObj.toString();
        }
      }
    } catch( NamingException e ) {
      // if running on a dev machine this will probably happen
      LOGGER.warn( "Could not find context for lookup of " + p_name + " - assuming running in dev mode with defaults.  Error was: " + e.toString( true ) );
      LOGGER.info( "Error received on lookup of " + name + ":", e );
    }
    return p_default;
  }

  protected void logLookup( String source, String lookup, String value ) {
    if( value.contains( "password" ) ) {
      // avoid displaying any password info
      LOGGER.warn( "Successfully looked up sensitive value from " + source + " for name '" + lookup + "': [******]" );
    } else {
      LOGGER.warn( "Successfully looked up value from " + source + " for name '" + lookup + "': '" + value + "'" );
    }
  }

  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource ) {
    LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
    entityManagerFactory.setPersistenceUnitName( "com.sw.app.data.persistence" );
    entityManagerFactory.setDataSource( dataSource );
    entityManagerFactory.setJpaVendorAdapter( new HibernateJpaVendorAdapter() );
    entityManagerFactory.setLoadTimeWeaver( new InstrumentationLoadTimeWeaver() );
    entityManagerFactory.setJpaDialect( new HibernateJpaDialect() );
    entityManagerFactory.setPackagesToScan( "com.sw.app.data", "com.sw.app.rawimport",
                                            "com.sw.app.view", "com.sw.app.warranty" );

    entityManagerFactory.setJpaPropertyMap( hibernateJpaProperties( dataSource ) );
    return entityManagerFactory;
  }

  private Map<String, ?> hibernateJpaProperties( DataSource dataSource ) {
    HashMap<String, String> properties = new HashMap<>();

    // Need to copy these values over, otherwise c3p0 can't see them.
    if( dataSource instanceof BasicDataSource ) {
      BasicDataSource source = (BasicDataSource)p_dataSource;

      properties.put( "hibernate.connection.driver_class", source.getDriverClassName() );
      properties.put( "hibernate.connection.url", source.getUrl() );
      properties.put( "hibernate.connection.username", source.getUsername() );
      properties.put( "hibernate.connection.password", source.getPassword() );
    }

    // Added to avoid some merge problems when updating entities (eg contact to custimport)
    properties.put( "hibernate.event.merge.entity_copy_observer", "allow" );

    // Second level cache
    properties.put( "hibernate.cache.use_second_level_cache", "true" );
    properties.put( "hibernate.cache.use_query_cache", "true" );
    properties.put( "hibernate.cache.provider_class", "org.hibernate.cache.EhCacheProvider" );
    properties.put( "hibernate.cache.region.factory_class", EhCacheRegionFactory.class.getName() );
    properties.put( "hibernate.generate_statistics", "false" );

    properties.put( "hibernate.show_sql", "false" );
    properties.put( "hibernate.format_sql", "false" );

    // validate | update | create | create-drop -->
    properties.put( "hibernate.hbm2ddl.auto", "update" );
    properties.put( "hibernate.dialect", MySQL5Dialect.class.getName() );

    // [main] WARN  org.hibernate.cfg.AnnotationBinder - HHH000457: Joined inheritance hierarchy [com.sw.system4.data.collateral.AbstractCollateral] defined explicit @DiscriminatorColumn.  Legacy Hibernate behavior was to ignore the @DiscriminatorColumn.  However, as part of issue HHH-6911 we now apply the explicit @DiscriminatorColumn.  If you would prefer the legacy behavior, enable the `hibernate.discriminator.ignore_explicit_for_joined` setting (hibernate.discriminator.ignore_explicit_for_joined=true) -->
    properties.put( "hibernate.discriminator.ignore_explicit_for_joined", "true" );

    //properties.put("hibernate.hbm2ddl.import_files", "insert-data.sql");
    //properties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");

    // This wasnt required in persistence.xml, but for some reason is here.
    properties.put( "hibernate.connection.provider_class", C3P0ConnectionProvider.class.getName() );

    // just adding c3p0 props was enough in persistence.xml, but not here.
    properties.put( "hibernate.c3p0.min_size", "5" );
    properties.put( "hibernate.c3p0.max_size", "20" );
    properties.put( "hibernate.c3p0.timeout", "300" ); // 5mins
    properties.put( "hibernate.c3p0.max_statements", "50" );
    properties.put( "hibernate.c3p0.idle_test_period", "100" );
    properties.put( "hibernate.c3p0.preferredTestQuery", "select 1" );
    properties.put( "hibernate.c3p0.testConnectionOnCheckout", "true" );
    properties.put( "hibernate.c3p0.numHelperThreads", "12" );
    properties.put( "hibernate.c3p0.maxStatementsPerConnection", "25" );
    properties.put( "hibernate.c3p0.statementCacheNumDeferredCloseThreads", "1" );

    return l_properties;
  }

  @Bean
  public JpaTransactionManager transactionManager( EntityManagerFactory emf ) {
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
    jpaTransactionManager.setEntityManagerFactory( emf );
    return jpaTransactionManager;
  }
}

1 Ответ

0 голосов
/ 15 февраля 2019

Какая у вас база данных?Это облачная база данных?У меня была похожая проблема с CloudSQL.Случилось так, что некоторые активные соединения ничего не сделали с базой данных, со стороны базы данных она отклоняет соединение через несколько часов.Но на стороне приложения вы увидите это как активное соединение.Я использовал apache dbcp pool и смог решить проблему, используя это в конфигурациях базы данных.

    dataSource.setValidationQuery("SELECT 1");
    dataSource.setTestOnBorrow(true);
    dataSource.setTestWhileIdle(true);

Поскольку вы используете C3P0, следующие команды должны работать для вас.

    hibernate.dbcp.testOnBorrow=true
    hibernate.dbcp.testOnReturn=true
    hibernate.dbcp.validationQuery=SELECT 1
...