Hiệu suất nhà máy / bộ nhớ đệm xấu


9

Tôi đã có một ứng dụng java ee khá lớn với một đường dẫn lớn, xử lý nhiều xml. Hiện tại tôi đang cố gắng tăng tốc một số chức năng của mình và định vị các đường dẫn mã chậm thông qua các trình biên dịch lấy mẫu.

Một điều tôi nhận thấy là đặc biệt là các phần trong mã của chúng tôi trong đó chúng tôi có các cuộc gọi như TransformerFactory.newInstance(...)rất chậm. Tôi đã theo dõi điều này để FactoryFinderphương pháp findServiceProviderluôn tạo ra một ServiceLoaderthể hiện mới . Trong ServiceLoader javadoc tôi đã tìm thấy các lưu ý sau về bộ nhớ đệm:

Các nhà cung cấp được định vị và khởi động một cách lười biếng, đó là, theo yêu cầu. Trình tải dịch vụ duy trì bộ đệm của các nhà cung cấp đã được tải cho đến nay. Mỗi lời gọi của phương thức iterator trả về một trình vòng lặp đầu tiên mang lại tất cả các phần tử của bộ đệm, theo thứ tự khởi tạo, sau đó lười biếng định vị và khởi tạo bất kỳ nhà cung cấp nào còn lại, lần lượt thêm từng bộ vào bộ đệm. Bộ nhớ cache có thể được xóa thông qua phương thức tải lại.

Càng xa càng tốt. Đây là một phần của FactoryFinder#findServiceProviderphương thức OpenJDKs :

private static <T> T findServiceProvider(final Class<T> type)
        throws TransformerFactoryConfigurationError
    {
      try {
            return AccessController.doPrivileged(new PrivilegedAction<T>() {
                public T run() {
                    final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
                    final Iterator<T> iterator = serviceLoader.iterator();
                    if (iterator.hasNext()) {
                        return iterator.next();
                    } else {
                        return null;
                    }
                 }
            });
        } catch(ServiceConfigurationError e) {
            ...
        }
    }

Mỗi cuộc gọi để findServiceProvidergọi ServiceLoader.load. Điều này tạo ra một ServiceLoader mới mỗi lần. Bằng cách này, có vẻ như không sử dụng cơ chế bộ nhớ đệm ServiceLoaders nào cả. Mỗi cuộc gọi sẽ quét đường dẫn lớp cho ServiceProvider được yêu cầu.

Những gì tôi đã thử:

  1. Tôi biết bạn có thể thiết lập một thuộc tính hệ thống như javax.xml.transform.TransformerFactoryđể chỉ định một triển khai cụ thể. Bằng cách này, Factory Downloader không sử dụng quy trình ServiceLoader và siêu nhanh. Đáng buồn thay, đây là một thuộc tính rộng jvm và ảnh hưởng đến các quá trình java khác đang chạy trong jvm của tôi. Ví dụ: ứng dụng của tôi giao hàng với Saxon và nên sử dụng com.saxonica.config.EnterpriseTransformerFactoryTôi đã có một ứng dụng khác không giao hàng với Saxon. Ngay khi tôi đặt thuộc tính hệ thống, ứng dụng khác của tôi không khởi động được, vì không có com.saxonica.config.EnterpriseTransformerFactoryđường dẫn lớp của nó. Vì vậy, điều này dường như không phải là một lựa chọn cho tôi.
  2. Tôi đã cấu trúc lại mọi nơi mà a TransformerFactory.newInstanceđược gọi và lưu vào bộ biến áp. Nhưng có nhiều nơi khác nhau trong phần phụ thuộc của tôi, nơi tôi không thể cấu trúc lại mã.

Câu hỏi của tôi là: Tại sao Factory Downloader không sử dụng lại ServiceLoader? Có cách nào để tăng tốc toàn bộ quá trình ServiceLoader này ngoài việc sử dụng các thuộc tính hệ thống không? Không thể thay đổi điều này trong JDK để FactorySense sử dụng lại một cá thể ServiceLoader? Ngoài ra, điều này không cụ thể cho một Factory Downloader duy nhất. Bahaviour này là giống nhau cho tất cả các lớp Factory Downloader trong javax.xmlgói tôi đã xem xét cho đến nay.

Tôi đang sử dụng OpenJDK 8/11. Các ứng dụng của tôi được triển khai trong một ví dụ Tomcat 9.

Chỉnh sửa: Cung cấp thêm chi tiết

Đây là ngăn xếp cuộc gọi cho một lệnh gọi XMLInputFactory.newInstance duy nhất: nhập mô tả hình ảnh ở đây

Nơi mà hầu hết các tài nguyên được sử dụng là trong ServiceLoaders$LazyIterator.hasNextService. Phương thức này gọi getResourcesClassLoader để đọc META-INF/services/javax.xml.stream.XMLInputFactorytệp. Cuộc gọi đó chỉ mất khoảng 35ms mỗi lần.

Có cách nào để hướng dẫn Tomcat lưu trữ tốt hơn các tệp này để chúng được phục vụ nhanh hơn không?


Tôi đồng ý với đánh giá của bạn về Factory Downloader.java. Có vẻ như nó nên lưu vào bộ đệm dịch vụ. Bạn đã thử tải xuống nguồn openjdk và xây dựng nó. Tôi biết rằng âm thanh như một nhiệm vụ lớn nhưng nó có thể không. Ngoài ra, có thể đáng để viết một vấn đề chống lại Factory Downloader.java và xem liệu có ai đó xử lý vấn đề này và đưa ra giải pháp hay không.
djhallx

Bạn đã thử đặt thuộc tính bằng cách sử dụng -Dcờ cho Tomcatquy trình của mình chưa? Ví dụ: -Djavax.xml.transform.TransformerFactory=<factory class>.Không nên ghi đè các thuộc tính cho các ứng dụng khác. Bài viết của bạn được mô tả tốt và có lẽ bạn đã thử nó nhưng tôi muốn xác nhận. Xem Cách đặt thuộc tính hệ thống Javax.xml.transform.TransformerFactory , Cách đặt Đối số HeapMemory hoặc JVM trong Tomcat
Michał Ziober

Câu trả lời:


1

35 ms có vẻ như có thời gian truy cập đĩa liên quan và điều đó chỉ ra vấn đề với bộ đệm của hệ điều hành.

Nếu có bất kỳ thư mục / mục không jar nào trên đường dẫn lớp có thể làm mọi thứ chậm lại. Ngoài ra nếu tài nguyên không có mặt tại vị trí đầu tiên được kiểm tra.

ClassLoader.getResourcecó thể bị ghi đè nếu bạn có thể đặt trình tải lớp ngữ cảnh của luồng, thông qua cấu hình (tôi đã không chạm vào tomcat trong nhiều năm) hoặc chỉ Thread.setContextClassLoader.


Âm thanh như thế này có thể làm việc. Tôi sẽ có một cái nhìn về điều này sớm hay muộn. Cảm ơn bạn!
Wagner Michael

1

Tôi có thể có thêm 30 phút để gỡ lỗi này và xem Tomcat thực hiện Resource Cacheing như thế nào.

Đặc biệt CachedResource.validateResources(có thể tìm thấy trong ngọn lửa ở trên) là điều tôi quan tâm. Nó trả về truenếu CachedResourcevẫn còn hiệu lực:

protected boolean validateResources(boolean useClassLoaderResources) {
        long now = System.currentTimeMillis();
        if (this.webResources == null) {
            ...
        }

        // TTL check here!!
        if (now < this.nextCheck) {
            return true;
        } else if (this.root.isPackedWarFile()) {
            this.nextCheck = this.ttl + now;
            return true;
        } else {
            return false;
        }
    }

Có vẻ như một bộ nhớ đệm thực sự có thời gian để sống (ttl). Thực sự có một cách trong Tomcat để định cấu hình cacheTtl nhưng bạn chỉ có thể tăng giá trị này. Có vẻ như cấu hình bộ nhớ đệm tài nguyên không thực sự linh hoạt một cách dễ dàng.

Vì vậy, Tomcat của tôi có giá trị mặc định là 5000 ms được cấu hình. Điều này đã lừa tôi khi thực hiện kiểm tra hiệu suất vì tôi có ít hơn 5 giây giữa các yêu cầu của mình (nhìn vào biểu đồ và công cụ). Đó là lý do tại sao tất cả các yêu cầu của tôi về cơ bản chạy mà không có bộ nhớ cache và kích hoạt nặng như vậy ZipFile.openmỗi lần.

Vì vậy, vì tôi không thực sự có nhiều kinh nghiệm với cấu hình Tomcat nên tôi chưa chắc đâu là giải pháp phù hợp ở đây. Việc tăng cacheTTL giúp lưu trữ lâu hơn nhưng không khắc phục được sự cố trong thời gian dài.

Tóm lược

Tôi nghĩ rằng thực sự có hai thủ phạm ở đây.

  1. Các lớp Factory Downloader không sử dụng lại ServiceLoader. Có thể có một lý do hợp lệ tại sao họ không sử dụng lại chúng - mặc dù tôi thực sự không thể nghĩ ra một lý do.

  2. Tomcat đuổi bộ nhớ cache sau một thời gian cố định cho tài nguyên ứng dụng web (các tệp trong đường dẫn lớp - giống như một ServiceLoadercấu hình)

Kết hợp điều này với việc không xác định Thuộc tính hệ thống cho lớp ServiceLoader và bạn nhận được một cuộc gọi Factory Downloader chậm mỗi cacheTtlgiây.

Bây giờ tôi có thể sống với việc tăng cacheTtl trong một thời gian dài hơn. Tôi cũng có thể xem qua đề xuất của Tom Hawtins về việc ghi đè Classloader.getResourcesngay cả khi tôi nghĩ rằng đây là một cách khắc nghiệt để thoát khỏi nút thắt hiệu suất này. Nó có thể đáng xem mặc dù.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.