Java学习第22天 - 云原生与容器化

学习目标

  • 掌握云原生应用的核心概念和设计原则
  • 学习Docker容器化技术
  • 掌握Kubernetes容器编排
  • 了解微服务在云环境中的部署策略
  • 学习服务网格(Service Mesh)技术
  • 掌握云原生监控和日志管理

一、云原生基础概念

1. 云原生应用特征

java 复制代码
// 1. 云原生应用配置
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableConfigServer
public class CloudNativeApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(CloudNativeApplication.class, args);
    }
    
    // 健康检查端点
    @GetMapping("/health")
    public ResponseEntity<Map<String, Object>> health() {
        Map<String, Object> health = new HashMap<>();
        health.put("status", "UP");
        health.put("timestamp", Instant.now());
        health.put("version", getClass().getPackage().getImplementationVersion());
        return ResponseEntity.ok(health);
    }
    
    // 就绪检查端点
    @GetMapping("/ready")
    public ResponseEntity<Map<String, Object>> ready() {
        Map<String, Object> ready = new HashMap<>();
        ready.put("status", "READY");
        ready.put("database", checkDatabaseConnection());
        ready.put("redis", checkRedisConnection());
        return ResponseEntity.ok(ready);
    }
    
    private String checkDatabaseConnection() {
        try {
            // 检查数据库连接
            return "UP";
        } catch (Exception e) {
            return "DOWN";
        }
    }
    
    private String checkRedisConnection() {
        try {
            // 检查Redis连接
            return "UP";
        } catch (Exception e) {
            return "DOWN";
        }
    }
}

// 2. 配置外部化
@Configuration
@ConfigurationProperties(prefix = "app")
@Data
public class AppConfig {
    
    private String name;
    private String version;
    private String environment;
    private Database database;
    private Redis redis;
    private Security security;
    
    @Data
    public static class Database {
        private String url;
        private String username;
        private String password;
        private int maxConnections;
        private int minConnections;
    }
    
    @Data
    public static class Redis {
        private String host;
        private int port;
        private String password;
        private int timeout;
    }
    
    @Data
    public static class Security {
        private String jwtSecret;
        private long jwtExpiration;
        private boolean corsEnabled;
    }
}

// 3. 环境特定配置
@Profile("dev")
@Configuration
public class DevConfig {
    
    @Bean
    public DataSource devDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:h2:mem:testdb");
        config.setUsername("sa");
        config.setPassword("");
        config.setMaximumPoolSize(5);
        return new HikariDataSource(config);
    }
}

@Profile("prod")
@Configuration
public class ProdConfig {
    
    @Bean
    public DataSource prodDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(System.getenv("DATABASE_URL"));
        config.setUsername(System.getenv("DATABASE_USERNAME"));
        config.setPassword(System.getenv("DATABASE_PASSWORD"));
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        return new HikariDataSource(config);
    }
}

2. 12-Factor应用原则

java 复制代码
// 1. 配置管理
@Component
public class ConfigurationManager {
    
    @Value("${app.name:default-app}")
    private String appName;
    
    @Value("${app.version:1.0.0}")
    private String appVersion;
    
    @Value("${server.port:8080}")
    private int serverPort;
    
    @Value("${spring.profiles.active:default}")
    private String activeProfile;
    
    public String getAppName() {
        return appName;
    }
    
    public String getAppVersion() {
        return appVersion;
    }
    
    public int getServerPort() {
        return serverPort;
    }
    
    public String getActiveProfile() {
        return activeProfile;
    }
    
    // 从环境变量读取配置
    public String getDatabaseUrl() {
        return System.getenv("DATABASE_URL");
    }
    
    public String getRedisUrl() {
        return System.getenv("REDIS_URL");
    }
}

// 2. 日志管理
@Component
@Slf4j
public class LoggingService {
    
    @PostConstruct
    public void init() {
        // 配置日志格式
        System.setProperty("logging.pattern.console", 
            "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n");
    }
    
    public void logInfo(String message, Object... args) {
        log.info(message, args);
    }
    
    public void logError(String message, Throwable throwable) {
        log.error(message, throwable);
    }
    
    public void logWithTraceId(String traceId, String message, Object... args) {
        MDC.put("traceId", traceId);
        log.info(message, args);
        MDC.remove("traceId");
    }
}

// 3. 进程管理
@Component
public class ProcessManager {
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @EventListener
    public void handleContextClosedEvent(ContextClosedEvent event) {
        log.info("Application is shutting down...");
        // 清理资源
        cleanup();
    }
    
    @PreDestroy
    public void cleanup() {
        log.info("Cleaning up resources...");
        // 关闭数据库连接池
        // 关闭Redis连接
        // 清理临时文件
    }
}

二、Docker容器化

1. Dockerfile编写

dockerfile 复制代码
# 多阶段构建Dockerfile
# 第一阶段:构建应用
FROM maven:3.8.4-openjdk-11-slim AS builder

WORKDIR /app

# 复制pom.xml并下载依赖
COPY pom.xml .
RUN mvn dependency:go-offline -B

# 复制源代码并构建
COPY src ./src
RUN mvn clean package -DskipTests

# 第二阶段:运行应用
FROM openjdk:11-jre-slim

# 安装必要的工具
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 创建应用用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 设置工作目录
WORKDIR /app

# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar

# 创建日志目录
RUN mkdir -p /app/logs && chown -R appuser:appuser /app

# 切换到应用用户
USER appuser

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]

2. Docker Compose配置

yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  # 应用服务
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DATABASE_URL=jdbc:postgresql://postgres:5432/appdb
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis
    networks:
      - app-network
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped

  # 数据库服务
  postgres:
    image: postgres:13-alpine
    environment:
      - POSTGRES_DB=appdb
      - POSTGRES_USER=appuser
      - POSTGRES_PASSWORD=apppass
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    networks:
      - app-network
    restart: unless-stopped

  # Redis服务
  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - app-network
    restart: unless-stopped

  # Nginx反向代理
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app
    networks:
      - app-network
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

networks:
  app-network:
    driver: bridge

3. Docker化Spring Boot应用

java 复制代码
// 1. Docker配置类
@Configuration
@EnableConfigurationProperties
public class DockerConfig {
    
    @Bean
    @ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
    public DockerInfoContributor dockerInfoContributor() {
        return new DockerInfoContributor();
    }
    
    @Bean
    public HealthIndicator dockerHealthIndicator() {
        return new DockerHealthIndicator();
    }
}

// 2. Docker信息贡献者
@Component
public class DockerInfoContributor implements InfoContributor {
    
    @Override
    public void contribute(Info.Builder builder) {
        Map<String, Object> dockerInfo = new HashMap<>();
        dockerInfo.put("containerId", getContainerId());
        dockerInfo.put("image", getImageName());
        dockerInfo.put("version", getImageVersion());
        dockerInfo.put("environment", System.getenv("ENVIRONMENT"));
        
        builder.withDetail("docker", dockerInfo);
    }
    
    private String getContainerId() {
        return System.getenv("HOSTNAME");
    }
    
    private String getImageName() {
        return System.getenv("IMAGE_NAME");
    }
    
    private String getImageVersion() {
        return System.getenv("IMAGE_VERSION");
    }
}

// 3. Docker健康检查
@Component
public class DockerHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        try {
            // 检查容器资源使用情况
            long memoryUsed = getMemoryUsed();
            long memoryTotal = getMemoryTotal();
            double memoryUsage = (double) memoryUsed / memoryTotal;
            
            if (memoryUsage > 0.9) {
                return Health.down()
                    .withDetail("memory", "High memory usage: " + (memoryUsage * 100) + "%")
                    .build();
            }
            
            return Health.up()
                .withDetail("memory", "Memory usage: " + (memoryUsage * 100) + "%")
                .withDetail("containerId", getContainerId())
                .build();
                
        } catch (Exception e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
    }
    
    private long getMemoryUsed() {
        // 获取内存使用量
        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }
    
    private long getMemoryTotal() {
        // 获取总内存
        return Runtime.getRuntime().maxMemory();
    }
    
    private String getContainerId() {
        return System.getenv("HOSTNAME");
    }
}

三、Kubernetes部署

1. Kubernetes配置文件

yaml 复制代码
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: microservices
  labels:
    name: microservices

---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: microservices
data:
  application.yml: |
    spring:
      profiles:
        active: k8s
      datasource:
        url: jdbc:postgresql://postgres-service:5432/appdb
        username: ${DB_USERNAME}
        password: ${DB_PASSWORD}
      redis:
        host: redis-service
        port: 6379
        password: ${REDIS_PASSWORD}
    app:
      name: microservices-app
      version: 1.0.0
      environment: k8s

---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: microservices
type: Opaque
data:
  DB_USERNAME: YXBwdXNlcg==  # appuser
  DB_PASSWORD: YXBwcGFzcw==  # apppass
  REDIS_PASSWORD: cmVkaXNwYXNz  # redispass
  JWT_SECRET: bXlqd3RzZWNyZXQ=  # myjwtsecret

---
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
  namespace: microservices
  labels:
    app: microservices-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: microservices-app
  template:
    metadata:
      labels:
        app: microservices-app
    spec:
      containers:
      - name: app
        image: microservices-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "k8s"
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: DB_USERNAME
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: DB_PASSWORD
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: REDIS_PASSWORD
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: JWT_SECRET
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        volumeMounts:
        - name: config-volume
          mountPath: /app/config
        - name: logs-volume
          mountPath: /app/logs
      volumes:
      - name: config-volume
        configMap:
          name: app-config
      - name: logs-volume
        emptyDir: {}
      imagePullSecrets:
      - name: regcred

---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: app-service
  namespace: microservices
spec:
  selector:
    app: microservices-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: microservices
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - api.example.com
    secretName: app-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80

2. Kubernetes服务发现

java 复制代码
// 1. Kubernetes服务发现
@Component
public class KubernetesServiceDiscovery {
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    public List<ServiceInstance> getServiceInstances(String serviceName) {
        return discoveryClient.getInstances(serviceName);
    }
    
    public String getServiceUrl(String serviceName) {
        List<ServiceInstance> instances = getServiceInstances(serviceName);
        if (instances.isEmpty()) {
            throw new ServiceNotFoundException("No instances found for service: " + serviceName);
        }
        
        ServiceInstance instance = instances.get(0);
        return instance.getUri().toString();
    }
    
    public List<String> getAllServiceNames() {
        return discoveryClient.getServices();
    }
}

// 2. Kubernetes配置
@Configuration
@EnableDiscoveryClient
public class KubernetesConfig {
    
    @Bean
    public KubernetesClient kubernetesClient() {
        return new DefaultKubernetesClient();
    }
    
    @Bean
    public ServiceDiscovery serviceDiscovery(KubernetesClient kubernetesClient) {
        return new KubernetesServiceDiscovery(kubernetesClient);
    }
}

// 3. 服务发现实现
public class KubernetesServiceDiscovery implements ServiceDiscovery {
    
    private final KubernetesClient kubernetesClient;
    
    public KubernetesServiceDiscovery(KubernetesClient kubernetesClient) {
        this.kubernetesClient = kubernetesClient;
    }
    
    @Override
    public List<ServiceInstance> getInstances(String serviceName) {
        try {
            Service service = kubernetesClient.services()
                .inNamespace("microservices")
                .withName(serviceName)
                .get();
            
            if (service == null) {
                return Collections.emptyList();
            }
            
            List<ServiceInstance> instances = new ArrayList<>();
            String namespace = service.getMetadata().getNamespace();
            String serviceName = service.getMetadata().getName();
            
            // 获取Endpoints
            Endpoints endpoints = kubernetesClient.endpoints()
                .inNamespace(namespace)
                .withName(serviceName)
                .get();
            
            if (endpoints != null && endpoints.getSubsets() != null) {
                for (EndpointSubset subset : endpoints.getSubsets()) {
                    for (EndpointAddress address : subset.getAddresses()) {
                        for (EndpointPort port : subset.getPorts()) {
                            ServiceInstance instance = new ServiceInstance() {
                                @Override
                                public String getServiceId() {
                                    return serviceName;
                                }
                                
                                @Override
                                public String getHost() {
                                    return address.getIp();
                                }
                                
                                @Override
                                public int getPort() {
                                    return port.getPort();
                                }
                                
                                @Override
                                public boolean isSecure() {
                                    return false;
                                }
                                
                                @Override
                                public URI getUri() {
                                    try {
                                        return new URI("http://" + getHost() + ":" + getPort());
                                    } catch (URISyntaxException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                                
                                @Override
                                public Map<String, String> getMetadata() {
                                    return Collections.emptyMap();
                                }
                            };
                            instances.add(instance);
                        }
                    }
                }
            }
            
            return instances;
            
        } catch (Exception e) {
            log.error("Failed to discover service: " + serviceName, e);
            return Collections.emptyList();
        }
    }
}

四、服务网格(Service Mesh)

1. Istio配置

yaml 复制代码
# istio/virtual-service.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: app-virtual-service
  namespace: microservices
spec:
  hosts:
  - app-service
  http:
  - match:
    - headers:
        user-type:
          exact: premium
    route:
    - destination:
        host: app-service
        subset: premium
    fault:
      delay:
        percentage:
          value: 0.1
        fixedDelay: 5s
  - route:
    - destination:
        host: app-service
        subset: standard
    retries:
      attempts: 3
      perTryTimeout: 2s
    timeout: 10s

---
# istio/destination-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: app-destination-rule
  namespace: microservices
spec:
  host: app-service
  subsets:
  - name: premium
    labels:
      version: premium
  - name: standard
    labels:
      version: standard
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 50
        maxRequestsPerConnection: 10
    circuitBreaker:
      consecutiveErrors: 3
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
    loadBalancer:
      simple: ROUND_ROBIN

---
# istio/gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: app-gateway
  namespace: microservices
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - api.example.com
    tls:
      httpsRedirect: true
  - port:
      number: 443
      name: https
      protocol: HTTPS
    hosts:
    - api.example.com
    tls:
      mode: SIMPLE
      credentialName: app-tls

2. 服务网格集成

java 复制代码
// 1. Istio服务网格集成
@Configuration
@EnableIstio
public class IstioConfig {
    
    @Bean
    public IstioServiceMesh istioServiceMesh() {
        return new IstioServiceMesh();
    }
    
    @Bean
    public TrafficManagement trafficManagement() {
        return new TrafficManagement();
    }
}

// 2. 流量管理
@Component
public class TrafficManagement {
    
    @Autowired
    private IstioServiceMesh istioServiceMesh;
    
    public void configureTrafficSplit(String serviceName, Map<String, Integer> versionWeights) {
        try {
            // 配置流量分割
            VirtualService virtualService = new VirtualService();
            virtualService.setMetadata(new ObjectMeta());
            virtualService.getMetadata().setName(serviceName + "-virtual-service");
            virtualService.getMetadata().setNamespace("microservices");
            
            VirtualServiceSpec spec = new VirtualServiceSpec();
            spec.setHosts(Arrays.asList(serviceName));
            
            List<HTTPRoute> httpRoutes = new ArrayList<>();
            HTTPRoute httpRoute = new HTTPRoute();
            
            List<HTTPRouteDestination> destinations = new ArrayList<>();
            for (Map.Entry<String, Integer> entry : versionWeights.entrySet()) {
                HTTPRouteDestination destination = new HTTPRouteDestination();
                destination.setDestination(new Destination(serviceName, entry.getKey()));
                destination.setWeight(entry.getValue());
                destinations.add(destination);
            }
            
            httpRoute.setRoute(destinations);
            httpRoutes.add(httpRoute);
            spec.setHttp(httpRoutes);
            virtualService.setSpec(spec);
            
            istioServiceMesh.createVirtualService(virtualService);
            
        } catch (Exception e) {
            log.error("Failed to configure traffic split for service: " + serviceName, e);
        }
    }
    
    public void configureCircuitBreaker(String serviceName, CircuitBreakerConfig config) {
        try {
            // 配置熔断器
            DestinationRule destinationRule = new DestinationRule();
            destinationRule.setMetadata(new ObjectMeta());
            destinationRule.getMetadata().setName(serviceName + "-destination-rule");
            destinationRule.getMetadata().setNamespace("microservices");
            
            DestinationRuleSpec spec = new DestinationRuleSpec();
            spec.setHost(serviceName);
            
            TrafficPolicy trafficPolicy = new TrafficPolicy();
            CircuitBreaker circuitBreaker = new CircuitBreaker();
            circuitBreaker.setConsecutiveErrors(config.getConsecutiveErrors());
            circuitBreaker.setInterval(config.getInterval());
            circuitBreaker.setBaseEjectionTime(config.getBaseEjectionTime());
            circuitBreaker.setMaxEjectionPercent(config.getMaxEjectionPercent());
            
            trafficPolicy.setCircuitBreaker(circuitBreaker);
            spec.setTrafficPolicy(trafficPolicy);
            destinationRule.setSpec(spec);
            
            istioServiceMesh.createDestinationRule(destinationRule);
            
        } catch (Exception e) {
            log.error("Failed to configure circuit breaker for service: " + serviceName, e);
        }
    }
}

// 3. 服务网格监控
@Component
public class ServiceMeshMonitoring {
    
    @Autowired
    private IstioServiceMesh istioServiceMesh;
    
    @Scheduled(fixedRate = 30000) // 每30秒检查一次
    public void monitorServiceHealth() {
        try {
            // 获取服务健康状态
            Map<String, ServiceHealth> serviceHealths = istioServiceMesh.getServiceHealths();
            
            for (Map.Entry<String, ServiceHealth> entry : serviceHealths.entrySet()) {
                String serviceName = entry.getKey();
                ServiceHealth health = entry.getValue();
                
                if (health.getStatus() == ServiceHealth.Status.UNHEALTHY) {
                    log.warn("Service {} is unhealthy: {}", serviceName, health.getMessage());
                    // 发送告警
                    sendAlert(serviceName, health);
                }
            }
            
        } catch (Exception e) {
            log.error("Failed to monitor service health", e);
        }
    }
    
    private void sendAlert(String serviceName, ServiceHealth health) {
        // 发送告警逻辑
        log.error("ALERT: Service {} is unhealthy - {}", serviceName, health.getMessage());
    }
}

五、云原生监控

1. Prometheus监控

java 复制代码
// 1. Prometheus配置
@Configuration
@EnablePrometheusMetrics
public class PrometheusConfig {
    
    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
    
    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

// 2. 自定义指标
@Component
public class CustomMetrics {
    
    private final Counter requestCounter;
    private final Timer requestTimer;
    private final Gauge activeConnections;
    private final Counter errorCounter;
    
    public CustomMetrics(MeterRegistry meterRegistry) {
        this.requestCounter = Counter.builder("http_requests_total")
            .description("Total HTTP requests")
            .tag("application", "microservices-app")
            .register(meterRegistry);
        
        this.requestTimer = Timer.builder("http_request_duration_seconds")
            .description("HTTP request duration")
            .tag("application", "microservices-app")
            .register(meterRegistry);
        
        this.activeConnections = Gauge.builder("active_connections")
            .description("Active database connections")
            .tag("application", "microservices-app")
            .register(meterRegistry, this, CustomMetrics::getActiveConnections);
        
        this.errorCounter = Counter.builder("http_errors_total")
            .description("Total HTTP errors")
            .tag("application", "microservices-app")
            .register(meterRegistry);
    }
    
    public void incrementRequestCounter(String method, String endpoint) {
        requestCounter.increment(Tags.of("method", method, "endpoint", endpoint));
    }
    
    public void recordRequestDuration(String method, String endpoint, Duration duration) {
        requestTimer.record(duration, Tags.of("method", method, "endpoint", endpoint));
    }
    
    public void incrementErrorCounter(String method, String endpoint, String errorType) {
        errorCounter.increment(Tags.of("method", method, "endpoint", endpoint, "error_type", errorType));
    }
    
    private double getActiveConnections() {
        // 获取活跃连接数
        return 10.0; // 示例值
    }
}

// 3. 监控切面
@Aspect
@Component
public class MonitoringAspect {
    
    @Autowired
    private CustomMetrics customMetrics;
    
    @Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public Object monitorRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        String method = getHttpMethod(joinPoint);
        String endpoint = getEndpoint(joinPoint);
        
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = joinPoint.proceed();
            customMetrics.incrementRequestCounter(method, endpoint);
            return result;
        } catch (Exception e) {
            customMetrics.incrementErrorCounter(method, endpoint, e.getClass().getSimpleName());
            throw e;
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            customMetrics.recordRequestDuration(method, endpoint, Duration.ofMillis(duration));
        }
    }
    
    private String getHttpMethod(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        
        if (method.isAnnotationPresent(GetMapping.class)) return "GET";
        if (method.isAnnotationPresent(PostMapping.class)) return "POST";
        if (method.isAnnotationPresent(PutMapping.class)) return "PUT";
        if (method.isAnnotationPresent(DeleteMapping.class)) return "DELETE";
        
        return "UNKNOWN";
    }
    
    private String getEndpoint(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        
        if (method.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping mapping = method.getAnnotation(RequestMapping.class);
            return mapping.value().length > 0 ? mapping.value()[0] : "/";
        }
        
        return "/";
    }
}

2. Grafana仪表板

json 复制代码
{
  "dashboard": {
    "id": null,
    "title": "Microservices Dashboard",
    "tags": ["microservices", "spring-boot"],
    "timezone": "browser",
    "panels": [
      {
        "id": 1,
        "title": "HTTP Requests",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(http_requests_total[5m])",
            "legendFormat": "{{method}} {{endpoint}}"
          }
        ],
        "yAxes": [
          {
            "label": "Requests/sec"
          }
        ]
      },
      {
        "id": 2,
        "title": "Response Time",
        "type": "graph",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "95th percentile"
          },
          {
            "expr": "histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "50th percentile"
          }
        ],
        "yAxes": [
          {
            "label": "Response Time (s)"
          }
        ]
      },
      {
        "id": 3,
        "title": "Error Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(http_errors_total[5m])",
            "legendFormat": "{{error_type}}"
          }
        ],
        "yAxes": [
          {
            "label": "Errors/sec"
          }
        ]
      },
      {
        "id": 4,
        "title": "Active Connections",
        "type": "singlestat",
        "targets": [
          {
            "expr": "active_connections",
            "legendFormat": "Active Connections"
          }
        ],
        "valueName": "current"
      }
    ],
    "time": {
      "from": "now-1h",
      "to": "now"
    },
    "refresh": "30s"
  }
}

六、日志管理

1. 结构化日志

java 复制代码
// 1. 日志配置
@Configuration
@EnableLogging
public class LoggingConfig {
    
    @Bean
    public LoggerContext loggerContext() {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        return context;
    }
    
    @Bean
    public PatternLayoutEncoder patternLayoutEncoder() {
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n");
        encoder.setContext(loggerContext());
        encoder.start();
        return encoder;
    }
}

// 2. 结构化日志服务
@Service
@Slf4j
public class StructuredLoggingService {
    
    @Autowired
    private ObjectMapper objectMapper;
    
    public void logRequest(String traceId, String method, String endpoint, Map<String, Object> parameters) {
        Map<String, Object> logData = new HashMap<>();
        logData.put("traceId", traceId);
        logData.put("event", "request");
        logData.put("method", method);
        logData.put("endpoint", endpoint);
        logData.put("parameters", parameters);
        logData.put("timestamp", Instant.now());
        
        try {
            String jsonLog = objectMapper.writeValueAsString(logData);
            log.info(jsonLog);
        } catch (Exception e) {
            log.error("Failed to serialize log data", e);
        }
    }
    
    public void logResponse(String traceId, String method, String endpoint, int statusCode, long duration) {
        Map<String, Object> logData = new HashMap<>();
        logData.put("traceId", traceId);
        logData.put("event", "response");
        logData.put("method", method);
        logData.put("endpoint", endpoint);
        logData.put("statusCode", statusCode);
        logData.put("duration", duration);
        logData.put("timestamp", Instant.now());
        
        try {
            String jsonLog = objectMapper.writeValueAsString(logData);
            log.info(jsonLog);
        } catch (Exception e) {
            log.error("Failed to serialize log data", e);
        }
    }
    
    public void logError(String traceId, String method, String endpoint, Throwable throwable) {
        Map<String, Object> logData = new HashMap<>();
        logData.put("traceId", traceId);
        logData.put("event", "error");
        logData.put("method", method);
        logData.put("endpoint", endpoint);
        logData.put("error", throwable.getMessage());
        logData.put("stackTrace", getStackTrace(throwable));
        logData.put("timestamp", Instant.now());
        
        try {
            String jsonLog = objectMapper.writeValueAsString(logData);
            log.error(jsonLog);
        } catch (Exception e) {
            log.error("Failed to serialize log data", e);
        }
    }
    
    private String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        throwable.printStackTrace(pw);
        return sw.toString();
    }
}

// 3. 日志切面
@Aspect
@Component
public class LoggingAspect {
    
    @Autowired
    private StructuredLoggingService loggingService;
    
    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        String traceId = generateTraceId();
        MDC.put("traceId", traceId);
        
        String method = getHttpMethod(joinPoint);
        String endpoint = getEndpoint(joinPoint);
        Map<String, Object> parameters = getParameters(joinPoint);
        
        loggingService.logRequest(traceId, method, endpoint, parameters);
        
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = joinPoint.proceed();
            long duration = System.currentTimeMillis() - startTime;
            loggingService.logResponse(traceId, method, endpoint, 200, duration);
            return result;
        } catch (Exception e) {
            loggingService.logError(traceId, method, endpoint, e);
            throw e;
        } finally {
            MDC.remove("traceId");
        }
    }
    
    private String generateTraceId() {
        return UUID.randomUUID().toString();
    }
    
    private String getHttpMethod(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        
        if (method.isAnnotationPresent(GetMapping.class)) return "GET";
        if (method.isAnnotationPresent(PostMapping.class)) return "POST";
        if (method.isAnnotationPresent(PutMapping.class)) return "PUT";
        if (method.isAnnotationPresent(DeleteMapping.class)) return "DELETE";
        
        return "UNKNOWN";
    }
    
    private String getEndpoint(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        
        if (method.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping mapping = method.getAnnotation(RequestMapping.class);
            return mapping.value().length > 0 ? mapping.value()[0] : "/";
        }
        
        return "/";
    }
    
    private Map<String, Object> getParameters(ProceedingJoinPoint joinPoint) {
        Map<String, Object> parameters = new HashMap<>();
        Object[] args = joinPoint.getArgs();
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        
        for (int i = 0; i < args.length; i++) {
            parameters.put(paramNames[i], args[i]);
        }
        
        return parameters;
    }
}

2. ELK日志收集

yaml 复制代码
# docker-compose-elk.yml
version: '3.8'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.15.0
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    networks:
      - elk

  logstash:
    image: docker.elastic.co/logstash/logstash:7.15.0
    ports:
      - "5044:5044"
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
    depends_on:
      - elasticsearch
    networks:
      - elk

  kibana:
    image: docker.elastic.co/kibana/kibana:7.15.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
    networks:
      - elk

  filebeat:
    image: docker.elastic.co/beats/filebeat:7.15.0
    volumes:
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml
      - ./logs:/usr/share/filebeat/logs
    depends_on:
      - logstash
    networks:
      - elk

volumes:
  elasticsearch_data:

networks:
  elk:
    driver: bridge

七、综合实战练习

1. 云原生微服务项目

java 复制代码
// 1. 云原生应用主类
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableConfigServer
@EnablePrometheusMetrics
public class CloudNativeMicroservicesApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(CloudNativeMicroservicesApplication.class, args);
    }
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public WebClient webClient() {
        return WebClient.builder()
            .baseUrl("http://localhost:8080")
            .build();
    }
}

// 2. 云原生配置
@Configuration
@EnableConfigurationProperties
public class CloudNativeConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "app.cloud")
    public CloudConfig cloudConfig() {
        return new CloudConfig();
    }
    
    @Bean
    public CloudNativeService cloudNativeService(CloudConfig cloudConfig) {
        return new CloudNativeService(cloudConfig);
    }
}

// 3. 云原生服务
@Service
@Slf4j
public class CloudNativeService {
    
    private final CloudConfig cloudConfig;
    private final CustomMetrics customMetrics;
    private final StructuredLoggingService loggingService;
    
    public CloudNativeService(CloudConfig cloudConfig, 
                            CustomMetrics customMetrics,
                            StructuredLoggingService loggingService) {
        this.cloudConfig = cloudConfig;
        this.customMetrics = customMetrics;
        this.loggingService = loggingService;
    }
    
    public String processRequest(String request) {
        String traceId = generateTraceId();
        MDC.put("traceId", traceId);
        
        try {
            loggingService.logRequest(traceId, "POST", "/process", 
                Map.of("request", request));
            
            // 处理请求
            String response = doProcess(request);
            
            loggingService.logResponse(traceId, "POST", "/process", 200, 100);
            customMetrics.incrementRequestCounter("POST", "/process");
            
            return response;
            
        } catch (Exception e) {
            loggingService.logError(traceId, "POST", "/process", e);
            customMetrics.incrementErrorCounter("POST", "/process", e.getClass().getSimpleName());
            throw e;
        } finally {
            MDC.remove("traceId");
        }
    }
    
    private String doProcess(String request) {
        // 模拟处理逻辑
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "Processed: " + request;
    }
    
    private String generateTraceId() {
        return UUID.randomUUID().toString();
    }
}

2. 部署脚本

bash 复制代码
#!/bin/bash
# deploy.sh - 云原生应用部署脚本

set -e

# 配置变量
APP_NAME="microservices-app"
VERSION="1.0.0"
NAMESPACE="microservices"
REGISTRY="your-registry.com"

echo "开始部署云原生应用..."

# 1. 构建Docker镜像
echo "构建Docker镜像..."
docker build -t ${REGISTRY}/${APP_NAME}:${VERSION} .
docker push ${REGISTRY}/${APP_NAME}:${VERSION}

# 2. 部署到Kubernetes
echo "部署到Kubernetes..."
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml

# 3. 等待部署完成
echo "等待部署完成..."
kubectl wait --for=condition=available --timeout=300s deployment/${APP_NAME}-deployment -n ${NAMESPACE}

# 4. 检查部署状态
echo "检查部署状态..."
kubectl get pods -n ${NAMESPACE}
kubectl get services -n ${NAMESPACE}
kubectl get ingress -n ${NAMESPACE}

# 5. 运行健康检查
echo "运行健康检查..."
kubectl port-forward service/${APP_NAME}-service 8080:80 -n ${NAMESPACE} &
PORT_FORWARD_PID=$!

sleep 10

curl -f http://localhost:8080/actuator/health || {
    echo "健康检查失败"
    kill $PORT_FORWARD_PID
    exit 1
}

kill $PORT_FORWARD_PID

echo "部署完成!"
echo "应用访问地址: http://api.example.com"
echo "监控地址: http://grafana.example.com"
echo "日志地址: http://kibana.example.com"

八、学习总结

1. 云原生核心概念

  • 容器化: 使用Docker将应用打包成容器
  • 编排: 使用Kubernetes管理容器生命周期
  • 服务网格: 使用Istio管理服务间通信
  • 监控: 使用Prometheus和Grafana监控应用
  • 日志
相关推荐
渣哥4 小时前
原来 Java 里线程安全集合有这么多种
java
间彧4 小时前
Spring Boot集成Spring Security完整指南
java
间彧4 小时前
Spring Secutiy基本原理及工作流程
java
Java水解5 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆7 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学8 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole8 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊8 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端