90 phút
Microservices với Spring Cloud
Microservices Architecture
Ưu điểm
- Independent deployment: Mỗi service deploy riêng
- Technology diversity: Có thể dùng tech stack khác nhau
- Scalability: Scale từng service độc lập
- Fault isolation: Lỗi 1 service không ảnh hưởng toàn hệ thống
Nhược điểm
- Complexity: Phức tạp hơn monolith
- Network latency: Giao tiếp qua network
- Distributed transactions: Khó đảm bảo consistency
- Testing: Khó test integration
Service Discovery với Eureka
Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
Eureka Client
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
API Gateway
Spring Cloud Gateway
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
server:
port: 8080
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
Custom Filter
@Component
public class AuthGatewayFilter implements GlobalFilter, Ordered {
private final JwtService jwtService;
public AuthGatewayFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath().value();
if (path.startsWith("/api/auth/")) {
return chain.filter(exchange);
}
String authHeader = exchange.getRequest().getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
try {
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);
exchange.getRequest().mutate()
.header("X-Auth-User", username)
.build();
} catch (Exception e) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
Inter-Service Communication
OpenFeign Client
@FeignClient(name = "user-service", path = "/api/users")
public interface UserClient {
@GetMapping("/{id}")
UserResponse getUser(@PathVariable Long id);
@PostMapping
UserResponse createUser(@RequestBody CreateUserRequest request);
}
Feign với fallback
@FeignClient(
name = "user-service",
fallbackFactory = UserClientFallback.class
)
public interface UserClient {
@GetMapping("/{id}")
UserResponse getUser(@PathVariable Long id);
}
@Component
public class UserClientFallback implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return id -> {
log.warn("Fallback for getUser({}): {}", id, cause.getMessage());
return new UserResponse(id, "Unknown", null, null, null);
};
}
}
Circuit Breaker với Resilience4j
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
@Service
@RequiredArgsConstructor
public class OrderService {
private final UserClient userClient;
@CircuitBreaker(name = "userService", fallbackMethod = "fallback")
@Retry(name = "userService")
@TimeLimiter(name = "userService")
public CompletableFuture<OrderResponse> createOrder(Long userId, OrderRequest req) {
return CompletableFuture.supplyAsync(() -> {
UserResponse user = userClient.getUser(userId);
// process order
return new OrderResponse(user.id(), req);
});
}
private CompletableFuture<OrderResponse> fallback(
Long userId, OrderRequest req, Throwable t
) {
log.error("Circuit breaker fallback", t);
return CompletableFuture.failedFuture(
new BusinessException("User service unavailable"));
}
}
Configuration
resilience4j:
circuitbreaker:
instances:
userService:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 10s
failureRateThreshold: 50
retry:
instances:
userService:
maxAttempts: 3
waitDuration: 1s
timelimiter:
instances:
userService:
timeoutDuration: 3s
Distributed Tracing
Micrometer Tracing + Zipkin
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
management:
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
Configuration Server
Config Server
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/myorg/config-repo
default-label: main
Message Queue với Kafka
Producer
@Service
@RequiredArgsConstructor
public class OrderEventPublisher {
private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
public void publishOrderCreated(OrderEvent event) {
kafkaTemplate.send("order-events", event.orderId(), event)
.whenComplete((result, ex) -> {
if (ex != null) {
log.error("Failed to publish event", ex);
}
});
}
}
Consumer
@Service
@Slf4j
public class OrderEventConsumer {
@KafkaListener(topics = "order-events", groupId = "notification-service")
public void handleOrderCreated(OrderEvent event) {
log.info("Received order event: {}", event);
// send notification
}
}
Docker Compose cho microservices
version: '3.8'
services:
eureka:
build: ./eureka-server
ports:
- "8761:8761"
config-server:
build: ./config-server
ports:
- "8888:8888"
depends_on:
- eureka
gateway:
build: ./gateway
ports:
- "8080:8080"
depends_on:
- eureka
user-service:
build: ./user-service
depends_on:
- eureka
- postgres
order-service:
build: ./order-service
depends_on:
- eureka
- postgres
- kafka
postgres:
image: postgres:15
environment:
POSTGRES_DB: microservices
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
kafka:
image: confluentinc/cp-kafka:latest
environment:
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
zipkin:
image: openzipkin/zipkin:latest
ports:
- "9411:9411"
Bài tập thực hành
Hãy xây dựng microservices architecture hoàn chỉnh!