Какие знаешь способы защиты от сбоев базы данных в одном экземпляре?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Защита от сбоев базы данных — архитектура высокой доступности
Одним из критических компонентов любого приложения является база данных. Сбой БД может привести к полной потере функциональности. Рассмотрим стратегии защиты в случае одного экземпляра БД.
Connection Pooling
Connection Pooling — управление пулом соединений для оптимизации использования ресурсов:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000); // 30 сек
config.setIdleTimeout(600000); // 10 минут
config.setMaxLifetime(1800000); // 30 минут
config.setAutoCommit(true);
config.setConnectionTestQuery("SELECT 1");
return new HikariDataSource(config);
}
}
Преимущества: Повторное использование соединений, меньше overhead Ограничения: Защищает только от истощения соединений, не от сбоев
Circuit Breaker Pattern
Circuit Breaker — прерывает запросы при обнаружении сбоя, чтобы дать БД восстановиться:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private CircuitBreakerFactory circuitBreakerFactory;
public User getUserById(Long id) {
return circuitBreakerFactory.create("userService")
.run(
() -> userRepository.findById(id).orElse(null),
throwable -> getDefaultUser() // fallback
);
}
}
@Configuration
public class CircuitBreakerConfig {
@Bean
public Resilience4jCircuitBreakerFactory resilience4jCircuitBreakerFactory() {
return new Resilience4jCircuitBreakerFactory();
}
}
@Configuration
public class CircuitBreakerProperties {
@Bean
public Customizer<Resilience4jCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4jConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.failureRateThreshold(50.0f)
.waitDurationInOpenState(Duration.ofSeconds(10))
.automaticTransitionFromOpenToHalfOpenEnabled(true)
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(5))
.build())
.build());
}
}
Состояния Circuit Breaker:
- CLOSED — нормальная работа, запросы проходят
- OPEN — обнаружен сбой, запросы блокируются
- HALF_OPEN — попытка восстановления, некоторые запросы пропускаются
Retry с экспоненциальной задержкой
Retry Logic — повтор запросов при временных сбоях:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Retryable(
value = {TemporaryDataAccessException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public Order saveOrder(Order order) {
return orderRepository.save(order);
}
@Recover
public Order recoverSaveOrder(TemporaryDataAccessException e, Order order) {
logger.error("Failed to save order after retries", e);
// Fallback стратегия
return new Order();
}
}
@Configuration
@EnableRetry
public class RetryConfig {
// Конфигурация Retry с экспоненциальной задержкой
}
Преимущества: Восстанавливает временные сбои, прост в реализации Недостатки: Не помогает при постоянных сбоях, может замедлить отклик
Timeout Management
Timeout — ограничение времени ожидания ответа от БД:
@Configuration
public class JdbcConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
// Socket timeout (зависит от драйвера)
config.addDataSourceProperty("socketTimeout", "30");
// Connection timeout
config.setConnectionTimeout(10000);
return new HikariDataSource(config);
}
}
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional(timeout = 10) // 10 секунд на транзакцию
public List<Product> getProducts() {
return productRepository.findAll();
}
}
Преимущества: Предотвращает зависание запросов Недостатки: Может прерывать долгие, но необходимые операции
Bulkhead Pattern (Изоляция ресурсов)
Bulkhead — изолирует критичные операции от менее важных:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Bulkhead(name = "criticalOperation", type = Bulkhead.Type.THREADPOOL)
public User getCriticalUser(Long id) {
return userRepository.findById(id).orElse(null);
}
@Bulkhead(name = "nonCriticalOperation", type = Bulkhead.Type.SEMAPHORE)
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
@Configuration
public class BulkheadConfig {
@Bean
public Customizer<Resilience4jBulkheadFactory> customizer() {
return factory -> factory.configureDefault(id -> {
if ("criticalOperation".equals(id)) {
return new Resilience4jConfigBuilder(id)
.bulkheadConfig(ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(5)
.coreThreadPoolSize(2)
.build())
.build();
}
return new Resilience4jConfigBuilder(id)
.bulkheadConfig(BulkheadConfig.custom()
.maxConcurrentCalls(10)
.maxWaitDuration(Duration.ofSeconds(1))
.build())
.build();
});
}
}
Read Replicas (Масштабирование чтения)
Отделение операций чтения на отдельный экземпляр БД (secondary):
@Configuration
public class RoutingDataSourceConfig {
@Bean(name = "primaryDataSource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://primary-db:5432/mydb")
.username("user")
.password("password")
.driverClassName("org.postgresql.Driver")
.build();
}
@Bean(name = "replicaDataSource")
public DataSource replicaDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://replica-db:5432/mydb")
.username("user")
.password("password")
.driverClassName("org.postgresql.Driver")
.build();
}
@Bean
public DataSource routingDataSource(
@Qualifier("primaryDataSource") DataSource primary,
@Qualifier("replicaDataSource") DataSource replica) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("primary", primary);
targetDataSources.put("replica", replica);
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(primary);
return routingDataSource;
}
}
@Component
public class DbContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setReadOnly() {
contextHolder.set("replica");
}
public static void setReadWrite() {
contextHolder.set("primary");
}
public static String getDataSourceKey() {
return contextHolder.get() != null ? contextHolder.get() : "primary";
}
public static void clear() {
contextHolder.remove();
}
}
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDataSourceKey();
}
}
@Aspect
@Component
public class DataSourceAspect {
@Before("execution(* *.find*(..")) // все find методы
public void setReadOnly() {
DbContextHolder.setReadOnly();
}
@Before("execution(* *.save*(..)) || execution(* *.update*(..))")
public void setReadWrite() {
DbContextHolder.setReadWrite();
}
@After("execution(* *.*(.."))
public void clearContext() {
DbContextHolder.clear();
}
}
Health Checks и Monitoring
Health Checks — периодическая проверка доступности БД:
@Component
public class DatabaseHealthIndicator extends AbstractHealthIndicator {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
protected void doHealthCheck(Health.Builder builder) {
try {
Integer result = jdbcTemplate.queryForObject(
"SELECT 1",
Integer.class
);
if (result != null) {
builder.up().withDetail("database", "OK");
} else {
builder.down().withDetail("database", "Invalid response");
}
} catch (Exception e) {
builder.down()
.withDetail("database", "Connection failed")
.withException(e);
}
}
}
@Configuration
public class ActuatorConfig {
// Spring Boot автоматически включит /actuator/health endpoint
}
Кэширование
Caching — уменьшает нагрузку на БД за счет кэширования часто запрашиваемых данных:
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
@Autowired
private UserRepository userRepository;
@Cacheable(key = "#id", unless = "#result == null")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
@CacheEvict(key = "#user.id")
public void updateUser(User user) {
userRepository.save(user);
}
@CacheEvict(allEntries = true)
public void invalidateCache() {
}
}
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users", "products");
}
}
Backup и Recovery
Backup стратегия — обеспечивает восстановление при потере данных:
@Service
public class BackupService {
private final String backupCommand =
"pg_dump -h localhost -U user mydb | gzip > /backup/db_$(date +%Y%m%d_%H%M%S).sql.gz";
@Scheduled(cron = "0 2 * * *") // Каждый день в 2:00 AM
public void dailyBackup() {
try {
Runtime.getRuntime().exec(backupCommand);
logger.info("Daily backup completed successfully");
} catch (IOException e) {
logger.error("Backup failed", e);
sendAlert("Database backup failed");
}
}
}
Сравнение методов
| Метод | Уровень | Сложность | Overhead |
|---|---|---|---|
| Connection Pooling | Низкий | Низкая | Минимальный |
| Circuit Breaker | Средний | Средняя | Низкий |
| Retry | Средний | Низкая | Зависит |
| Timeout | Средний | Низкая | Минимальный |
| Bulkhead | Высокий | Средняя | Средний |
| Read Replicas | Высокий | Высокая | Средний |
| Caching | Высокий | Средняя | Значительный |
| Health Checks | Низкий | Низкая | Минимальный |
Комплексная стратегия
Для надежной защиты применяй комбинацию:
- Connection Pooling — базовая защита
- Health Checks + Monitoring — раннее обнаружение
- Retry + Circuit Breaker — восстановление при временных сбоях
- Timeout — предотвращение зависания
- Caching — снижение нагрузки
- Read Replicas — масштабирование
- Backup & Recovery — восстановление при катастрофе