Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
CAP теорема: основы и практическое применение
CAP теорема (также известная как теорема Брюэра) — это фундаментальный принцип в распределённых системах, который определяет ограничения при проектировании баз данных и микросервисов.
Что такое CAP?
CAP — это акроним трёх гарантий:
1. Consistency (Согласованность)
Все узлы системы видят одни и те же данные в один момент времени.
// Пример нарушения консистентности:
// Клиент 1: пишет user.balance = 1000
// Клиент 2: читает user.balance = 500 (не обновленное значение)
// ✅ Согласованная система:
public class ConsistentUserService {
private final DataSource dataSource;
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transfer(UUID fromUser, UUID toUser, BigDecimal amount) {
// Если записываем, ВСЕ читают актуальные данные
User from = userRepository.findByIdForUpdate(fromUser);
User to = userRepository.findByIdForUpdate(toUser);
from.decreaseBalance(amount);
to.increaseBalance(amount);
userRepository.save(from);
userRepository.save(to);
}
}
2. Availability (Доступность)
Система всегда доступна для чтения и записи, на каждый запрос есть успешный ответ.
// Пример высокой доступности:
public class AvailableUserService {
private final UserRepository primaryReplica;
private final UserRepository secondaryReplica1;
private final UserRepository secondaryReplica2;
public User getUser(UUID id) {
try {
return primaryReplica.findById(id);
} catch (Exception e) {
// Primary не отвечает — берём из secondary
try {
return secondaryReplica1.findById(id);
} catch (Exception e2) {
return secondaryReplica2.findById(id);
}
}
}
public void saveUser(User user) {
// Записываем на все реплики асинхронно
CompletableFuture.runAsync(() -> primaryReplica.save(user));
CompletableFuture.runAsync(() -> secondaryReplica1.save(user));
CompletableFuture.runAsync(() -> secondaryReplica2.save(user));
}
}
3. Partition Tolerance (Устойчивость к разделению сети)
Система продолжает работать даже если часть узлов потеряет связь с другой частью (сетевой раздел).
// Пример partition tolerance:
public class PartitionTolerantService {
private final Node primaryDataCenter;
private final Node backupDataCenter;
public void handleNetworkPartition() {
// Если сеть разделена: primary в европе, backup в азии
// Backup может продолжить обслуживать запросы
// Но данные могут рассинхронизироваться
if (!primaryDataCenter.isReachable()) {
logger.warn("Network partition detected");
// Switchover to backup
activateBackupDataCenter();
}
}
}
Теорема Брюэра
Можешь выбрать максимум ДВА из трёх свойств:
C (Consistency) — все данные в sync
/ \
/ \
/ \
CP / \ CA
/ \
/____________\
P (Partition Tolerance)
A (Availability)
Три стратегии
CP системы (Consistency + Partition Tolerance)
Жертвуем доступностью — при разделении сети система может стать недоступной, но данные всегда консистентны.
Примеры: PostgreSQL (синхронная репликация), ZooKeeper, HBase, etcd
// CP подход: PostgreSQL с синхронной репликацией
public class CPUserService {
@Transactional(isolation = Isolation.SERIALIZABLE)
public void saveUser(User user) {
// Данные пишутся сначала в primary
userRepository.save(user);
// Потом синхронно на все replicas (ждём ответа)
// Если replica не отвечает — транзакция откатывается
// ПЛЮСЫ: 100% консистентность
// МИНУСЫ: если replica упала, запись не пройдёт
}
}
AP системы (Availability + Partition Tolerance)
Жертвуем консистентностью — при разделении сети разные части могут иметь разные данные, но система всегда отвечает.
Примеры: DynamoDB, Cassandra, MongoDB (по умолчанию), Redis
// AP подход: Cassandra
public class APUserService {
@Autowired
private CassandraOperations cassandra;
public void saveUser(User user) {
// Пишем с eventual consistency
cassandra.insert(user);
// Система сразу говорит "ОК"
// Но данные распространяются на все узлы с задержкой
// ПЛЮСЫ: высокая доступность, масштабируемость
// МИНУСЫ: может быть временная несогласованность
}
}
CA системы (Consistency + Availability)
Жертвуем partition tolerance — может работать только если сеть идеальна (что невозможно в реальности).
Примеры: Практически все монолитные БД (Oracle, MySQL), которые работают на одной машине
// CA подход: монолитная БД на одном сервере
public class CAUserService {
@Transactional
public void saveUser(User user) {
userRepository.save(user); // Все данные в sync, всегда доступно
// НО: если сервер упал — всё недоступно
}
}
Практическое применение в архитектуре
Сценарий 1: E-commerce платформа
// Данные о товарах: AP (availability важнее)
// Могут быть слегка устаревшими, но система всегда доступна
public class ProductService {
@Autowired
private MongoDB mongoDb; // AP база
public Product getProduct(String id) {
return mongoDb.findById(id); // Может вернуть слегка старое значение
}
}
// Платежи: CP (консистентность критична)
// Деньги никогда не теряются, даже если медленнее
public class PaymentService {
@Autowired
private PostgreSQL postgres; // CP база
@Transactional(isolation = Isolation.SERIALIZABLE)
public void processPayment(Payment payment) {
// Гарантированная консистентность
}
}
Сценарий 2: Микросервисная архитектура
// Если два сервиса теряют связь (partition):
// ❌ Ждём синхронизации (теряем доступность):
public void orderPayment(Order order) {
// Синхронный вызов payment service
PaymentResponse response = paymentService.charge(order.getAmount());
// Если payment недоступен — весь заказ падает
}
// ✅ Используем очередь (жертвуем временной консистентностью):
public void orderPayment(Order order) {
// Асинхронно через очередь (RabbitMQ, Kafka)
eventBus.publish(new OrderCreatedEvent(order));
// Система доступна, даже если payment service недоступен
// Платёж обработается позже (eventual consistency)
}
Вывод
Современный подход: нет универсального выбора. В одной системе могут быть разные части:
- CP для критичных данных (платежи, транзакции)
- AP для некритичных (кэш, analytics, рекомендации)
- CA только если это монолит на одном сервере
Ключевой навык: понимать trade-off и выбирать правильную архитектуру для каждой части системы.