Какие свойства нужны приложению для масштабирования
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Свойства приложения для масштабирования (Scalability)
Масштабируемость — это способность приложения обрабатывать растущий объём данных и нарастающую нагрузку без деградации производительности. Существуют ключевые архитектурные и технические свойства, которые необходимы для этого.
1. Statelessness (Отсутствие состояния)
Важность: Stateless приложение можно легко масштабировать горизонтально (добавлять серверы).
// Плохо - STATEFUL (привязан к сессии)
@RestController
public class ShoppingCartController {
@GetMapping("/cart")
public Cart getCart(HttpSession session) {
// Состояние хранится в session - сложно масштабировать
return (Cart) session.getAttribute("cart");
}
}
// Хорошо - STATELESS
@RestController
public class ShoppingCartController {
@GetMapping("/users/{id}/cart")
public Cart getCart(@PathVariable Long userId) {
// Состояние загружается из БД, каждый запрос независим
return cartService.getCart(userId);
}
}
// Результат:
// Stateless → можно обрабатывать запрос на любом сервере
// Server 1: request 1
// Server 2: request 2
// Server 3: request 3
// Все работают параллельно
Конфигурация для statelessness:
# application.yml
server:
servlet:
session:
persistent: false
timeout: 30m
spring:
session:
store-type: none # Не хранить сессию
2. Горизонтальное масштабирование (Horizontal Scaling)
Характеристики приложения:
- Load Balancer распределяет запросы между несколькими инстансами
- Не привязано к локальному файловой системе
- Не хранит состояние в памяти
Request → Load Balancer (nginx, HAProxy)
├→ Instance 1 (port 8080)
├→ Instance 2 (port 8080)
├→ Instance 3 (port 8080)
└→ Instance 4 (port 8080)
Любой инстанс может обработать запрос
Пример конфигурации:
# nginx.conf
upstream backend {
server app1:8080;
server app2:8080;
server app3:8080;
server app4:8080;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Kubernetes:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3 # Автоматически масштабирует на 3 инстанса
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
3. Кэширование (Caching Strategy)
Многоуровневое кэширование:
// 1. L1 Cache - In-Memory (Caffeine)
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new CaffeineCacheManager("products", "users");
}
}
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id);
}
}
// 2. L2 Cache - Redis (Distributed)
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
@Service
public class OrderService {
@Cacheable(value = "orders", key = "#userId", cacheManager = "redisManager")
public List<Order> getUserOrders(Long userId) {
return orderRepository.findByUserId(userId);
}
}
// 3. Cache Invalidation
@CacheEvict(value = "orders", key = "#userId")
public void updateOrder(Long userId, Order order) {
orderRepository.save(order);
}
Cache Warming:
@Component
public class CacheWarmer {
@EventListener(ContextRefreshedEvent.class)
public void warmCache() {
// Предварительно загрузить часто используемые данные
List<Category> categories = categoryRepository.findAll();
categories.forEach(c -> cacheService.cache(c));
}
}
4. База данных масштабирования
Database replication и sharding:
Master (Write) → Replication → Slave 1 (Read)
→ Slave 2 (Read)
→ Slave 3 (Read)
// Master-Slave конфигурация
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource masterDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://master.db:5432/myapp")
.build();
}
@Bean
public DataSource slaveDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://slave.db:5432/myapp")
.build();
}
}
// Использование
@Service
public class UserService {
@Transactional(readOnly = true)
public User getUser(Long id) {
// Используется slave для чтения
return userRepository.findById(id);
}
@Transactional(readOnly = false)
public void updateUser(User user) {
// Использует master для записи
userRepository.save(user);
}
}
Database Sharding (горизонтальное разделение):
// Sharding по user_id
@Service
public class UserOrderService {
private List<DataSource> shards; // Несколько БД
public Order getOrder(Long userId, Long orderId) {
int shardIndex = (int) (userId % shards.size());
DataSource shard = shards.get(shardIndex);
// Запросить из конкретной БД
return getOrderFromShard(shard, orderId);
}
}
// Пример распределения:
// userId 1, 4, 7, 10... → Shard 1
// userId 2, 5, 8, 11... → Shard 2
// userId 3, 6, 9, 12... → Shard 3
5. Асинхронная обработка (Async Processing)
Отделение long-running operations:
// Плохо - синхронный процесс блокирует
@PostMapping("/orders")
public ResponseEntity<OrderDTO> createOrder(@RequestBody CreateOrderRequest request) {
Order order = orderService.createOrder(request);
sendEmailNotification(order); // Ждём отправки email (2 секунды)
generateInvoice(order); // Ждём генерации инвойса (3 секунды)
return ResponseEntity.ok(OrderMapper.toDTO(order));
} // Итого 5 секунд для пользователя
// Хорошо - асинхронная обработка
@PostMapping("/orders")
public ResponseEntity<OrderDTO> createOrder(@RequestBody CreateOrderRequest request) {
Order order = orderService.createOrder(request);
// Отправить в очередь - пользователь получит ответ сразу
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
return ResponseEntity.ok(OrderMapper.toDTO(order));
} // Итого < 100ms
// Обработчики
@Component
public class OrderEventHandler {
@EventListener
@Async
public void handleOrderCreated(OrderCreatedEvent event) {
Order order = orderService.getOrder(event.getOrderId());
emailService.send(order); // 2 секунды
}
@EventListener
@Async
public void generateInvoice(OrderCreatedEvent event) {
Order order = orderService.getOrder(event.getOrderId());
invoiceService.generate(order); // 3 секунды
}
}
Message Queue для масштабирования:
// RabbitMQ / Kafka
@Configuration
public class MessagingConfig {
@Bean
public Queue orderQueue() {
return QueueBuilder.durable("orders").build();
}
}
@Service
public class OrderPublisher {
public void publishOrderCreated(Order order) {
rabbitTemplate.convertAndSend("orders",
new OrderCreatedMessage(order));
}
}
@Component
public class OrderConsumer {
@RabbitListener(queues = "orders")
public void handleOrderCreated(OrderCreatedMessage message) {
// Несколько потребителей могут обрабатывать очередь
processOrder(message.getOrderId());
}
}
6. Connection Pooling
Оптимальная конфигурация HikariCP:
spring:
datasource:
hikari:
maximum-pool-size: 20 # Количество соединений в пуле
minimum-idle: 5 # Минимум idle соединений
connection-timeout: 20000 # Timeout получить соединение (20 сек)
idle-timeout: 300000 # Timeout закрыть idle соединение (5 мин)
max-lifetime: 1200000 # Max lifetime соединения (20 мин)
auto-commit: true
leak-detection-threshold: 15000 # Detect leaked connections
7. API Rate Limiting
Защита от перегрузки:
@Configuration
public class RateLimitingConfig {
@Bean
public RateLimiter rateLimiter() {
return RateLimiter.create(100); // 100 requests/sec
}
}
@Component
public class RateLimitingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!rateLimiter.tryAcquire()) {
((HttpServletResponse) response).setStatus(429); // Too Many Requests
return;
}
chain.doFilter(request, response);
}
}
8. Мониторинг и Автоскейлинг
Kubernetes HPA (Horizontal Pod Autoscaler):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Масштабируется если CPU > 70%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # Масштабируется если memory > 80%
9. Обработка больших объёмов данных
Stream обработка вместо batch:
// Плохо - загружаем 1 млн записей в памяь
List<User> users = userRepository.findAll(); // OutOfMemoryError
users.forEach(user -> processUser(user));
// Хорошо - потоковая обработка
final int BATCH_SIZE = 1000;
userRepository.findAll().forEach(users -> {
users.forEach(user -> processUser(user));
}, BATCH_SIZE);
// Или использовать Stream
try (Stream<User> stream = userRepository.streamAll()) {
stream.forEach(user -> processUser(user));
}
Заключение
Масштабируемое приложение должно быть stateless, использовать горизонтальное масштабирование, иметь многоуровневое кэширование, асинхронную обработку, оптимальный database scaling и мониторинг для автоматического масштабирования. Все эти свойства должны быть встроены в архитектуру с самого начала, а не добавлены потом.