Pic-a-day: Lab 1 - ذخیره و تجزیه و تحلیل تصاویر (جاوای بومی)

1. بررسی اجمالی

در اولین آزمایشگاه کد، تصاویر را در یک سطل ذخیره خواهید کرد. این یک رویداد ایجاد فایل ایجاد می کند که توسط یک سرویس مستقر در Cloud Run مدیریت می شود. این سرویس با Vision API تماس می گیرد تا تجزیه و تحلیل تصویر را انجام دهد و نتایج را در یک دیتا استور ذخیره کند.

c0650ee4a76db35e.png

چیزی که یاد خواهید گرفت

  • فضای ذخیره سازی ابری
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore

2. راه اندازی و الزامات

تنظیم محیط خود به خود

  1. به Google Cloud Console وارد شوید و یک پروژه جدید ایجاد کنید یا از یک موجود استفاده مجدد کنید. اگر قبلاً یک حساب Gmail یا Google Workspace ندارید، باید یک حساب ایجاد کنید .

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • نام پروژه نام نمایشی برای شرکت کنندگان این پروژه است. این یک رشته کاراکتری است که توسط API های Google استفاده نمی شود. همیشه می توانید آن را به روز کنید.
  • شناسه پروژه در تمام پروژه‌های Google Cloud منحصربه‌فرد است و تغییرناپذیر است (پس از تنظیم نمی‌توان آن را تغییر داد). Cloud Console به طور خودکار یک رشته منحصر به فرد تولید می کند. معمولاً برای شما مهم نیست که چیست. در اکثر کدها، باید شناسه پروژه خود را ارجاع دهید (معمولاً با نام PROJECT_ID شناخته می شود). اگر شناسه تولید شده را دوست ندارید، ممکن است یک شناسه تصادفی دیگر ایجاد کنید. از طرف دیگر، می‌توانید خودتان را امتحان کنید، و ببینید آیا در دسترس است یا خیر. پس از این مرحله نمی توان آن را تغییر داد و در طول مدت پروژه باقی می ماند.
  • برای اطلاع شما، یک مقدار سوم وجود دارد، یک شماره پروژه ، که برخی از API ها از آن استفاده می کنند. در مورد هر سه این مقادیر در مستندات بیشتر بیاموزید.
  1. در مرحله بعد، برای استفاده از منابع Cloud/APIها باید صورتحساب را در کنسول Cloud فعال کنید . اجرا کردن از طریق این کد لبه هزینه زیادی نخواهد داشت. برای خاموش کردن منابع برای جلوگیری از تحمیل صورت‌حساب فراتر از این آموزش، می‌توانید منابعی را که ایجاد کرده‌اید حذف کنید یا پروژه را حذف کنید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان 300 دلاری هستند.

Cloud Shell را راه اندازی کنید

در حالی که Google Cloud را می توان از راه دور از لپ تاپ شما کار کرد، در این کد لبه از Google Cloud Shell استفاده خواهید کرد، یک محیط خط فرمان که در Cloud اجرا می شود.

از Google Cloud Console ، روی نماد Cloud Shell در نوار ابزار بالا سمت راست کلیک کنید:

84688aa223b1c3a2.png

تهیه و اتصال به محیط فقط چند لحظه طول می کشد. وقتی تمام شد، باید چیزی شبیه به این را ببینید:

320e18fedb7fbe0.png

این ماشین مجازی با تمام ابزارهای توسعه که شما نیاز دارید بارگذاری شده است. این یک فهرست اصلی 5 گیگابایتی دائمی را ارائه می دهد و در Google Cloud اجرا می شود و عملکرد و احراز هویت شبکه را تا حد زیادی افزایش می دهد. تمام کارهای شما در این کد لبه را می توان در یک مرورگر انجام داد. شما نیازی به نصب چیزی ندارید.

3. API ها را فعال کنید

برای این آزمایشگاه، از Cloud Functions و Vision API استفاده خواهید کرد، اما ابتدا باید آنها را یا در Cloud Console یا با gcloud فعال کنید.

برای فعال کردن Vision API در Cloud Console، Cloud Vision API را در نوار جستجو جستجو کنید:

8f3522d790bb026c.png

شما در صفحه Cloud Vision API قرار خواهید گرفت:

d785572fa14c87c2.png

روی دکمه ENABLE کلیک کنید.

همچنین می‌توانید با استفاده از ابزار خط فرمان gcloud آن را فعال کنید.

در داخل Cloud Shell، دستور زیر را اجرا کنید:

gcloud services enable vision.googleapis.com

برای اتمام موفقیت آمیز باید عملیات را مشاهده کنید:

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

Cloud Run و Cloud Build را نیز فعال کنید:

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

4. ایجاد سطل (کنسول)

یک سطل ذخیره سازی برای تصاویر ایجاد کنید. می توانید این کار را از کنسول Google Cloud Platform ( consol.cloud.google.com ) یا با ابزار خط فرمان gsutil از Cloud Shell یا محیط توسعه محلی خود انجام دهید.

از منوی "همبرگر" (☰)، به صفحه Storage بروید.

d08ecb0ae29330a1.png

سطل خود را نام ببرید

روی دکمه CREATE BUCKET کلیک کنید.

8951851554a430d2.png

CONTINUE کلیک کنید.

مکان را انتخاب کنید

24b24625157ab467.png

یک سطل چند منطقه ای در منطقه مورد نظر خود ایجاد کنید (اینجا Europe ).

CONTINUE کلیک کنید.

کلاس ذخیره سازی پیش فرض را انتخاب کنید

9e7bd365fa94a2e0.png

کلاس ذخیره سازی Standard را برای داده های خود انتخاب کنید.

CONTINUE کلیک کنید.

کنترل دسترسی را تنظیم کنید

1ff4a1f6e57045f5.png

از آنجایی که با تصاویر در دسترس عموم کار می کنید، می خواهید همه تصاویر ذخیره شده ما در این سطل کنترل دسترسی یکسانی داشته باشند.

گزینه Uniform Access Control را انتخاب کنید.

CONTINUE کلیک کنید.

حفاظت/رمزگذاری را تنظیم کنید

2d469b076029d365.png

پیش فرض ( Google-managed key) را حفظ کنید، زیرا از کلیدهای رمزگذاری خود استفاده نخواهید کرد.

روی CREATE کلیک کنید تا در نهایت ایجاد سطل ما نهایی شود.

allUsers را به عنوان نمایشگر ذخیره سازی اضافه کنید

به برگه Permissions بروید:

19564b3ad8688ae8.png

یک عضو allUsers را با نقش Storage > Storage Object Viewer به سطل اضافه کنید:

d655e760c76d62c1.png

روی SAVE کلیک کنید.

5. سطل (gsutil) را ایجاد کنید

همچنین می توانید از ابزار خط فرمان gsutil در Cloud Shell برای ایجاد سطل استفاده کنید.

در Cloud Shell، یک متغیر برای نام سطل یکتا تنظیم کنید. Cloud Shell قبلاً GOOGLE_CLOUD_PROJECT را روی شناسه پروژه منحصر به فرد شما تنظیم کرده است. می توانید آن را به نام سطل اضافه کنید.

به عنوان مثال:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

ایجاد یک منطقه استاندارد چند منطقه ای در اروپا:

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

از دسترسی یکنواخت به سطح سطل اطمینان حاصل کنید:

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

سطل را عمومی کنید:

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

اگر به بخش Cloud Storage کنسول بروید، باید یک سطل uploaded-pictures عمومی داشته باشید:

65c63ef4a6eb30ad.png

تست کنید که می توانید تصاویر را در سطل آپلود کنید و تصاویر آپلود شده برای عموم در دسترس هستند، همانطور که در مرحله قبل توضیح داده شد.

6. دسترسی عمومی به سطل را آزمایش کنید

با بازگشت به مرورگر ذخیره سازی، سطل خود را با دسترسی «عمومی» (شامل یک علامت هشدار که به شما یادآوری می کند که هر کسی به محتوای آن سطل دسترسی دارد) را در لیست می بینید.

e639a9ba625b71a6.png

اکنون سطل شما برای دریافت تصاویر آماده است.

اگر روی نام سطل کلیک کنید، جزئیات سطل را مشاهده خواهید کرد.

1f88a2290290aba8.png

در آنجا، می‌توانید دکمه Upload files را امتحان کنید تا آزمایش کنید که می‌توانید تصویری را به سطل اضافه کنید. یک پنجره انتخابگر فایل از شما می خواهد که یک فایل را انتخاب کنید. پس از انتخاب، در سطل شما آپلود می شود و دوباره دسترسی public را که به طور خودکار به این فایل جدید نسبت داده شده است، خواهید دید.

1209e7ebe1f63b10.png

در کنار برچسب دسترسی Public ، نماد پیوند کوچکی را نیز مشاهده خواهید کرد. وقتی روی آن کلیک می کنید، مرورگر شما به آدرس عمومی آن تصویر می رود که به شکل زیر خواهد بود:

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

با BUCKET_NAME نام منحصربه‌فرد جهانی است که برای سطل خود انتخاب کرده‌اید، و سپس نام فایل تصویرتان.

با کلیک بر روی کادر کنار نام تصویر، دکمه DELETE فعال می شود و می توانید این تصویر اول را حذف کنید.

7. پایگاه داده را آماده کنید

شما اطلاعات مربوط به تصویر ارائه شده توسط Vision API را در پایگاه داده Cloud Firestore ، یک پایگاه داده اسناد NoSQL سریع، کاملا مدیریت شده، بدون سرور و بومی ابری ذخیره خواهید کرد. با رفتن به بخش Firestore در Cloud Console پایگاه داده خود را آماده کنید:

e57a673537b5deca.png

دو گزینه ارائه می شود: Native mode یا Datastore mode . از حالت بومی استفاده کنید، که ویژگی‌های اضافی مانند پشتیبانی آفلاین و همگام‌سازی بلادرنگ را ارائه می‌دهد.

روی SELECT NATIVE MODE کلیک کنید.

1a2e363fae5c7e96.png

یک منطقه چندگانه (اینجا در اروپا، اما در حالت ایده آل حداقل همان منطقه ای که عملکرد و سطل ذخیره سازی شما هستند) انتخاب کنید.

روی دکمه CREATE DATABASE کلیک کنید.

پس از ایجاد پایگاه داده، باید موارد زیر را مشاهده کنید:

7dcc82751ed483fb.png

با کلیک بر روی دکمه + START COLLECTION یک مجموعه جدید ایجاد کنید.

pictures مجموعه نام .

dce3d73884ac8c83.png

شما نیازی به ایجاد سند ندارید. وقتی تصاویر جدید در Cloud Storage ذخیره می شوند و توسط Vision API تجزیه و تحلیل می شوند، آنها را به صورت برنامه نویسی اضافه خواهید کرد.

روی Save کلیک کنید.

Firestore اولین سند پیش فرض را در مجموعه جدید ایجاد می کند، می توانید با خیال راحت آن سند را حذف کنید زیرا حاوی اطلاعات مفیدی نیست:

63e95c844b3f79d3.png

اسنادی که به صورت برنامه نویسی در مجموعه ما ایجاد می شوند دارای 4 فیلد خواهند بود:

  • نام (رشته): نام فایل تصویر آپلود شده که کلید سند نیز می باشد
  • برچسب ها (آرایه رشته ها): برچسب های موارد شناسایی شده توسط Vision API
  • رنگ (رشته): کد رنگ هگزادسیمال رنگ غالب (یعنی #ab12ef)
  • ایجاد (تاریخ): مُهر زمانی ذخیره شدن فراداده این تصویر
  • تصویر بندانگشتی (بولی): یک فیلد اختیاری که در صورت ایجاد یک تصویر کوچک برای این تصویر وجود دارد و درست است.

از آنجایی که در Firestore برای یافتن تصاویری که دارای ریز عکس‌ها هستند جستجو می‌کنیم و بر اساس تاریخ ایجاد مرتب می‌کنیم، باید یک فهرست جستجو ایجاد کنیم.

می توانید با دستور زیر در Cloud Shell ایندکس را ایجاد کنید:

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

یا می‌توانید این کار را از کنسول Cloud انجام دهید، با کلیک بر روی Indexes ، در ستون پیمایش در سمت چپ، و سپس ایجاد یک نمایه ترکیبی مانند شکل زیر:

2236d3a024a59232.png

روی Create کلیک کنید. ایجاد فهرست ممکن است چند دقیقه طول بکشد.

8. کد را شبیه سازی کنید

اگر قبلاً در آزمایشگاه کد قبلی نبوده اید، کد را شبیه سازی کنید:

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

سپس می توانید برای شروع ساخت آزمایشگاه به دایرکتوری حاوی سرویس بروید:

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

شما طرح بندی فایل زیر را برای سرویس خواهید داشت:

4c2a18a2c8b69dc5.png

9. کد سرویس را کاوش کنید

شما با نگاهی به نحوه فعال شدن کتابخانه های کلاینت جاوا در pom.xml با استفاده از یک BOM شروع می کنید:

ابتدا فایل pom.xml را باز کنید که وابستگی های برنامه جاوا ما را فهرست می کند. تمرکز بر روی استفاده از Vision، Cloud Storage و Firestore API است

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0-M3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>services</groupId>
        <artifactId>image-analysis</artifactId>
        <version>0.0.1</version>
        <name>image-analysis</name>
        <description>Spring App for Image Analysis</description>
    <properties>
        <java.version>17</java.version>
        <maven.compiler.target>17</maven.compiler.target>
        <maven.compiler.source>17</maven.compiler.source>        
        <spring-cloud.version>2023.0.0-M2</spring-cloud.version>
        <testcontainers.version>1.19.1</testcontainers.version>
    </properties>
...
  <dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>libraries-bom</artifactId>
            <version>26.24.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
  </dependencyManagement>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
                <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-function-web</artifactId>
                </dependency>
        <dependency>
            <groupId>com.google.cloud.functions</groupId>
            <artifactId>functions-framework-api</artifactId>
            <version>1.1.0</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>        

این عملکرد در کلاس EventController پیاده سازی شده است. هر بار که یک تصویر جدید در سطل آپلود می شود، سرویس اعلانی برای پردازش دریافت می کند:

@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 {
...
}

کد به اعتبار سنجی سرصفحه های 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);
} 

اکنون می توان یک درخواست ایجاد کرد و کد یکی از این درخواست ها را برای ارسال به 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);

ما 3 قابلیت کلیدی Vision API را درخواست می کنیم:

  • تشخیص برچسب : برای درک آنچه در آن تصاویر وجود دارد
  • ویژگی های تصویر : برای دادن ویژگی های جالب به تصویر (ما به رنگ غالب تصویر علاقه مند هستیم)
  • جستجوی ایمن : برای اطلاع از اینکه آیا تصویر برای نمایش بی خطر است (این تصویر نباید حاوی محتوای بزرگسالان / پزشکی / نژادی / خشونت آمیز باشد)

در این مرحله، می‌توانیم با Vision API تماس بگیریم:

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

برای مرجع، در اینجا پاسخ از 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
}

اگر خطایی برگردانده نشد، می‌توانیم ادامه دهیم، به همین دلیل این بلوک را داریم:

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);
}

ما می‌خواهیم برچسب‌های چیزها، دسته‌ها یا مضامین شناسایی شده در تصویر را دریافت کنیم:

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

ما علاقه مندیم که رنگ غالب تصویر را بدانیم:

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);
}

بیایید بررسی کنیم که آیا تصویر برای نمایش امن است یا خیر:

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);
}

ما در حال بررسی ویژگی های بزرگسالان / جعل / پزشکی / خشونت / نژادپرستانه هستیم تا ببینیم که آیا محتمل یا بسیار محتمل هستند .

اگر نتیجه جستجوی ایمن درست باشد، می‌توانیم متادیتا را در Firestore ذخیره کنیم:

// Saving result to Firestore
if (isSafe) {
          ApiFuture<WriteResult> writeResult = 
               eventService.storeImage(fileName, labels,
                                       mainColor);
          logger.info("Picture metadata saved in Firestore at " + 
               writeResult.get().getUpdateTime());
}
...
  public ApiFuture<WriteResult> storeImage(String fileName, 
                                           List<String> labels, 
                                           String mainColor) {
    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());

    return doc.set(data, SetOptions.merge());
  }

10. با GraalVM تصاویر برنامه بسازید

در این مرحله اختیاری، با استفاده از GraalVM یک JIT based app image و سپس یک Native Java app image ایجاد می‌کنید.

برای اجرای بیلد، باید مطمئن شوید که یک JDK مناسب و سازنده تصویر native نصب و پیکربندی شده است. چندین گزینه در دسترس است.

To start ، GraalVM 22.3.x Community Edition را دانلود کنید و دستورالعمل های صفحه نصب GraalVM را دنبال کنید.

این فرآیند را می توان تا حد زیادی با کمک SDKMAN ساده کرد!

برای نصب توزیع JDK مناسب با SDKman ، با استفاده از دستور install شروع کنید:

sdk install java 17.0.8-graal

به SDKman دستور دهید از این نسخه برای ساخت JIT و AOT استفاده کند:

sdk use java 17.0.8-graal

در Cloudshell ، برای راحتی شما، می توانید GraalVM و ابزار native-image را با این دستورات ساده نصب کنید:

# download GraalVM
wget https://download.oracle.com/graalvm/17/latest/graalvm-jdk-17_linux-x64_bin.tar.gz 
tar -xzf graalvm-jdk-17_linux-x64_bin.tar.gz

ls -lart

# configure Java 17 and GraalVM for Java 17
# note the name of the latest GraalVM version, as unpacked by the tar command
echo Existing JVM: $JAVA_HOME
cd graalvm-jdk-17.0.8+9.1

export JAVA_HOME=$PWD
cd bin
export PATH=$PWD:$PATH

echo JAVA HOME: $JAVA_HOME
echo PATH: $PATH

cd ../..

# validate the version with
java -version 

# observe
Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14, mixed mode, sharing)

ابتدا متغیرهای محیط پروژه GCP را تنظیم کنید:

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

سپس می توانید برای شروع ساخت آزمایشگاه به دایرکتوری حاوی سرویس بروید:

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

تصویر برنامه JIT را بسازید:

./mvnw package

گزارش ساخت را در ترمینال مشاهده کنید:

...
[INFO] Results:
[INFO] 
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ image-analysis ---
[INFO] Building jar: /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:3.2.0-M3:repackage (repackage) @ image-analysis ---
[INFO] Replacing main artifact /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.335 s
[INFO] Finished at: 2023-10-10T19:33:25Z
[INFO] ------------------------------------------------------------------------

تصویر Native را بسازید (از AOT استفاده می کند):

./mvnw native:compile -Pnative

گزارش ساخت را در ترمینال مشاهده کنید، از جمله گزارش‌های ساخت تصویر بومی:

توجه داشته باشید که ساخت، بسته به دستگاهی که روی آن آزمایش می کنید، کمی بیشتر طول می کشد.

...
[2/7] Performing analysis...  [*********]                                                              (124.5s @ 4.53GB)
  29,732 (93.19%) of 31,905 classes reachable
  60,161 (70.30%) of 85,577 fields reachable
 261,973 (67.29%) of 389,319 methods reachable
   2,940 classes, 2,297 fields, and 97,421 methods registered for reflection
      81 classes,    90 fields, and    62 methods registered for JNI access
       4 native libraries: dl, pthread, rt, z
[3/7] Building universe...                                                                              (11.7s @ 4.67GB)
[4/7] Parsing methods...      [***]                                                                      (6.1s @ 5.91GB)
[5/7] Inlining methods...     [****]                                                                     (4.5s @ 4.39GB)
[6/7] Compiling methods...    [******]                                                                  (35.3s @ 4.60GB)
[7/7] Creating image...                                                                                 (12.9s @ 4.61GB)
  80.08MB (47.43%) for code area:   190,483 compilation units
  73.81MB (43.72%) for image heap:  660,125 objects and 189 resources
  14.95MB ( 8.86%) for other data
 168.84MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
   2.66MB com.google.cloud.vision.v1p4beta1                   18.51MB byte[] for code metadata
   2.60MB com.google.cloud.vision.v1                           9.27MB java.lang.Class
   2.49MB com.google.protobuf                                  7.34MB byte[] for reflection metadata
   2.40MB com.google.cloud.vision.v1p3beta1                    6.35MB byte[] for java.lang.String
   2.17MB com.google.storage.v2                                5.72MB java.lang.String
   2.12MB com.google.firestore.v1                              4.46MB byte[] for embedded resources
   1.64MB sun.security.ssl                                     4.30MB c.oracle.svm.core.reflect.SubstrateMethodAccessor
   1.51MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3    4.27MB byte[] for general heap data
   1.47MB com.google.cloud.vision.v1p2beta1                    2.50MB com.oracle.svm.core.hub.DynamicHubCompanion
   1.34MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3     1.17MB java.lang.Object[]
  58.34MB for 977 more packages                                9.19MB for 4667 more object types
------------------------------------------------------------------------------------------------------------------------
                        13.5s (5.7% of total time) in 75 GCs | Peak RSS: 9.44GB | CPU load: 6.13
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
 /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating '/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis' in 3m 57s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:28 min
[INFO] Finished at: 2023-10-10T19:53:30Z
[INFO] ------------------------------------------------------------------------

11. ساخت و انتشار تصاویر کانتینر

بیایید یک تصویر ظرف در دو نسخه مختلف بسازیم: یکی به عنوان یک JIT image و دیگری به عنوان یک Native Java image .

ابتدا متغیرهای محیط پروژه GCP را تنظیم کنید:

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

ساخت تصویر JIT:.

./mvnw spring-boot:build-image -Pji

گزارش ساخت را در ترمینال مشاهده کنید:

[INFO]     [creator]     Timer: Saving docker.io/library/image-analysis-maven-jit:latest... started at 2023-10-10T20:00:31Z
[INFO]     [creator]     *** Images (4c84122a1826):
[INFO]     [creator]           docker.io/library/image-analysis-maven-jit:latest
[INFO]     [creator]     Timer: Saving docker.io/library/image-analysis-maven-jit:latest... ran for 6.975913605s and ended at 2023-10-10T20:00:38Z
[INFO]     [creator]     Timer: Exporter ran for 8.068588001s and ended at 2023-10-10T20:00:38Z
[INFO]     [creator]     Timer: Cache started at 2023-10-10T20:00:38Z
[INFO]     [creator]     Reusing cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'buildpacksio/lifecycle:cache.sbom'
[INFO]     [creator]     Timer: Cache ran for 200.449002ms and ended at 2023-10-10T20:00:38Z
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-maven-jit:latest'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  43.887 s
[INFO] Finished at: 2023-10-10T20:00:39Z
[INFO] ------------------------------------------------------------------------

تصویر AOT (Native) را بسازید:.

./mvnw spring-boot:build-image -Pnative

گزارش ساخت را در ترمینال مشاهده کنید، از جمله گزارش‌های ساخت تصویر بومی.

توجه:

  • که بسته به دستگاهی که روی آن تست می کنید، ساخت کمی بیشتر طول می کشد
  • تصاویر را می توان بیشتر با UPX فشرده کرد، با این حال تأثیر منفی کوچکی بر عملکرد راه اندازی دارد، بنابراین این ساخت از UPX استفاده نمی کند - همیشه یک مبادله جزئی است.
...
[INFO]     [creator]     Saving docker.io/library/image-analysis-maven-native:latest...
[INFO]     [creator]     *** Images (13167702674e):
[INFO]     [creator]           docker.io/library/image-analysis-maven-native:latest
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/bellsoft-liberica:native-image-svm'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]     [creator]     Adding cache layer 'buildpacksio/lifecycle:cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-maven-native:latest'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:37 min
[INFO] Finished at: 2023-10-10T20:05:16Z
[INFO] ------------------------------------------------------------------------

تأیید کنید که تصاویر ساخته شده اند:

docker images | grep image-analysis

دو تصویر را تگ کنید و به GCR فشار دهید:

# JIT image
docker tag image-analysis-maven-jit gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit

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

12. در Cloud Run مستقر شوید

زمان استقرار سرویس است.

شما دو بار سرویس را مستقر خواهید کرد، یک بار با استفاده از تصویر JIT و بار دوم با استفاده از تصویر AOT (Native). هر دو استقرار سرویس، برای مقاصد مقایسه، تصویر یکسانی را از سطل به صورت موازی پردازش خواهند کرد.

ابتدا متغیرهای محیط پروژه 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

تصویر JIT را اجرا کنید و گزارش استقرار را در کنسول مشاهده کنید:

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

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

تصویر Native را اجرا کنید و گزارش استقرار را در کنسول مشاهده کنید:

gcloud run deploy image-analysis-native \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native \
     --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. Eventarc Triggers را راه اندازی کنید

Eventarc یک راه حل استاندارد برای مدیریت جریان تغییرات حالت، به نام رویدادها، بین میکروسرویس های جدا شده ارائه می دهد. هنگامی که فعال می‌شود، Eventarc این رویدادها را از طریق اشتراک‌های Pub/Sub به مقصدهای مختلف هدایت می‌کند (در این سند، به مقصد رویداد مراجعه کنید) در حالی که تحویل، امنیت، مجوز، قابلیت مشاهده و مدیریت خطا را برای شما مدیریت می‌کند.

می‌توانید یک راه‌انداز Eventarc ایجاد کنید تا سرویس Cloud Run شما اعلان‌های یک رویداد یا مجموعه‌ای از رویدادها را دریافت کند. با تعیین فیلترها برای تریگر، می‌توانید مسیریابی رویداد، از جمله منبع رویداد و سرویس Cloud Run را پیکربندی کنید.

ابتدا متغیرهای محیط پروژه 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

اعطای pubsub.publisher به حساب سرویس 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'

راه‌اندازهای Eventarc را برای تصاویر سرویس JIT و Native برای پردازش تصویر تنظیم کنید:

gcloud eventarc triggers list --location=eu

gcloud eventarc triggers create image-analysis-jit-trigger \
     --destination-run-service=image-analysis-jit \
     --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    

توجه داشته باشید که دو محرک ایجاد شده اند:

gcloud eventarc triggers list --location=eu

14. نسخه های سرویس تست

پس از اجرای موفقیت آمیز سرویس، تصویری را در Cloud Storage پست می‌کنید، ببینید آیا خدمات ما فراخوانی شده‌اند، Vision API چه چیزی را برمی‌گرداند، و آیا ابرداده در Firestore ذخیره می‌شود.

به Cloud Storage برگردید و روی سطلی که در ابتدای آزمایشگاه ایجاد کردیم کلیک کنید:

33442485a1d76921.png

پس از ورود به صفحه جزئیات سطل، روی دکمه Upload files کلیک کنید تا یک عکس آپلود شود.

به عنوان مثال، یک تصویر GeekHour.jpeg به همراه پایگاه کد شما در زیر /services/image-analysis/java ارائه می شود. یک تصویر را انتخاب کنید و Open button فشار دهید:

d57529452f62bd32.png

اکنون می‌توانید اجرای سرویس را بررسی کنید، با image-analysis-jit و سپس image-analysis-native شروع کنید.

از منوی "همبرگر" (☰)، به Cloud Run > image-analysis-jit بروید.

روی Logs کلیک کنید و خروجی را مشاهده کنید:

ae1a4a94c7c7a166.png

و در واقع، در لیست لاگ ها، می توانم ببینم که سرویس JIT image-analysis-jit فراخوانی شده است.

گزارش ها شروع و پایان اجرای سرویس را نشان می دهد. و در این بین، ما می‌توانیم گزارش‌هایی را که در تابع خود قرار می‌دهیم با دستورات log در سطح INFO ببینیم. می بینیم:

  • جزئیات رویدادی که عملکرد ما را تحریک می کند،
  • نتایج خام از فراخوانی Vision API،
  • برچسب هایی که در تصویری که آپلود کردیم یافت شد،
  • اطلاعات رنگ های غالب،
  • اینکه آیا تصویر برای نشان دادن بی خطر است،
  • و در نهایت آن ابرداده های مربوط به تصویر در Firestore ذخیره شده است.

شما این فرآیند را برای سرویس image-analysis-native تکرار خواهید کرد.

از منوی "همبرگر" (☰)، به Cloud Run > image-analysis-native سرویس بروید.

روی Logs کلیک کنید و خروجی را مشاهده کنید:

4afe22833c1fd14c.png

اکنون می خواهید مشاهده کنید که آیا فراداده تصویر در Fiorestore ذخیره شده است یا خیر.

دوباره از منوی "همبرگر" (☰)، به بخش Firestore بروید. در زیر بخش Data (به طور پیش فرض نشان داده شده است)، باید مجموعه pictures را با یک سند جدید اضافه کنید، مطابق با تصویری که اخیراً آپلود کرده اید:

82d6c468956e7cfc.png

15. تمیز کردن (اختیاری)

اگر قصد ندارید با دیگر آزمایشگاه‌های این سری ادامه دهید، می‌توانید منابع را پاکسازی کنید تا در هزینه‌ها صرفه‌جویی کنید و در کل شهروند ابری خوبی باشید. می توانید منابع را به صورت جداگانه به صورت زیر پاک کنید.

سطل را حذف کنید:

gsutil rb gs://${BUCKET_PICTURES}

حذف تابع:

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

با انتخاب حذف مجموعه از مجموعه، مجموعه Firestore را حذف کنید:

6cc86a7b88fdb4d3.png

یا می توانید کل پروژه را حذف کنید:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. تبریک می گویم!

تبریک می گویم! شما اولین سرویس کلیدی پروژه را با موفقیت اجرا کردید!

آنچه را پوشش داده ایم

  • فضای ذخیره سازی ابری
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore
  • تصاویر بومی جاوا

مراحل بعدی