Pic-a-daily: Lưu trữ và phân tích ảnh bằng thư viện ứng dụng Java gốc của Google

1. Tổng quan

Trong lớp học lập trình đầu tiên, bạn sẽ lưu trữ các bức ảnh trong một bộ chứa. Thao tác này sẽ tạo một sự kiện tạo tệp do một dịch vụ được triển khai trong Cloud Run xử lý. Dịch vụ này sẽ thực hiện lệnh gọi đến Vision API để phân tích hình ảnh và lưu kết quả trong kho dữ liệu.

427de3100de3a61e.png.

Kiến thức bạn sẽ học được

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore

2. Thiết lập và yêu cầu

Thiết lập môi trường theo tiến độ riêng

  1. Đăng nhập vào Google Cloud Console rồi tạo dự án mới hoặc sử dụng lại dự án hiện có. Nếu chưa có tài khoản Gmail hoặc Google Workspace, bạn phải tạo một tài khoản.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Tên dự án là tên hiển thị của những người tham gia dự án này. Đây là một chuỗi ký tự không được API của Google sử dụng. Bạn có thể cập nhật thông tin này bất cứ lúc nào.
  • Mã dự án phải là duy nhất trong tất cả các dự án Google Cloud và không thể thay đổi (không thể thay đổi sau khi đã đặt). Cloud Console sẽ tự động tạo một chuỗi duy nhất; thường bạn không quan tâm đến sản phẩm đó là gì. Trong hầu hết các lớp học lập trình, bạn sẽ cần tham chiếu đến Mã dự án (mã này thường được xác định là PROJECT_ID). Nếu không thích mã đã tạo, bạn có thể tạo một mã nhận dạng ngẫu nhiên khác. Ngoài ra, bạn có thể thử phương pháp của riêng mình và xem có được cung cấp hay không. Bạn không thể thay đổi thông tin này sau bước này và thông tin đó sẽ vẫn tồn tại trong thời gian của dự án.
  • Đối với thông tin của bạn, có giá trị thứ ba, Project Number (Số dự án) mà một số API sử dụng. Tìm hiểu thêm về cả ba giá trị này trong tài liệu này.
  1. Tiếp theo, bạn sẽ phải bật tính năng thanh toán trong Cloud Console để sử dụng API/tài nguyên trên đám mây. Việc chạy qua lớp học lập trình này sẽ không tốn nhiều chi phí. Để tắt các tài nguyên nhằm tránh bị tính phí ngoài hướng dẫn này, bạn có thể xoá các tài nguyên bạn đã tạo hoặc xoá toàn bộ dự án. Người dùng mới của Google Cloud đủ điều kiện tham gia chương trình Dùng thử miễn phí 300 USD.

Khởi động Cloud Shell

Mặc dù bạn có thể vận hành Google Cloud từ xa trên máy tính xách tay, nhưng trong lớp học lập trình này, bạn sẽ sử dụng Google Cloud Shell, một môi trường dòng lệnh chạy trong Đám mây.

Trong Google Cloud Console, hãy nhấp vào biểu tượng Cloud Shell ở thanh công cụ trên cùng bên phải:

55efc1aaa7a4d3ad.pngS

Sẽ chỉ mất một chút thời gian để cấp phép và kết nối với môi trường. Sau khi hoàn tất, bạn sẽ thấy như sau:

7ffe5cbb04455448.pngS

Máy ảo này chứa tất cả các công cụ phát triển mà bạn cần. Phiên bản này cung cấp thư mục gốc có dung lượng ổn định 5 GB và chạy trên Google Cloud, giúp nâng cao đáng kể hiệu suất và khả năng xác thực của mạng. Bạn có thể thực hiện tất cả các công việc trong lớp học lập trình này trong trình duyệt. Bạn không cần cài đặt gì cả.

3. Bật API

Đối với phòng thí nghiệm này, bạn sẽ sử dụng Cloud Functions and Vision API, nhưng trước tiên, bạn cần bật các API này trong Cloud Console hoặc bằng gcloud.

Để bật Vision API trong Cloud Console, hãy tìm Cloud Vision API trong thanh tìm kiếm:

cf48b1747ba6a6fb.png

Bạn sẽ được chuyển đến trang Cloud Vision API:

ba4af419e6086fbb.png

Nhấp vào nút ENABLE.

Ngoài ra, bạn cũng có thể bật dịch vụ Cloud Shell bằng công cụ dòng lệnh gcloud.

Bên trong Cloud Shell, hãy chạy lệnh sau:

gcloud services enable vision.googleapis.com

Bạn sẽ thấy thao tác hoàn tất thành công:

Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.

Bật cả Cloud Run và Cloud Build:

gcloud services enable cloudbuild.googleapis.com \
  run.googleapis.com

4. Tạo bộ chứa (bảng điều khiển)

Tạo bộ chứa lưu trữ cho ảnh. Bạn có thể thực hiện việc này từ bảng điều khiển Google Cloud Platform ( console.cloud.google.com) hoặc bằng công cụ dòng lệnh gsutil của Cloud Shell hoặc môi trường phát triển cục bộ của bạn.

Từ "hamburger" (Podcast) hãy chuyển đến trang Storage.

1930e055d138150a.pngS

Đặt tên cho bộ chứa

Nhấp vào nút CREATE BUCKET.

34147939358517f8.pngS

Nhấp vào CONTINUE.

Chọn vị trí

197817f20be07678.pngS

Tạo một bộ chứa nhiều khu vực trong khu vực mà bạn chọn (tại đây Europe).

Nhấp vào CONTINUE.

Chọn lớp bộ nhớ mặc định

53cd91441c8caf0e.pngS

Chọn lớp bộ nhớ Standard cho dữ liệu của bạn.

Nhấp vào CONTINUE.

Đặt chế độ kiểm soát quyền truy cập

8c2b3b459d934a51.pngS

Khi làm việc với các hình ảnh có thể truy cập công khai, bạn muốn tất cả ảnh của chúng tôi được lưu trữ trong bộ chứa này có cùng quyền kiểm soát truy cập đồng nhất.

Chọn tuỳ chọn kiểm soát quyền truy cập của Uniform.

Nhấp vào CONTINUE.

Thiết lập tính năng bảo vệ/mã hoá

d931c24c3e705a68.png

Giữ giá trị mặc định (Google-managed key), vì bạn sẽ không sử dụng khoá mã hoá của riêng mình.

Nhấp vào CREATE để hoàn tất việc tạo bộ chứa.

Thêm tất cả người dùng làm người xem bộ nhớ

Chuyển đến thẻ Permissions:

d0ecfdcff730ea51.png

Thêm một thành phần allUsers vào bộ chứa với vai trò là Storage > Storage Object Viewer như sau:

e9f25ec1ea0b6cc6.png

Nhấp vào SAVE.

5. Tạo bộ chứa (myactivity)

Bạn cũng có thể dùng công cụ dòng lệnh gsutil trong Cloud Shell để tạo nhóm.

Trong Cloud Shell, hãy đặt một biến cho tên bộ chứa duy nhất. Cloud Shell đã đặt GOOGLE_CLOUD_PROJECT thành mã dự án duy nhất của bạn. Bạn có thể thêm tên đó vào tên bộ chứa.

Ví dụ:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

Tạo một vùng đa vùng tiêu chuẩn ở Châu Âu:

gsutil mb -l EU gs://${BUCKET_PICTURES}

Đảm bảo quyền truy cập đồng nhất ở cấp bộ chứa:

gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}

Chuyển bộ chứa sang chế độ công khai:

gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}

Nếu chuyển đến phần Cloud Storage của bảng điều khiển, bạn sẽ có một bộ chứa uploaded-pictures công khai:

a98ed4ba17873e40.png

Kiểm tra để đảm bảo rằng bạn có thể tải ảnh lên bộ chứa và ảnh đã tải lên sẽ xuất hiện công khai, như được giải thích trong bước trước.

6. Kiểm thử quyền truy cập công khai vào bộ chứa

Quay lại trình duyệt lưu trữ, bạn sẽ thấy bộ chứa của mình trong danh sách với "Công khai" quyền truy cập (bao gồm cả biển cảnh báo để nhắc bạn rằng bất kỳ ai cũng có quyền truy cập vào nội dung trong bộ chứa đó).

89e7a4d2c80a0319.pngS

Bộ chứa của bạn hiện đã sẵn sàng để nhận ảnh.

Nếu nhấp vào tên bộ chứa, bạn sẽ thấy thông tin chi tiết về bộ chứa đó.

131387f12d3eb2d3.pngS

Ở đó, bạn có thể thử nút Upload files để kiểm tra xem bạn có thể thêm ảnh vào bộ chứa hay không. Cửa sổ bật lên của trình chọn tệp sẽ yêu cầu bạn chọn một tệp. Sau khi chọn tệp này, tệp sẽ được tải lên bộ chứa của bạn và bạn sẽ thấy lại quyền truy cập public đã được tự động gán cho tệp mới này.

e87584471a6e9c6d.png

Dọc theo nhãn truy cập Public, bạn cũng sẽ thấy một biểu tượng nhỏ về đường liên kết. Khi nhấp vào hình ảnh đó, trình duyệt của bạn sẽ chuyển đến URL công khai của hình ảnh đó, có dạng như sau:

https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png

Với BUCKET_NAME là tên duy nhất trên toàn cầu mà bạn đã chọn cho bộ chứa của mình, sau đó là tên tệp cho ảnh của bạn.

Khi nhấp vào hộp kiểm dọc theo tên ảnh, nút DELETE sẽ được bật và bạn có thể xoá hình ảnh đầu tiên này.

7. Chuẩn bị cơ sở dữ liệu

Bạn sẽ lưu trữ thông tin về hình ảnh do Vision API cung cấp vào cơ sở dữ liệu Cloud Firestore, một cơ sở dữ liệu tài liệu NoSQL nhanh, được quản lý hoàn toàn, không máy chủ và dựa trên đám mây. Chuẩn bị cơ sở dữ liệu bằng cách chuyển đến phần Firestore trong Cloud Console:

9e4708d2257de058.pngS

Có hai lựa chọn: Native mode hoặc Datastore mode. Hãy sử dụng chế độ gốc. Chế độ này cung cấp thêm các tính năng như hỗ trợ ngoại tuyến và đồng bộ hoá theo thời gian thực.

Nhấp vào SELECT NATIVE MODE.

9449ace8cc84de43.pngs

Hãy chọn một khu vực (ở đây là Châu Âu, nhưng lý tưởng nhất là cùng một khu vực với chức năng và bộ chứa lưu trữ của bạn).

Nhấp vào nút CREATE DATABASE.

Sau khi tạo cơ sở dữ liệu, bạn sẽ thấy như sau:

56265949a124819e.png.

Tạo một bộ sưu tập mới bằng cách nhấp vào nút + START COLLECTION.

Tập hợp tên pictures.

75806ee24c4e13a7.pngS

Bạn không cần tạo tài liệu. Bạn sẽ thêm ảnh theo chương trình khi ảnh mới được lưu trữ trong Cloud Storage và được Vision API phân tích.

Nhấp vào Save.

Firestore tạo tài liệu mặc định đầu tiên trong tập hợp mới tạo, bạn có thể yên tâm xoá tài liệu đó vì tài liệu đó không chứa thông tin hữu ích nào:

5c2f1e17ea47f48f.png.

Các tài liệu được tạo theo chương trình trong bộ sưu tập của chúng tôi sẽ chứa 4 trường:

  • name (chuỗi): tên tệp của hình ảnh đã tải lên, cũng là khoá của tài liệu
  • nhãn (mảng chuỗi): nhãn của các mục được nhận dạng bằng Vision API
  • color (chuỗi): mã màu thập lục phân của màu chủ đạo (ví dụ: #ab12ef)
  • Đã tạo (ngày): dấu thời gian khi siêu dữ liệu của hình ảnh này được lưu trữ
  • hình thu nhỏ (boolean): một trường tùy chọn sẽ hiển thị và đúng nếu đã được tạo một hình thu nhỏ cho ảnh này

Vì chúng ta sẽ tìm kiếm trong Firestore để tìm những bức ảnh có hình thu nhỏ và sắp xếp theo ngày tạo, chúng ta sẽ cần tạo một chỉ mục tìm kiếm.

Bạn có thể tạo chỉ mục bằng lệnh sau trong Cloud Shell:

gcloud firestore indexes composite create \
  --collection-group=pictures \
  --field-config field-path=thumbnail,order=descending \
  --field-config field-path=created,order=descending

Hoặc bạn cũng có thể thiết lập từ Cloud Console bằng cách nhấp vào Indexes trong cột điều hướng bên trái, rồi tạo một chỉ mục tổng hợp như minh hoạ dưới đây:

ecb8b95e3c791272.png

Nhấp vào Create. Quá trình tạo chỉ mục có thể mất vài phút.

8. Sao chép mã

Sao chép mã nếu bạn chưa truy cập vào lớp học lập trình trước đó:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

Sau đó, bạn có thể chuyển đến thư mục chứa dịch vụ để bắt đầu xây dựng phòng thí nghiệm:

cd serverless-photosharing-workshop/services/image-analysis/java

Bạn sẽ có bố cục tệp sau đây cho dịch vụ:

f79613aff479d8ad.png

9. Tìm hiểu mã dịch vụ

Bạn bắt đầu bằng cách xem cách bật Thư viện ứng dụng Java trong pom.xml bằng BOM:

Trước tiên, hãy chỉnh sửa tệp pom.xml liệt kê các phần phụ thuộc của hàm Java. Cập nhật mã để thêm phần phụ thuộc Maven API của Cloud Vision:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cloudfunctions</groupId>
  <artifactId>gcs-function</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>26.1.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud.functions</groupId>
      <artifactId>functions-framework-api</artifactId>
      <version>1.0.4</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-firestore</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-vision</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-storage</artifactId>
    </dependency>
  </dependencies>

Chức năng này được triển khai trong lớp EventController. Mỗi lần một hình ảnh mới được tải lên bộ chứa, dịch vụ sẽ nhận được thông báo để xử lý:

@RestController
public class EventController {
  private static final Logger logger = Logger.getLogger(EventController.class.getName());
    
  private static final List<String> requiredFields = Arrays.asList("ce-id", "ce-source", "ce-type", "ce-specversion");

  @RequestMapping(value = "/", method = RequestMethod.POST)
  public ResponseEntity<String> receiveMessage(
    @RequestBody Map<String, Object> body, @RequestHeader Map<String, String> headers) throws IOException, InterruptedException, ExecutionException {
...
}

Mã này sẽ tiếp tục xác thực tiêu đề Cloud Events:

System.out.println("Header elements");
for (String field : requiredFields) {
    if (headers.get(field) == null) {
    String msg = String.format("Missing expected header: %s.", field);
    System.out.println(msg);
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
    } else {
    System.out.println(field + " : " + headers.get(field));
    }
}

System.out.println("Body elements");
for (String bodyField : body.keySet()) {
    System.out.println(bodyField + " : " + body.get(bodyField));
}

if (headers.get("ce-subject") == null) {
    String msg = "Missing expected header: ce-subject.";
    System.out.println(msg);
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
} 

Lúc này, bạn có thể tạo một yêu cầu và đoạn mã này sẽ chuẩn bị một yêu cầu như vậy để gửi đến Vision API:

try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
    List<AnnotateImageRequest> requests = new ArrayList<>();
    
    ImageSource imageSource = ImageSource.newBuilder()
        .setGcsImageUri("gs://" + bucketName + "/" + fileName)
        .build();

    Image image = Image.newBuilder()
        .setSource(imageSource)
        .build();

    Feature featureLabel = Feature.newBuilder()
        .setType(Type.LABEL_DETECTION)
        .build();
    Feature featureImageProps = Feature.newBuilder()
        .setType(Type.IMAGE_PROPERTIES)
        .build();
    Feature featureSafeSearch = Feature.newBuilder()
        .setType(Type.SAFE_SEARCH_DETECTION)
        .build();
        
    AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
        .addFeatures(featureLabel)
        .addFeatures(featureImageProps)
        .addFeatures(featureSafeSearch)
        .setImage(image)
        .build();
    
    requests.add(request);

Chúng tôi yêu cầu cung cấp 3 chức năng chính của Vision API:

  • Phát hiện nhãn: để tìm hiểu nội dung của những hình ảnh đó
  • Thuộc tính hình ảnh: để cung cấp các thuộc tính thú vị của hình ảnh (chúng ta quan tâm đến màu chủ đạo của hình ảnh)
  • Tìm kiếm an toàn: biết liệu hình ảnh đó có an toàn để hiển thị hay không (hình ảnh không được chứa nội dung người lớn / nội dung y tế / nội dung không phù hợp cho người xem chưa đến tuổi trưởng thành / nội dung bạo lực)

Lúc này, chúng ta có thể thực hiện lệnh gọi đến Vision API:

...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...

Để tham khảo, dưới đây là phản hồi từ Vision API:

{
  "faceAnnotations": [],
  "landmarkAnnotations": [],
  "logoAnnotations": [],
  "labelAnnotations": [
    {
      "locations": [],
      "properties": [],
      "mid": "/m/01yrx",
      "locale": "",
      "description": "Cat",
      "score": 0.9959855675697327,
      "confidence": 0,
      "topicality": 0.9959855675697327,
      "boundingPoly": null
    },
    ✄ - - - ✄
  ],
  "textAnnotations": [],
  "localizedObjectAnnotations": [],
  "safeSearchAnnotation": {
    "adult": "VERY_UNLIKELY",
    "spoof": "UNLIKELY",
    "medical": "VERY_UNLIKELY",
    "violence": "VERY_UNLIKELY",
    "racy": "VERY_UNLIKELY",
    "adultConfidence": 0,
    "spoofConfidence": 0,
    "medicalConfidence": 0,
    "violenceConfidence": 0,
    "racyConfidence": 0,
    "nsfwConfidence": 0
  },
  "imagePropertiesAnnotation": {
    "dominantColors": {
      "colors": [
        {
          "color": {
            "red": 203,
            "green": 201,
            "blue": 201,
            "alpha": null
          },
          "score": 0.4175916016101837,
          "pixelFraction": 0.44456374645233154
        },
        ✄ - - - ✄
      ]
    }
  },
  "error": null,
  "cropHintsAnnotation": {
    "cropHints": [
      {
        "boundingPoly": {
          "vertices": [
            { "x": 0, "y": 118 },
            { "x": 1177, "y": 118 },
            { "x": 1177, "y": 783 },
            { "x": 0, "y": 783 }
          ],
          "normalizedVertices": []
        },
        "confidence": 0.41695669293403625,
        "importanceFraction": 1
      }
    ]
  },
  "fullTextAnnotation": null,
  "webDetection": null,
  "productSearchResults": null,
  "context": null
}

Nếu không có lỗi được trả về, chúng ta có thể tiếp tục. Đó là lý do chúng ta có khối if:

if (responses.size() == 0) {
    logger.info("No response received from Vision API.");
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}

AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
    logger.info("Error: " + response.getError().getMessage());
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}

Chúng ta sẽ lấy nhãn của những sự vật, danh mục hoặc chủ đề được nhận dạng trong ảnh:

List<String> labels = response.getLabelAnnotationsList().stream()
    .map(annotation -> annotation.getDescription())
    .collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
    logger.info("- " + label);
}

Chúng tôi muốn biết màu chủ đạo của hình ảnh:

String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
    DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
    ColorInfo colorInfo = colorsAnn.getColors(0);

    mainColor = rgbHex(
        colorInfo.getColor().getRed(), 
        colorInfo.getColor().getGreen(), 
        colorInfo.getColor().getBlue());

    logger.info("Color: " + mainColor);
}

Hãy kiểm tra xem hình ảnh có an toàn để hiển thị không:

boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
    SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();

    isSafe = Stream.of(
        safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
        safeSearch.getSpoof(), safeSearch.getViolence())
    .allMatch( likelihood -> 
        likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
    );

    logger.info("Safe? " + isSafe);
}

Chúng tôi đang kiểm tra các đặc điểm liên quan đến người lớn / giả mạo / nội dung y tế / nội dung bạo lực / không phù hợp cho người xem chưa đến tuổi trưởng thành để xem liệu các đặc điểm đó có khả năng hay rất có thể không.

Nếu kết quả tìm kiếm an toàn là phù hợp, chúng ta có thể lưu trữ siêu dữ liệu trong Firestore:

// Saving result to Firestore
if (isSafe) {
    FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
    Firestore pictureStore = firestoreOptions.getService();

    DocumentReference doc = pictureStore.collection("pictures").document(fileName);

    Map<String, Object> data = new HashMap<>();
    data.put("labels", labels);
    data.put("color", mainColor);
    data.put("created", new Date());

    ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());

    logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}

10. Tạo hình ảnh ứng dụng bằng GraalVM (không bắt buộc)

Trong bước không bắt buộc này, bạn sẽ tạo một JIT(JVM) based app image, sau đó là AOT(Native) Java app image bằng cách sử dụng GraalVM.

Để chạy bản dựng, bạn cần đảm bảo đã cài đặt và định cấu hình trình tạo hình ảnh gốc cũng như JDK thích hợp. Có một số lựa chọn.

To start, hãy tải GraalVM 22.2.x Community Edition xuống và làm theo hướng dẫn trên trang cài đặt GraalVM.

Bạn có thể đơn giản hoá đáng kể quy trình này với sự trợ giúp của SDKMAN!

Để cài đặt cách phân phối JDK thích hợp bằng SDKman, hãy bắt đầu bằng cách sử dụng lệnh cài đặt:

sdk install java 22.2.r17-grl

Hướng dẫn SDKman sử dụng phiên bản này, cho cả bản dựng JIT và AOT:

sdk use java 22.2.0.r17-grl

Cài đặt native-image utility cho GraalVM:

gu install native-image

Trong Cloudshell, để thuận tiện, bạn có thể cài đặt GraalVM và tiện ích native-image bằng những lệnh đơn giản sau:

# install GraalVM in your home directory
cd ~

# download GraalVM
wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.2.0/graalvm-ce-java17-linux-amd64-22.2.0.tar.gz
ls
tar -xzvf graalvm-ce-java17-linux-amd64-22.2.0.tar.gz

# configure Java 17 and GraalVM 22.2
echo Existing JVM: $JAVA_HOME
cd graalvm-ce-java17-22.2.0
export JAVA_HOME=$PWD
cd bin
export PATH=$PWD:$PATH

echo JAVA HOME: $JAVA_HOME
echo PATH: $PATH

# install the native image utility
java -version
gu install native-image

cd ../..

Trước tiên, hãy đặt các biến môi trường của dự án GCP:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

Sau đó, bạn có thể chuyển đến thư mục chứa dịch vụ để bắt đầu xây dựng phòng thí nghiệm:

cd serverless-photosharing-workshop/services/image-analysis/java

Tạo hình ảnh ứng dụng JIT(JVM):

./mvnw package -Pjvm

Quan sát nhật ký bản dựng trong cửa sổ dòng lệnh:

...
[INFO] --- spring-boot-maven-plugin:2.7.3:repackage (repackage) @ image-analysis ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  24.009 s
[INFO] Finished at: 2022-09-26T22:17:32-04:00
[INFO] ------------------------------------------------------------------------

Tạo hình ảnh AOT(Gốc):

./mvnw package -Pnative -DskipTests

Quan sát nhật ký bản dựng trong thiết bị đầu cuối, bao gồm cả nhật ký bản dựng hình ảnh gốc:

Lưu ý rằng quá trình tạo bản dựng sẽ mất nhiều thời gian hơn, tuỳ thuộc vào máy bạn đang kiểm thử.

...
[2/7] Performing analysis...  [**********]                                                              (95.4s @ 3.57GB)
  23,346 (94.42%) of 24,725 classes reachable
  44,625 (68.71%) of 64,945 fields reachable
 163,759 (70.79%) of 231,322 methods reachable
     989 classes, 1,402 fields, and 11,032 methods registered for reflection
      63 classes,    69 fields, and    55 methods registered for JNI access
       5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z
[3/7] Building universe...                                                                              (10.0s @ 5.35GB)
[4/7] Parsing methods...      [***]                                                                      (9.7s @ 3.13GB)
[5/7] Inlining methods...     [***]                                                                      (4.5s @ 3.29GB)
[6/7] Compiling methods...    [[6/7] Compiling methods...    [********]                                                                (67.6s @ 5.72GB)
[7/7] Creating image...                                                                                  (8.7s @ 4.59GB)
  62.21MB (54.80%) for code area:   100,371 compilation units
  50.98MB (44.91%) for image heap:  465,035 objects and 365 resources
 337.09KB ( 0.29%) for other data
 113.52MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
   2.36MB com.google.protobuf                                 12.70MB byte[] for code metadata
   1.90MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3    6.66MB java.lang.Class
   1.73MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3     6.47MB byte[] for embedded resources
   1.67MB sun.security.ssl                                     4.61MB byte[] for java.lang.String
   1.54MB com.google.cloud.vision.v1                           4.37MB java.lang.String
   1.46MB com.google.firestore.v1                              3.38MB byte[] for general heap data
   1.37MB io.grpc.xds.shaded.io.envoyproxy.envoy.api.v2.core   1.96MB com.oracle.svm.core.hub.DynamicHubCompanion
   1.32MB i.g.xds.shaded.io.envoyproxy.envoy.api.v2.route      1.80MB byte[] for reflection metadata
   1.09MB java.util                                          911.80KB java.lang.String[]
   1.08MB com.google.re2j                                    826.48KB c.o.svm.core.hub.DynamicHub$ReflectionMetadata
  45.91MB for 772 more packages                                6.45MB for 3913 more object types
------------------------------------------------------------------------------------------------------------------------
                        15.1s (6.8% of total time) in 56 GCs | Peak RSS: 7.72GB | CPU load: 4.37
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
 /Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'image-analysis' in 3m 41s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:56 min
[INFO] Finished at: 2022-09-26T22:22:29-04:00
[INFO] ------------------------------------------------------------------------

11. Tạo và xuất bản hình ảnh vùng chứa

Hãy tạo hình ảnh vùng chứa trong hai phiên bản khác nhau: một phiên bản là JIT(JVM) image và một là AOT(Native) Java image.

Trước tiên, hãy đặt các biến môi trường của dự án GCP:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

Tạo hình ảnh JIT(JVM):.

./mvnw package -Pjvm-image

Quan sát nhật ký bản dựng trong cửa sổ dòng lệnh:

[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     Saving docker.io/library/image-analysis-jvm:r17...
[INFO]     [creator]     *** Images (03a44112456e):
[INFO]     [creator]           docker.io/library/image-analysis-jvm:r17
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-jvm:r17'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:11 min
[INFO] Finished at: 2022-09-26T13:09:34-04:00
[INFO] ------------------------------------------------------------------------

Tạo hình ảnh AOT(Gốc):

./mvnw package -Pnative-image

Quan sát nhật ký bản dựng trong thiết bị đầu cuối, bao gồm cả nhật ký bản dựng hình ảnh gốc và hoạt động nén hình ảnh bằng UPX.

Lưu ý rằng quá trình tạo bản dựng sẽ mất nhiều thời gian hơn, tuỳ thuộc vào máy bạn đang kiểm thử

...
[INFO]     [creator]     [2/7] Performing analysis...  [***********]                    (147.6s @ 3.10GB)
[INFO]     [creator]       23,362 (94.34%) of 24,763 classes reachable
[INFO]     [creator]       44,657 (68.67%) of 65,029 fields reachable
[INFO]     [creator]      163,926 (70.76%) of 231,656 methods reachable
[INFO]     [creator]          981 classes, 1,402 fields, and 11,026 methods registered for reflection
[INFO]     [creator]           63 classes,    68 fields, and    55 methods registered for JNI access
[INFO]     [creator]            4 native libraries: dl, pthread, rt, z
[INFO]     [creator]     [3/7] Building universe...                                      (21.1s @ 2.66GB)
[INFO]     [creator]     [4/7] Parsing methods...      [****]                            (13.7s @ 4.16GB)
[INFO]     [creator]     [5/7] Inlining methods...     [***]                              (9.6s @ 4.20GB)
[INFO]     [creator]     [6/7] Compiling methods...    [**********]                     (107.6s @ 3.36GB)
[INFO]     [creator]     [7/7] Creating image...                                         (14.7s @ 4.87GB)
[INFO]     [creator]       62.24MB (51.35%) for code area:   100,499 compilation units
[INFO]     [creator]       51.99MB (42.89%) for image heap:  473,948 objects and 473 resources
[INFO]     [creator]        6.98MB ( 5.76%) for other data
[INFO]     [creator]      121.21MB in total
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]     Top 10 packages in code area:           Top 10 object types in image heap:
[INFO]     [creator]        2.36MB com.google.protobuf             12.71MB byte[] for code metadata
[INFO]     [creator]        1.90MB i.g.x.s.i.e.e.config.core.v3     7.59MB byte[] for embedded resources
[INFO]     [creator]        1.73MB i.g.x.s.i.e.e.config.route.v3    6.66MB java.lang.Class
[INFO]     [creator]        1.67MB sun.security.ssl                 4.62MB byte[] for java.lang.String
[INFO]     [creator]        1.54MB com.google.cloud.vision.v1       4.39MB java.lang.String
[INFO]     [creator]        1.46MB com.google.firestore.v1          3.66MB byte[] for general heap data
[INFO]     [creator]        1.37MB i.g.x.s.i.e.envoy.api.v2.core    1.96MB c.o.s.c.h.DynamicHubCompanion
[INFO]     [creator]        1.32MB i.g.x.s.i.e.e.api.v2.route       1.80MB byte[] for reflection metadata
[INFO]     [creator]        1.09MB java.util                      910.41KB java.lang.String[]
[INFO]     [creator]        1.08MB com.google.re2j                826.95KB c.o.s.c.h.DynamicHu~onMetadata
[INFO]     [creator]       45.94MB for 776 more packages            6.69MB for 3916 more object types
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]         20.4s (5.6% of total time) in 81 GCs | Peak RSS: 6.75GB | CPU load: 4.53
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]     Produced artifacts:
[INFO]     [creator]      /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication (executable)
[INFO]     [creator]      /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication.build_artifacts.txt (txt)
[INFO]     [creator]     ================================================================================
[INFO]     [creator]     Finished generating '/layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication' in 5m 59s.
[INFO]     [creator]         Executing upx to compress native image
[INFO]     [creator]                            Ultimate Packer for eXecutables
[INFO]     [creator]                               Copyright (C) 1996 - 2020
[INFO]     [creator]     UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020
[INFO]     [creator]     
[INFO]     [creator]             File size         Ratio      Format      Name
[INFO]     [creator]        --------------------   ------   -----------   -----------
 127099880 ->  32416676   25.50%   linux/amd64   services.ImageAnalysisApplication
...
[INFO]     [creator]     ===> EXPORTING
...
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]     [creator]     Adding cache layer 'cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-native:r17'
------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  05:28 min
[INFO] Finished at: 2022-09-26T13:19:53-04:00
[INFO] ------------------------------------------------------------------------

Kiểm tra để đảm bảo hình ảnh đã được tạo:

docker images | grep image-analysis

Gắn thẻ và đẩy hai hình ảnh vào GCR:

# JIT(JVM) image
docker tag image-analysis-jvm:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17

# AOT(Native) image
docker tag image-analysis-native:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17
docker push  gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17

12. Triển khai lên Cloud Run

Thời gian triển khai dịch vụ.

Bạn sẽ triển khai dịch vụ hai lần, một lần sử dụng hình ảnh JIT(JVM) và lần thứ hai sử dụng hình ảnh AOT(Native). Cả hai lần triển khai dịch vụ sẽ xử lý song song cùng một hình ảnh từ bộ chứa để so sánh.

Trước tiên, hãy đặt các biến môi trường của dự án GCP:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region 
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1

Triển khai hình ảnh JIT(JVM) và quan sát nhật ký triển khai trong bảng điều khiển:

gcloud run deploy image-analysis-jvm \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17 \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated

...
Deploying container to Cloud Run service [image-analysis-jvm] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-jvm] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jvm-...-ew.a.run.app

Triển khai hình ảnh AOT(Gốc) và quan sát nhật ký triển khai trong bảng điều khiển:

gcloud run deploy image-analysis-native \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17 \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated 
...
Deploying container to Cloud Run service [image-analysis-native] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-native] revision [image-analysis-native-00005-ben] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-native-...-ew.a.run.app

13. Thiết lập trình kích hoạt Eventarc

Eventarc cung cấp một giải pháp chuẩn hoá để quản lý luồng thay đổi trạng thái, được gọi là sự kiện, giữa các dịch vụ vi mô được tách riêng. Khi được kích hoạt, Eventarc sẽ chuyển những sự kiện này thông qua gói thuê bao Pub/Sub đến nhiều điểm đến khác nhau (trong tài liệu này hãy xem Đích đến của sự kiện) đồng thời quản lý hoạt động phân phối, bảo mật, uỷ quyền, khả năng quan sát và xử lý lỗi cho bạn.

Bạn có thể tạo trình kích hoạt Eventarc để dịch vụ Cloud Run của bạn nhận thông báo về một sự kiện hoặc một tập hợp các sự kiện được chỉ định. Bằng cách chỉ định bộ lọc cho trình kích hoạt, bạn có thể định cấu hình định tuyến của sự kiện, bao gồm nguồn sự kiện và dịch vụ Cloud Run mục tiêu.

Trước tiên, hãy đặt các biến môi trường của dự án GCP:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region 
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1

Cấp pubsub.publisher cho tài khoản dịch vụ Cloud Storage:

SERVICE_ACCOUNT="$(gsutil kms serviceaccount -p ${GOOGLE_CLOUD_PROJECT})"

gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
    --member="serviceAccount:${SERVICE_ACCOUNT}" \
    --role='roles/pubsub.publisher'

Thiết lập trình kích hoạt Eventarc cho cả hình ảnh dịch vụ JVM(JIT) và AOT(Gốc) để xử lý hình ảnh:

gcloud eventarc triggers list --location=eu

gcloud eventarc triggers create image-analysis-jvm-trigger \
     --destination-run-service=image-analysis-jvm \
     --destination-run-region=europe-west1 \
     --location=eu \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
     --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com

gcloud eventarc triggers create image-analysis-native-trigger \
     --destination-run-service=image-analysis-native \
     --destination-run-region=europe-west1 \
     --location=eu \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
     --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com    

Lưu ý rằng 2 điều kiện kích hoạt đã được tạo:

gcloud eventarc triggers list --location=eu

14. Kiểm thử các phiên bản dịch vụ

Sau khi triển khai dịch vụ thành công, bạn sẽ đăng hình ảnh lên Cloud Storage, xem các dịch vụ của chúng tôi có được gọi hay không, Vision API trả về những nội dung gì và siêu dữ liệu có được lưu trữ trong Firestore hay không.

Quay lại Cloud Storage và nhấp vào bộ chứa chúng ta đã tạo ở đầu phòng thí nghiệm:

ff8a6567afc76235.png

Khi ở trong trang chi tiết về bộ chứa, hãy nhấp vào nút Upload files để tải ảnh lên.

Ví dụ: hình ảnh GeekHour.jpeg được cung cấp cùng với cơ sở mã trong /services/image-analysis/java. Chọn một hình ảnh rồi nhấn phím Open button:

347b76e8b775f2f5.pngS

Bây giờ, bạn có thể kiểm tra quá trình thực thi dịch vụ, bắt đầu bằng image-analysis-jvm, sau đó là image-analysis-native.

Từ "hamburger" (Podcast), hãy chuyển đến dịch vụ Cloud Run > image-analysis-jvm.

Nhấp vào Logs (Nhật ký) rồi quan sát kết quả:

810a8684414ceafa.png.

Và thực sự, trong danh sách nhật ký, tôi có thể thấy rằng dịch vụ JIT(JVM) image-analysis-jvm đã được gọi.

Nhật ký cho biết thời điểm bắt đầu và kết thúc thực thi dịch vụ. Và ở giữa, chúng ta có thể thấy các nhật ký mà chúng ta đã đặt vào hàm cùng với câu lệnh nhật ký ở cấp INFO. Chúng ta thấy:

  • Thông tin chi tiết về sự kiện kích hoạt chức năng của chúng tôi,
  • Kết quả thô từ lệnh gọi Vision API,
  • Nhãn được tìm thấy trong hình ảnh do chúng tôi tải lên,
  • Thông tin về màu chủ đạo,
  • Liệu hình ảnh có an toàn để hiển thị hay không,
  • Cuối cùng, những siêu dữ liệu về bức ảnh này đã được lưu trữ trong Firestore.

Bạn sẽ lặp lại quy trình cho dịch vụ image-analysis-native.

Từ "hamburger" (Podcast), hãy chuyển đến dịch vụ Cloud Run > image-analysis-native.

Nhấp vào Logs (Nhật ký) rồi quan sát kết quả:

b80308c7d0f55a3.png

Lúc này, bạn nên tìm hiểu xem liệu siêu dữ liệu của hình ảnh đã được lưu trữ trong Fiorestore hay chưa.

Lại từ "hamburger" nữa (Podcast), hãy chuyển đến phần Firestore. Trong tiểu mục Data (hiển thị theo mặc định), bạn sẽ thấy bộ sưu tập pictures có tài liệu mới được thêm vào, tương ứng với ảnh bạn vừa tải lên:

933a20a9709cb006.pngS

15. Dọn dẹp (Không bắt buộc)

Nếu không có ý định tiếp tục sử dụng các phòng thí nghiệm khác trong chuỗi chương trình này, bạn có thể dọn dẹp các tài nguyên để tiết kiệm chi phí và trở thành một công dân tốt về công nghệ đám mây. Bạn có thể dọn dẹp từng tài nguyên như sau.

Xoá bộ chứa:

gsutil rb gs://${BUCKET_PICTURES}

Xoá hàm:

gcloud functions delete picture-uploaded --region europe-west1 -q

Xoá bộ sưu tập trên Firestore bằng cách chọn Xoá bộ sưu tập khỏi bộ sưu tập:

410b551c3264f70a.pngS

Ngoài ra, bạn có thể xoá toàn bộ dự án theo cách sau:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. Xin chúc mừng!

Xin chúc mừng! Bạn đã triển khai thành công dịch vụ mã khoá đầu tiên của dự án!

Nội dung đã đề cập

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore
  • Hình ảnh Java gốc

Các bước tiếp theo