Chuyển đến nội dung chính

レッスン 18: キャッシュ、ヘルスチェック、API ゲートウェイ

@CacheResult を使用した Redis キャッシュ、ヘルスチェック (稼働状況/準備完了)、API ゲートウェイ パターン、レート制限、集約。

💻 プログラミング — レッスン 17 レッスン 18: キャッシュ、ヘルスチェック、API ゲートウェイ

Quarkus マイクロサービス: 基本から運用まで

パート 5: 回復力と可観測性

xdev.asia

はじめに

この記事では、Redis キャッシュによるデータベース負荷の軽減、ヘルス チェック (Kubernetes の準備/活性プローブ用)、および API ゲートウェイ (フロントエンドの単一のエントリ ポイントとして) という 3 つの横断的な懸念事項を備えたマイクロサービス プラットフォームを完成させます。

Redis キャッシュ

依存関係

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-cache</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-redis-cache</artifactId>
</dependency>

構成

# Dev Services tự động start Redis container
# Production:
%prod.quarkus.redis.hosts=redis://localhost:6379
%prod.quarkus.redis.password=${REDIS_PASSWORD}

# Cache TTL
quarkus.cache.redis.product-cache.ttl=5M
quarkus.cache.redis.category-cache.ttl=30M
quarkus.cache.redis.product-list-cache.ttl=2M

@CacheResult — キャッシュメソッドの戻り値

import io.quarkus.cache.CacheResult;
import io.quarkus.cache.CacheInvalidate;
import io.quarkus.cache.CacheInvalidateAll;
import io.quarkus.cache.CacheKey;

@ApplicationScoped
public class ProductService {

    // Cache kết quả findById
    @CacheResult(cacheName = "product-cache")
    public ProductDTO getById(@CacheKey Long id) {
        Log.infof("Cache MISS: product %d", id);
        Product product = productRepo.findByIdOptional(id)
            .orElseThrow(() ->
                new ResourceNotFoundException("Product", id));
        return ProductDTO.from(product);
    }

    // Invalidate khi update
    @CacheInvalidate(cacheName = "product-cache")
    @Transactional
    public ProductDTO update(@CacheKey Long id,
                             UpdateProductRequest req) {
        Product product = productRepo.findByIdOptional(id)
            .orElseThrow(() ->
                new ResourceNotFoundException("Product", id));
        // ... update fields ...
        return ProductDTO.from(product);
    }

    // Invalidate khi delete
    @CacheInvalidate(cacheName = "product-cache")
    @Transactional
    public void delete(@CacheKey Long id) {
        productRepo.deleteById(id);
    }

    // Cache list với composite key
    @CacheResult(cacheName = "product-list-cache")
    public List<ProductListDTO> listByCategory(
            @CacheKey String category,
            @CacheKey int page,
            @CacheKey int size) {
        return productRepo
            .findActive(category, null,
                Sort.by("createdAt").descending())
            .page(Page.of(page, size))
            .list().stream()
            .map(ProductListDTO::from).toList();
    }

    // Invalidate toàn bộ cache
    @CacheInvalidateAll(cacheName = "product-list-cache")
    @Transactional
    public ProductDTO create(CreateProductRequest req) {
        // ... create product ...
    }
}

プログラムによるキャッシュ

import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.quarkus.cache.CaffeineCache;

@ApplicationScoped
public class CacheManager {

    @Inject
    @CacheName("product-cache")
    Cache productCache;

    public void warmUp() {
        // Pre-load popular products
        List<Product> popular = Product
            .find("status = 'ACTIVE'",
                  Sort.by("viewCount").descending())
            .page(Page.of(0, 100))
            .list();

        for (Product p : popular) {
            productCache.as(CaffeineCache.class)
                .put(p.id, CompletableFuture.completedFuture(
                    ProductDTO.from(p)));
        }
    }

    public void evictAll() {
        productCache.invalidateAll().await().indefinitely();
    }
}

ヘルスチェック

依存関係 (自動的に利用可能)

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

自動エンドポイント

# Tất cả health checks
curl http://localhost:8081/q/health

# Liveness — service đang chạy?
curl http://localhost:8081/q/health/live

# Readiness — service sẵn sàng nhận traffic?
curl http://localhost:8081/q/health/ready

# Startup — service đã start xong?
curl http://localhost:8081/q/health/started

組み込みのヘルスチェック

Quarkus は以下を自動的に追加します。

  • データベース (データソース): データベース接続はOKですか?
  • Kafka: ブローカーが接続されていますか?
  • Redis: キャッシュが接続されていますか?

カスタムヘルスチェック

import org.eclipse.microprofile.health.*;

@Liveness
@ApplicationScoped
public class ServiceLivenessCheck
        implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse
            .named("Product Service")
            .up()
            .withData("version", "1.0.0")
            .build();
    }
}

@Readiness
@ApplicationScoped
public class DatabaseReadinessCheck
        implements HealthCheck {

    @Inject
    ProductRepository productRepo;

    @Override
    public HealthCheckResponse call() {
        try {
            long count = productRepo.count();
            return HealthCheckResponse
                .named("Database")
                .up()
                .withData("products.count", count)
                .build();
        } catch (Exception e) {
            return HealthCheckResponse
                .named("Database")
                .down()
                .withData("error", e.getMessage())
                .build();
        }
    }
}

@Readiness
@ApplicationScoped
public class ExternalServiceCheck
        implements HealthCheck {

    @Inject @RestClient
    ProductServiceClient productClient;

    @Override
    public HealthCheckResponse call() {
        try {
            productClient.list(0, 1);
            return HealthCheckResponse
                .named("Product Service")
                .up().build();
        } catch (Exception e) {
            return HealthCheckResponse
                .named("Product Service")
                .down()
                .withData("error", e.getMessage())
                .build();
        }
    }
}

ヘルスチェックの応答

{
  "status": "UP",
  "checks": [
    {
      "name": "Product Service",
      "status": "UP",
      "data": { "version": "1.0.0" }
    },
    {
      "name": "Database",
      "status": "UP",
      "data": { "products.count": 150 }
    },
    {
      "name": "Reactive PostgreSQL connections health check",
      "status": "UP"
    }
  ]
}

API ゲートウェイ — Nginx / Envoy

Nginx の構成

# nginx.conf
upstream product-service {
    server product-service:8080;
}

upstream order-service {
    server order-service:8080;
}

upstream payment-service {
    server payment-service:8080;
}

upstream keycloak {
    server keycloak:8080;
}

server {
    listen 80;
    server_name api.ecommerce.xdev.asia;

    # Rate limiting
    limit_req_zone $binary_remote_addr
        zone=api:10m rate=100r/s;

    # Product Service
    location /api/v1/products {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://product-service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Order Service
    location /api/v1/orders {
        limit_req zone=api burst=10 nodelay;
        proxy_pass http://order-service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Payment Service
    location /api/v1/payments {
        limit_req zone=api burst=5 nodelay;
        proxy_pass http://payment-service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Keycloak
    location /realms {
        proxy_pass http://keycloak;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Health check aggregation
    location /health {
        access_log off;
        default_type application/json;
        return 200 '{"status":"UP","gateway":"nginx"}';
    }
}

Docker Compose

services:
  api-gateway:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - product-service
      - order-service
      - payment-service

演習

  1. 製品サービス用の Redis キャッシュを追加します (getById、 listByCategory) 2.プロダクト作成・更新・削除時のキャッシュ無効化を実装
  2. カスタム ヘルス チェックの作成: データベース、外部サービス
  3. サービスへの Nginx API Gateway ルーティングを構成する
  4. Nginx にレート制限を追加する
  5. テスト: 起動 → 準備完了 → 活性プローブ

概要

  • Redis キャッシュ (@CacheResult、 @CacheInvalidate) DBの負荷を軽減します
  • 健康診断 (/q/health/live、 /q/health/ready) Kubernetes プローブの場合
  • カスタムヘルスが拡張されます HealthCheck — データベース、外部サービスを確認する
  • API ゲートウェイ (Nginx) — シングル エントリ ポイント、ルーティング、レート制限、TLS 終端
  • レート制限によりバックエンド サービスを悪用から保護します

次の記事: テスト — @QuarkusTest および Testcontainers。