Microservices Architecture - Câu hỏi phỏng vấn Senior Engineer
1. Microservices Design Patterns
Câu hỏi:
"Hãy giải thích các design patterns chính trong microservices architecture và implement Service Discovery pattern."
Câu trả lời:
Core Microservices Patterns: - Service Discovery: Tự động tìm và register services - Circuit Breaker: Prevent cascading failures - API Gateway: Single entry point - Saga Pattern: Distributed transactions - CQRS: Command Query Responsibility Segregation
Ví dụ Service Discovery Implementation:
// 1. Service Registry Interface
public interface ServiceRegistry {
void register(ServiceInstance instance);
void deregister(String serviceId, String instanceId);
List<ServiceInstance> getInstances(String serviceId);
ServiceInstance getInstance(String serviceId);
}
// 2. Service Instance Model
@Data
@Builder
public class ServiceInstance {
private String serviceId;
private String instanceId;
private String host;
private int port;
private boolean secure;
private Map<String, String> metadata;
private HealthStatus healthStatus;
private LocalDateTime registrationTime;
private LocalDateTime lastHeartbeat;
public String getUri() {
return (secure ? "https" : "http") + "://" + host + ":" + port;
}
}
// 3. In-Memory Service Registry Implementation
@Component
public class InMemoryServiceRegistry implements ServiceRegistry {
private final Map<String, List<ServiceInstance>> serviceInstances = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
@PostConstruct
public void init() {
// Health check scheduler
scheduler.scheduleAtFixedRate(this::performHealthChecks, 30, 30, TimeUnit.SECONDS);
// Cleanup scheduler
scheduler.scheduleAtFixedRate(this::cleanupStaleInstances, 60, 60, TimeUnit.SECONDS);
}
@Override
public void register(ServiceInstance instance) {
serviceInstances.computeIfAbsent(instance.getServiceId(), k -> new ArrayList<>())
.add(instance);
log.info("Registered service instance: {}", instance);
}
@Override
public void deregister(String serviceId, String instanceId) {
List<ServiceInstance> instances = serviceInstances.get(serviceId);
if (instances != null) {
instances.removeIf(instance -> instance.getInstanceId().equals(instanceId));
log.info("Deregistered service instance: {}/{}", serviceId, instanceId);
}
}
@Override
public List<ServiceInstance> getInstances(String serviceId) {
return serviceInstances.getOrDefault(serviceId, Collections.emptyList())
.stream()
.filter(instance -> instance.getHealthStatus() == HealthStatus.UP)
.collect(Collectors.toList());
}
@Override
public ServiceInstance getInstance(String serviceId) {
List<ServiceInstance> instances = getInstances(serviceId);
if (instances.isEmpty()) {
throw new ServiceNotFoundException("No healthy instances found for service: " + serviceId);
}
// Simple round-robin load balancing
return instances.get(ThreadLocalRandom.current().nextInt(instances.size()));
}
private void performHealthChecks() {
serviceInstances.values().stream()
.flatMap(List::stream)
.forEach(this::checkInstanceHealth);
}
private void checkInstanceHealth(ServiceInstance instance) {
try {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForObject(instance.getUri() + "/actuator/health", String.class);
instance.setHealthStatus(HealthStatus.UP);
instance.setLastHeartbeat(LocalDateTime.now());
} catch (Exception ex) {
instance.setHealthStatus(HealthStatus.DOWN);
log.warn("Health check failed for {}: {}", instance, ex.getMessage());
}
}
private void cleanupStaleInstances() {
LocalDateTime cutoff = LocalDateTime.now().minusMinutes(5);
serviceInstances.values().forEach(instances ->
instances.removeIf(instance ->
instance.getLastHeartbeat().isBefore(cutoff)));
}
}
// 4. Service Discovery Client
@Component
public class ServiceDiscoveryClient {
private final ServiceRegistry serviceRegistry;
private final RestTemplate restTemplate;
private final LoadBalancer loadBalancer;
public ServiceDiscoveryClient(ServiceRegistry serviceRegistry,
RestTemplate restTemplate,
LoadBalancer loadBalancer) {
this.serviceRegistry = serviceRegistry;
this.restTemplate = restTemplate;
this.loadBalancer = loadBalancer;
}
public <T> T call(String serviceId, String path, Class<T> responseType) {
ServiceInstance instance = loadBalancer.choose(serviceId);
String url = instance.getUri() + path;
try {
return restTemplate.getForObject(url, responseType);
} catch (Exception ex) {
log.error("Failed to call service {}: {}", serviceId, ex.getMessage());
throw new ServiceCallException("Service call failed", ex);
}
}
public <T> ResponseEntity<T> callForEntity(String serviceId, String path,
Class<T> responseType) {
ServiceInstance instance = loadBalancer.choose(serviceId);
String url = instance.getUri() + path;
return restTemplate.getForEntity(url, responseType);
}
public <T, R> R post(String serviceId, String path, T request, Class<R> responseType) {
ServiceInstance instance = loadBalancer.choose(serviceId);
String url = instance.getUri() + path;
return restTemplate.postForObject(url, request, responseType);
}
}
// 5. Load Balancer Interface và Implementation
public interface LoadBalancer {
ServiceInstance choose(String serviceId);
}
@Component
public class RoundRobinLoadBalancer implements LoadBalancer {
private final ServiceRegistry serviceRegistry;
private final Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
public RoundRobinLoadBalancer(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
@Override
public ServiceInstance choose(String serviceId) {
List<ServiceInstance> instances = serviceRegistry.getInstances(serviceId);
if (instances.isEmpty()) {
throw new ServiceNotFoundException("No instances available for service: " + serviceId);
}
AtomicInteger counter = counters.computeIfAbsent(serviceId, k -> new AtomicInteger(0));
int index = counter.getAndIncrement() % instances.size();
return instances.get(index);
}
}
// 6. Auto-registration for Spring Boot services
@Component
public class ServiceAutoRegistration {
private final ServiceRegistry serviceRegistry;
@Value("${spring.application.name}")
private String serviceName;
@Value("${server.port}")
private int port;
@Value("${management.server.port:${server.port}}")
private int managementPort;
private ServiceInstance currentInstance;
public ServiceAutoRegistration(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
registerSelf();
}
@PreDestroy
public void deregisterSelf() {
if (currentInstance != null) {
serviceRegistry.deregister(currentInstance.getServiceId(),
currentInstance.getInstanceId());
}
}
private void registerSelf() {
try {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
currentInstance = ServiceInstance.builder()
.serviceId(serviceName)
.instanceId(serviceName + "-" + hostAddress + "-" + port)
.host(hostAddress)
.port(port)
.secure(false)
.healthStatus(HealthStatus.UP)
.registrationTime(LocalDateTime.now())
.lastHeartbeat(LocalDateTime.now())
.metadata(Map.of(
"management.port", String.valueOf(managementPort),
"version", getClass().getPackage().getImplementationVersion(),
"zone", "default"
))
.build();
serviceRegistry.register(currentInstance);
} catch (Exception ex) {
log.error("Failed to register service instance", ex);
}
}
}
2. Circuit Breaker Pattern
Câu hỏi:
"Implement Circuit Breaker pattern để prevent cascading failures trong microservices."
Câu trả lời:
Ví dụ Circuit Breaker Implementation:
// 1. Circuit Breaker States
public enum CircuitBreakerState {
CLOSED, // Normal operation
OPEN, // Failing, reject calls
HALF_OPEN // Testing if service recovered
}
// 2. Circuit Breaker Configuration
@Data
@Builder
public class CircuitBreakerConfig {
@Builder.Default
private int failureThreshold = 5; // Failures to open circuit
@Builder.Default
private int successThreshold = 3; // Successes to close circuit
@Builder.Default
private Duration timeout = Duration.ofSeconds(60); // How long to stay open
@Builder.Default
private Duration callTimeout = Duration.ofSeconds(5); // Individual call timeout
@Builder.Default
private Class<? extends Exception>[] failureExceptions = new Class[]{Exception.class};
@Builder.Default
private Class<? extends Exception>[] ignoreExceptions = new Class[]{IllegalArgumentException.class};
}
// 3. Circuit Breaker Implementation
public class CircuitBreaker {
private final String name;
private final CircuitBreakerConfig config;
private final AtomicReference<CircuitBreakerState> state = new AtomicReference<>(CircuitBreakerState.CLOSED);
private final AtomicInteger failureCount = new AtomicInteger(0);
private final AtomicInteger successCount = new AtomicInteger(0);
private final AtomicLong lastFailureTime = new AtomicLong(0);
private final MeterRegistry meterRegistry;
// Metrics
private final Counter successCounter;
private final Counter failureCounter;
private final Counter circuitOpenCounter;
private final Timer callTimer;
public CircuitBreaker(String name, CircuitBreakerConfig config, MeterRegistry meterRegistry) {
this.name = name;
this.config = config;
this.meterRegistry = meterRegistry;
// Initialize metrics
this.successCounter = Counter.builder("circuit_breaker_success")
.tag("name", name)
.register(meterRegistry);
this.failureCounter = Counter.builder("circuit_breaker_failure")
.tag("name", name)
.register(meterRegistry);
this.circuitOpenCounter = Counter.builder("circuit_breaker_open")
.tag("name", name)
.register(meterRegistry);
this.callTimer = Timer.builder("circuit_breaker_call_duration")
.tag("name", name)
.register(meterRegistry);
}
public <T> T execute(Supplier<T> operation) throws Exception {
return execute(operation, () -> {
throw new CircuitBreakerOpenException("Circuit breaker is open for: " + name);
});
}
public <T> T execute(Supplier<T> operation, Supplier<T> fallback) throws Exception {
if (state.get() == CircuitBreakerState.OPEN) {
if (shouldAttemptReset()) {
state.set(CircuitBreakerState.HALF_OPEN);
log.info("Circuit breaker {} transitioned to HALF_OPEN", name);
} else {
circuitOpenCounter.increment();
return fallback.get();
}
}
return callTimer.recordCallable(() -> {
try {
T result = executeWithTimeout(operation);
onSuccess();
return result;
} catch (Exception ex) {
onFailure(ex);
throw ex;
}
});
}
private <T> T executeWithTimeout(Supplier<T> operation) throws Exception {
CompletableFuture<T> future = CompletableFuture.supplyAsync(operation);
try {
return future.get(config.getCallTimeout().toMillis(), TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
future.cancel(true);
throw new CallTimeoutException("Operation timed out after " + config.getCallTimeout());
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Exception) {
throw (Exception) cause;
} else {
throw new RuntimeException(cause);
}
}
}
private void onSuccess() {
successCounter.increment();
if (state.get() == CircuitBreakerState.HALF_OPEN) {
int currentSuccessCount = successCount.incrementAndGet();
if (currentSuccessCount >= config.getSuccessThreshold()) {
reset();
}
} else {
failureCount.set(0);
}
}
private void onFailure(Exception ex) {
if (shouldIgnoreException(ex)) {
return;
}
failureCounter.increment();
lastFailureTime.set(System.currentTimeMillis());
int currentFailureCount = failureCount.incrementAndGet();
if (state.get() == CircuitBreakerState.HALF_OPEN) {
trip();
} else if (currentFailureCount >= config.getFailureThreshold()) {
trip();
}
}
private boolean shouldIgnoreException(Exception ex) {
for (Class<? extends Exception> ignoreException : config.getIgnoreExceptions()) {
if (ignoreException.isAssignableFrom(ex.getClass())) {
return true;
}
}
return false;
}
private void trip() {
state.set(CircuitBreakerState.OPEN);
successCount.set(0);
log.warn("Circuit breaker {} OPENED due to failures", name);
}
private void reset() {
state.set(CircuitBreakerState.CLOSED);
failureCount.set(0);
successCount.set(0);
log.info("Circuit breaker {} CLOSED - service recovered", name);
}
private boolean shouldAttemptReset() {
return System.currentTimeMillis() - lastFailureTime.get() >= config.getTimeout().toMillis();
}
public CircuitBreakerState getState() {
return state.get();
}
public CircuitBreakerMetrics getMetrics() {
return CircuitBreakerMetrics.builder()
.name(name)
.state(state.get())
.failureCount(failureCount.get())
.successCount(successCount.get())
.build();
}
}
// 4. Circuit Breaker Service
@Service
public class CircuitBreakerService {
private final Map<String, CircuitBreaker> circuitBreakers = new ConcurrentHashMap<>();
private final MeterRegistry meterRegistry;
public CircuitBreakerService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
public CircuitBreaker getCircuitBreaker(String name, CircuitBreakerConfig config) {
return circuitBreakers.computeIfAbsent(name,
key -> new CircuitBreaker(key, config, meterRegistry));
}
public CircuitBreaker getCircuitBreaker(String name) {
return getCircuitBreaker(name, CircuitBreakerConfig.builder().build());
}
public Map<String, CircuitBreakerMetrics> getAllMetrics() {
return circuitBreakers.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().getMetrics()
));
}
}
// 5. Service Client với Circuit Breaker
@Service
public class UserServiceClient {
private final RestTemplate restTemplate;
private final CircuitBreaker circuitBreaker;
private final ServiceDiscoveryClient serviceDiscoveryClient;
public UserServiceClient(RestTemplate restTemplate,
CircuitBreakerService circuitBreakerService,
ServiceDiscoveryClient serviceDiscoveryClient) {
this.restTemplate = restTemplate;
this.serviceDiscoveryClient = serviceDiscoveryClient;
CircuitBreakerConfig config = CircuitBreakerConfig.builder()
.failureThreshold(3)
.successThreshold(2)
.timeout(Duration.ofSeconds(30))
.callTimeout(Duration.ofSeconds(5))
.build();
this.circuitBreaker = circuitBreakerService.getCircuitBreaker("user-service", config);
}
public User getUserById(Long userId) {
try {
return circuitBreaker.execute(
() -> serviceDiscoveryClient.call("user-service",
"/api/users/" + userId, User.class),
() -> {
// Fallback: return cached user or default
log.warn("Using fallback for user: {}", userId);
return getCachedUser(userId);
}
);
} catch (Exception ex) {
log.error("Failed to get user {}: {}", userId, ex.getMessage());
throw new UserServiceException("Failed to retrieve user", ex);
}
}
public List<User> getAllUsers() {
try {
return circuitBreaker.execute(
() -> {
ResponseEntity<User[]> response = serviceDiscoveryClient
.callForEntity("user-service", "/api/users", User[].class);
return Arrays.asList(response.getBody());
},
() -> {
// Fallback: return empty list or cached data
log.warn("Using fallback for getAllUsers");
return getCachedUsers();
}
);
} catch (Exception ex) {
log.error("Failed to get all users: {}", ex.getMessage());
return Collections.emptyList();
}
}
public User createUser(CreateUserRequest request) {
try {
return circuitBreaker.execute(
() -> serviceDiscoveryClient.post("user-service",
"/api/users", request, User.class)
);
} catch (Exception ex) {
log.error("Failed to create user: {}", ex.getMessage());
throw new UserServiceException("Failed to create user", ex);
}
}
private User getCachedUser(Long userId) {
// Implementation của cache lookup
return User.builder()
.id(userId)
.name("Cached User")
.email("cached@example.com")
.build();
}
private List<User> getCachedUsers() {
// Implementation của cached users
return Collections.emptyList();
}
}
// 6. Health Indicator cho Circuit Breakers
@Component
public class CircuitBreakerHealthIndicator implements HealthIndicator {
private final CircuitBreakerService circuitBreakerService;
public CircuitBreakerHealthIndicator(CircuitBreakerService circuitBreakerService) {
this.circuitBreakerService = circuitBreakerService;
}
@Override
public Health health() {
Map<String, CircuitBreakerMetrics> metrics = circuitBreakerService.getAllMetrics();
boolean hasOpenCircuits = metrics.values().stream()
.anyMatch(metric -> metric.getState() == CircuitBreakerState.OPEN);
Health.Builder builder = hasOpenCircuits ? Health.down() : Health.up();
metrics.forEach((name, metric) -> {
builder.withDetail(name, Map.of(
"state", metric.getState(),
"failureCount", metric.getFailureCount(),
"successCount", metric.getSuccessCount()
));
});
return builder.build();
}
}
3. API Gateway Pattern
Câu hỏi:
"Design và implement một API Gateway với authentication, rate limiting, và request routing."