Kỹ thuật tối ưu hiệu năng với Mẫu thiết kế: Phân tích thực tế

Thiết kế mẫu (design pattern) là cách tiếp cận có hệ thống để giải quyết các vấn đề lập trình phổ biến, giúp cải thiện tính nhất quán và khả năng giao tiếp giữa các lập trình viên. Trong thực tế, hầu hết các mẫu thiết kế không trực tiếp nâng cao hiệu năng, mà chỉ định hình cách tổ chức mã nguồn. Bài viết này tập trung vào bốn mẫu thiết kế liên quan trực tiếp đến hiệu năng: Proxy, Singleton, Flyweight và Prototype.

### Phân tích chậm trễ trong Proxy động

Spring sử dụng Proxy để triển khai AOP. Khi xử lý các cắt mặt phức tạp (ví dụ: xác thực, ghi log), hiệu năng có thể bị ảnh hưởng. Dưới đây là cách dùng arthas để xác định điểm nghẽn hiệu năng mà không cần hiểu sâu mã nguồn:

1. Tạo service đơn giản:

@Component
public class ServiceBean {
    public void execute() {
        System.out.println("Process completed");
    }
}

2. Viết cắt mặt với độ trễ 500ms:

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(* com.example.service.ServiceBean.*(..))")
    public void servicePointcut() {}
    
    @Before("servicePointcut()")
    public void logBefore() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }
}

3. Kiểm tra hiệu năng qua controller:

@Controller
public class PerformanceController {
    @Autowired
    private ServiceBean service;
    
    @GetMapping("/test")
    public String test() {
        long start = System.currentTimeMillis();
        service.execute();
        return "Cost: " + (System.currentTimeMillis() - start) + "ms";
    }
}

Chạy endpoint `/test` sẽ hiển thị thời gian thực thi (~500ms). Dùng trace com.example.service.ServiceBean execute trong arthas để xác định điểm chậm trễ nằm ở lớp proxy.

### So sánh Proxy JDK vs CGLib

Trong Java, Proxy động có hai triển khai:

  • JDK Proxy: Dựa trên interface, sử dụng InvocationHandler
  • CGLib: Chạy trên lớp, dùng MethodInterceptor

Benchmark hiệu năng (Java 11):

Benchmark              Mode  Cnt   Score    Error  Units
ProxyBenchmark.cglib  thrpt   10  72500.3  ± 1200.5  ops/ms
ProxyBenchmark.jdk    thrpt   10  85000.7  ±  900.2  ops/ms

Kết quả cho thấy JDK Proxy nhanh hơn CGLib ~17% trong truy cập phương thức. Spring chọn CGLib chủ yếu vì khả năng proxy lớp không interface.

### Mẫu Singleton: Cách triển khai tối ưu

Spring sử dụng Singleton làm mặc định. Cách triển khai an toàn nhất:

public enum ServiceRegistry {
    INSTANCE;
    
    private final ServiceBean service = new ServiceBean();
    
    public ServiceBean getService() {
        return service;
    }
}

Tránh sử dụng double-checked locking do phức tạp và rủi ro với reordering. Mẫu enum đảm bảo tính nhất quán và không cần khóa đồng bộ.

### Mẫu Flyweight: Tái sử dụng đối tượng

Flyweight tối ưu hiệu năng bằng cách chia sẻ đối tượng. Ví dụ với chiến lược thanh toán:

Map<String, PaymentStrategy> strategies = new HashMap<>();
strategies.put("credit", new CreditPayment());
strategies.put("paypal", new PayPalPayment());

Cách tiếp cận này không chỉ là Flyweight (tái sử dụng) mà cũng là Strategy Pattern (thay đổi chiến lược). Điều quan trọng là xác định ngữ cảnh khi áp dụng mẫu.

### Mẫu Prototype: Giới hạn thực tế

Mẫu Prototype dựa trên phương thức clone() của Object, nhưng thường không được dùng do:

  • Chỉ hỗ trợ copy shallow
  • Phải viết code phức tạp cho deep copy
  • Thay vào đó, sử dụng new hoặc serial hóa (ví dụ: Serializable) dễ dàng hơn

Trong thực tế, Prototype chủ yếu là một tư duy, không phải giải pháp tốc độ.

Thẻ: Spring AOP JDK Proxy cglib Singleton Pattern Flyweight Pattern

Đăng vào ngày 2 tháng 6 lúc 03:09