Thiết kế động cơ biểu thức trực quan với khả năng sẵn sàng cao

Khi xây dựng hệ thống xử lý quy tắc nghiệp vụ linh hoạt, việc phụ thuộc vào một dịch vụ trung tâm như RuleLink có thể tạo ra điểm nghẽn về độ tin cậy. Một sự cố nhỏ ở tầng biểu thức có thể làm tê liệt toàn bộ luồng xử lý tài chính – đặc biệt khi mỗi ngày hệ thống nghiệp vụ-tài chính thực hiện hơn 50.000 lần gọi tới engine. Giải pháp không nằm ở việc mở rộng vô hạn số node, mà ở việc tái cấu trúc mô hình tương tác: từ gọi liên tục sang tải cục bộ + đồng bộ thông minh.

Nguyên lý ổn định kiến trúc: Giảm fan-out, tăng độc lập

Theo mô hình phân tích ổn định trong Clean Architecture, chỉ số bất ổn (Instability) được tính bằng công thức:

I = Fan-out / (Fan-in + Fan-out)

Trong đó Fan-out đại diện cho số lượng phụ thuộc bên ngoài của một thành phần — đây chính là yếu tố làm giảm độ bền. Thay vì tăng instance RuleLink, chúng ta tối ưu bằng cách:

  • Tải toàn bộ cấu hình quy tắc vào bộ nhớ ứng dụng ngay lúc khởi động.
  • Duy trì kết nối kéo dài (long polling) để nhận thay đổi theo thời gian thực.
  • Bổ sung cơ chế kéo dự phòng định kỳ nhằm đảm bảo tính nhất quán tuyệt đối.

Cơ chế đẩy thay đổi (Push Mechanism)

Hệ thống sử dụng mô hình "producer-consumer" với cơ sở dữ liệu làm trung gian lưu trữ nhật ký thay đổi. Thành phần ReleaseMessageScanner chạy nền, quét bảng app_scene_change_log mỗi 5 giây để phát hiện cập nhật mới và thông báo tới các listener.

Để tránh tải không cần thiết, NotificationController áp dụng kỹ thuật DeferredResult trong Spring MVC:

  1. Client gửi yêu cầu HTTP chờ đợi biến đổi.
  2. Server giữ kết nối mở tối đa 60s.
  3. Nếu có thay đổi phù hợp → trả về danh sách scene bị ảnh hưởng qua setResult().
  4. Nếu không có thay đổi → trả mã 304 Not Modified.

Mã nguồn lõi – Trình quét thông báo

public class ConfigChangeWatcher implements InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(ConfigChangeWatcher.class);

    private final ChangeLogRepository logRepo;
    private final List<ConfigUpdateHandler> handlers;
    private final ScheduledExecutorService scheduler;

    public ConfigChangeWatcher(ChangeLogRepository logRepo) {
        this.logRepo = logRepo;
        this.handlers = new CopyOnWriteArrayList<>();
        this.scheduler = Executors.newScheduledThreadPool(
            1, new NamedThreadFactory("ConfigChangeWatcher", true)
        );
    }

    @Override
    public void afterPropertiesSet() {
        scheduler.scheduleWithFixedDelay(
            this::scanAndNotify,
            5_000,
            5_000,
            TimeUnit.MILLISECONDS
        );
    }

    private void scanAndNotify() {
        try {
            List<ChangeRecord> updates = logRepo.findPendingUpdates();
            if (!updates.isEmpty()) {
                handlers.forEach(handler -> 
                    updates.forEach(record -> handler.onUpdate(record.getAppId(), record.getSceneId()))
                );
                logRepo.markAsProcessed(updates);
            }
        } catch (Exception e) {
            log.error("Failed to process config changes", e);
        }
    }

    public void registerHandler(ConfigUpdateHandler handler) {
        if (!handlers.contains(handler)) {
            handlers.add(handler);
        }
    }
}

Thiết kế phía client

Client được đóng gói dưới dạng Spring Boot Starter, kích hoạt qua chú giải @EnableExpressionEngineClient:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ExpressionEngineAutoConfiguration.class)
public @interface EnableExpressionEngineClient {
    String serverUrl() default "http://rulelink-server";
    int refreshIntervalMinutes() default 5;
    boolean enableLongPolling() default true;
}

Cơ chế vận hành bao gồm ba lớp:

  • Long-polling layer: Kết nối HTTP kéo dài tới endpoint /v1/notify, nhận cập nhật tức thì.
  • Polling fallback: Mỗi 5 phút (có thể cấu hình), client gửi phiên bản hiện tại lên server và nhận phản hồi 304 nếu không có thay đổi.
  • In-memory cache: Toàn bộ biểu thức được lưu trong RuleCacheManager, hỗ trợ tra cứu O(1) và kiểm tra tính nhất quán qua endpoint giám sát /actuator/rulecache.

Triển khai thực tế

Giải pháp được tích hợp vào hệ thống nghiệp vụ-tài chính trong vòng 3 tuần phát triển ngoài giờ. Với hai cơ chế đồng bộ (real-time push + scheduled pull), hệ thống duy trì hoạt động bình thường ngay cả khi RuleLink ngừng phục vụ hoàn toàn sau khi khởi động. Thời gian trễ tối đa giữa thay đổi quy tắc và áp dụng trên client là dưới 60s — đảm bảo tính nhất quán mà không đánh đổi độ ổn định.

Thẻ: spring-boot long-polling configuration-management microservices-architecture Java

Đăng vào ngày 2 tháng 6 lúc 17:13