Какие знаешь способы решения проблемы, когда приложение периодически теряет соединение с БД?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение проблемы периодической потери соединения с БД
Периодическая потеря соединения — одна из самых частых проблем в production. Обычно это вызвано timeout-ами, network issues или неправильной конфигурацией. Вот систематические решения:
1. Проверка Connection Pool (HikariCP)
Проблема:池 исчерпана или соединения зависают
spring:
datasource:
hikari:
maximum-pool-size: 20 # Макс соединений
minimum-idle: 5 # Мин свободных
connection-timeout: 30000 # 30 сек ждём соединение
idle-timeout: 600000 # Закрой неиспользуемое через 10 мин
max-lifetime: 1800000 # Макс 30 мин жизни соединения
connection-test-query: "SELECT 1" # Проверяй живое ли
leak-detection-threshold: 60000 # Логируй утечки > 60 сек
Диагностика:
import com.zaxxer.hikari.HikariDataSource;
@Component
public class PoolMonitor {
@Autowired
private DataSource dataSource;
@Scheduled(fixedDelay = 60000) // Каждую минуту
public void logPoolStatus() {
HikariDataSource pool = (HikariDataSource) dataSource;
log.info("Active connections: {}. Idle: {}. Max: {}",
pool.getHikariPoolMXBean().getActiveConnections(),
pool.getHikariPoolMXBean().getIdleConnections(),
pool.getMaximumPoolSize());
}
}
2. Test on Borrow (валидация соединений)
Проблема: БД вернула мёртвое соединение
spring:
datasource:
hikari:
# Проверяй соединение перед выдачей
connection-test-query: "SELECT 1"
# Или
test-on-borrow: true
validation-query: "SELECT 1"
Java конфиг:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
// Критичные настройки
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
// Проверка соединения
config.setConnectionTestQuery("SELECT 1");
config.setLeakDetectionThreshold(60000);
return new HikariDataSource(config);
}
}
3. Retry Logic (повтор при ошибке)
Проблема: временный network glitch теряет запрос
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Retryable(
value = {SQLException.class, DataAccessException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000) // 1 сек между попытками
)
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow();
}
@Recover
public User recover(Exception e, Long id) {
log.error("Failed to get user after 3 attempts", e);
throw new DataAccessException("Failed to fetch user", e);
}
}
Spring Boot конфиг:
spring:
retry:
enabled: true
Или вручную:
public <T> T executeWithRetry(Supplier<T> operation, int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
try {
return operation.get();
} catch (Exception e) {
if (i == maxRetries - 1) {
throw new RuntimeException("Max retries exceeded", e);
}
long delay = (long) Math.pow(2, i) * 1000; // Exponential backoff
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
throw new RuntimeException("Unreachable");
}
4. Keep-Alive (tcpkeepalives)
Проблема: firewall/LB закрывает idle соединение
spring:
datasource:
hikari:
jdbc-url: "jdbc:mysql://localhost:3306/mydb?autoReconnect=true&autoReconnectForPools=true&maxReconnects=5"
Для PostgreSQL:
spring:
datasource:
hikari:
jdbc-url: "jdbc:postgresql://localhost:5432/mydb?tcpKeepAlives=true&tcpKeepIdle=30"
На уровне ОС (Linux):
sysctl -w net.ipv4.tcp_keepalives_intvl=30
sysctl -w net.ipv4.tcp_keepalives_probes=5
sysctl -w net.ipv4.tcp_keepalives_time=600
5. Connection Eviction (удаление старых соединений)
Проблема: старые соединения становятся неживыми
spring:
datasource:
hikari:
idle-timeout: 600000 # Удали через 10 мин неиспользования
max-lifetime: 1800000 # Удали через 30 мин в любом случае
6. Увеличение timeout-ов
Проблема: network задержки > timeout
spring:
datasource:
hikari:
connection-timeout: 60000 # Было 30сек, теперь 60
jpa:
properties:
hibernate:
jdbc:
fetch_size: 100
batch_size: 50
# MySQL specific
mysql:
connection:
timeout: 30000
read-timeout: 30000
write-timeout: 30000
JDBC строка:
String url = "jdbc:mysql://localhost:3306/mydb?" +
"connectTimeout=30000&" + // Timeout подключения
"socketTimeout=30000&" + // Timeout чтения/записи
"autoReconnect=true";
7. Circuit Breaker (отключи при критичных ошибках)
Проблема: приложение бесконечно пытается к мёртвой БД
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
@Service
public class UserService {
@CircuitBreaker(
name = "userService",
fallbackMethod = "fallbackGetUser"
)
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow();
}
public User fallbackGetUser(Long id, Exception e) {
log.error("Circuit breaker triggered", e);
// Вернуть cached данные или бросить ошибку
throw new ServiceUnavailableException("User service temporarily unavailable");
}
}
Конфиг:
resilience4j:
circuitbreaker:
instances:
userService:
failure-rate-threshold: 50 # 50% ошибок → open
slow-call-rate-threshold: 100
wait-duration-in-open-state: 30000 # Закрыт на 30 сек
slow-call-duration-threshold: 10000 # > 10 сек = slow
permitted-number-of-calls-in-half-open-state: 3
sliding-window-size: 10
sliding-window-type: COUNT_BASED
8. Pooling при использовании ORM (Hibernate)
Проблема: Hibernate неправильно управляет сессией
spring:
jpa:
properties:
hibernate:
# Проверяй соединение перед выдачей
connection:
isolation: 2 # READ_COMMITTED
generate_statistics: true # Логируй статистику
// Явное управление сессией
@Service
public class TransactionService {
@Transactional
public void process() {
// Сессия откроется в начале, закроется в конце
userRepository.save(user);
}
}
9. Мониторинг и Логирование
Логируй все соединения:
logging:
level:
com.zaxxer.hikari: DEBUG # HikariCP логи
org.hibernate.engine.transaction.internal.TransactionImpl: DEBUG
org.springframework.jdbc: DEBUG
Metrics:
import io.micrometer.core.instrument.MeterRegistry;
@Component
public class DatabaseHealthCheck {
@Autowired
private MeterRegistry meterRegistry;
@PostConstruct
public void registerMetrics() {
// Метрики пула
meterRegistry.gauge("db.pool.size",
() -> pool.getActiveConnections());
}
}
10. Правильный shutdown приложения
Проблема: соединения не закрыты при выключении
@Configuration
public class ShutdownConfig {
@Bean
public DataSource dataSource() {
// HikariCP автоматически закроет при shutdown
return new HikariDataSource(config);
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
11. Network diagnostics
# Проверь соединение с БД
ping <db-host>
telnet <db-host> 3306
# MTU size (может быть причина разрывов)
ping -M do -s 1472 <db-host>
# TCP keep-alives
ss -to | grep mysql
Итоговый конфиг для production
spring:
datasource:
hikari:
jdbc-url: jdbc:mysql://prod-db:3306/mydb
username: ${DB_USER}
password: ${DB_PASSWORD}
# Pool размеры
maximum-pool-size: 20
minimum-idle: 5
# Timeout-ы
connection-timeout: 60000
idle-timeout: 600000
max-lifetime: 1800000
# Здоровье
connection-test-query: "SELECT 1"
leak-detection-threshold: 60000
# Reconnect
jdbc-url: "jdbc:mysql://prod-db:3306/mydb?autoReconnect=true&maxReconnects=3&tcpKeepAlives=true"
jpa:
properties:
hibernate:
generate_statistics: true
resilience4j:
circuitbreaker:
instances:
database:
failure-rate-threshold: 50
wait-duration-in-open-state: 30000
permitted-number-of-calls-in-half-open-state: 3
logging:
level:
com.zaxxer.hikari: INFO
Чеклист diagnostics
- ✓ HikariCP метрики (active/idle connections)
- ✓ Логи с WARN и ERROR уровня
- ✓ Проверь timeout-ы (connection, socket, query)
- ✓ Проверь pool size соответствует нагрузке
- ✓ tcpKeepAlives включены
- ✓ Connection test query настроена
- ✓ Retry logic для транзиентных ошибок
- ✓ Circuit breaker при критичных сбоях
- ✓ Graceful shutdown реализован
- ✓ Firewall/Load Balancer не режет idle соединения
Итог
Потеря соединения обычно вызвана одной из пяти причин:
- Pool исчерпана или соединения зависают
- Firewall закрывает idle соединения
- Timeout-ы слишком короткие
- БД перезагружается/переполнена
- Network issues
Диагностируй через логи HikariCP и метрики, применяй решения в зависимости от причины.