التقاط الصور يوميًا: تخزين الصور وتحليلها باستخدام مكتبات عملاء Java المحلية من Google

1. نظرة عامة

ستخزن الصور في حزمة في التمرين المعملي الأول للرموز. سيؤدي ذلك إلى إنشاء حدث إنشاء ملف ستتم معالجته من خلال خدمة تم نشرها في Cloud Run. ستتصل الخدمة بواجهة برمجة تطبيقات Vision API لتحليل الصور وحفظ النتائج في مخزن بيانات.

427de3100de3a61e.png

المُعطيات

  • تخزين في السحابة الإلكترونية
  • Cloud Run
  • واجهة برمجة تطبيقات Cloud Vision
  • Cloud Firestore

2. الإعداد والمتطلبات

إعداد بيئة ذاتية

  1. سجِّل الدخول إلى Google Cloud Console وأنشئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • اسم المشروع هو الاسم المعروض للمشاركين في هذا المشروع. وهي سلسلة أحرف لا تستخدمها Google APIs. ويمكنك تعديله في أي وقت.
  • يجب أن يكون رقم تعريف المشروع فريدًا في جميع مشاريع Google Cloud وغير قابل للتغيير (لا يمكن تغييره بعد ضبطه). تنشئ Cloud Console سلسلة فريدة تلقائيًا. فعادةً لا تهتم بما هو. في معظم الدروس التطبيقية حول الترميز، يجب الإشارة إلى رقم تعريف المشروع (يتم تحديده عادةً على أنّه PROJECT_ID). وإذا لم يعجبك المعرّف الذي تم إنشاؤه، يمكنك إنشاء رقم تعريف عشوائي آخر. ويمكنك بدلاً من ذلك تجربة طلبك الخاص ومعرفة ما إذا كان متوفّرًا. ولا يمكن تغييره بعد هذه الخطوة وسيبقى طوال مدة المشروع.
  • لمعلوماتك، هناك قيمة ثالثة، وهي رقم المشروع الذي تستخدمه بعض واجهات برمجة التطبيقات. اطّلِع على مزيد من المعلومات حول هذه القيم الثلاث في المستندات.
  1. بعد ذلك، عليك تفعيل الفوترة في Cloud Console لاستخدام الموارد/واجهات برمجة التطبيقات في Cloud. إنّ تنفيذ هذا الدرس التطبيقي حول الترميز لن يكون مكلفًا أو مكلفًا على الإطلاق. لإيقاف تشغيل الموارد حتى لا تتحمل الفوترة بعد هذا البرنامج التعليمي، يمكنك حذف الموارد التي أنشأتها أو حذف المشروع بأكمله. يكون مستخدمو Google Cloud الجدد مؤهَّلون للانضمام إلى برنامج فترة تجريبية مجانية بقيمة 300 دولار أمريكي.

بدء Cloud Shell

مع أنّه يمكن إدارة Google Cloud عن بُعد من الكمبيوتر المحمول، ستستخدم في هذا الدرس التطبيقي Google Cloud Shell، وهي بيئة سطر أوامر يتم تشغيلها في السحابة الإلكترونية.

من Google Cloud Console، انقر على رمز Cloud Shell في شريط الأدوات العلوي الأيسر:

55efc1aaa7a4d3ad.png

من المفترَض أن تستغرق عملية إدارة الحسابات والاتصال بالبيئة بضع لحظات فقط. عند الانتهاء، من المفترض أن يظهر لك شيء مثل هذا:

7ffe5cbb04455448.png

يتم تحميل هذه الآلة الافتراضية مزوّدة بكل أدوات التطوير التي ستحتاج إليها. وتوفّر هذه الشبكة دليلاً رئيسيًا دائمًا بسعة 5 غيغابايت وتعمل على Google Cloud، ما يحسّن بشكل كبير من أداء الشبكة والمصادقة. يمكنك تنفيذ جميع أعمالك في هذا الدرس التطبيقي حول الترميز من خلال متصفّح. لا تحتاج إلى تثبيت أي تطبيقات.

3- تفعيل واجهات برمجة التطبيقات

ستستخدم في هذا التمرين المعملي Cloud Functions وVision API، ولكن يجب تفعيلها أولاً في Cloud Console أو باستخدام gcloud.

لتفعيل Vision API في Cloud Console، ابحث عن Cloud Vision API في شريط البحث:

cf48b1747ba6a6fb.png

ستنتقل إلى صفحة Cloud Vision API:

ba4af419e6086fbb.png

انقر على الزر ENABLE.

بدلاً من ذلك، يمكنك أيضًا تفعيلها في Cloud Shell باستخدام أداة سطر الأوامر 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 ( console.cloud.google.com) أو باستخدام أداة سطر الأوامر gsutil من Cloud Shell أو بيئة التطوير المحلية.

من "الهامبرغر" (⇧)، انتقِل إلى صفحة "Storage".

1930e055d138150a.png

إدخال اسم للحزمة

انقر على الزر CREATE BUCKET.

34147939358517f8.png

انقر على CONTINUE.

اختيار موقع جغرافي

197817f20be07678.png

يمكنك إنشاء حزمة متعددة المناطق في المنطقة التي تختارها (هنا Europe).

انقر على CONTINUE.

اختيار فئة التخزين التلقائية

53cd91441c8caf0e.png

اختَر فئة تخزين تبلغ Standard لبياناتك.

انقر على CONTINUE.

ضبط التحكّم في الوصول

8c2b3b459d934a51.png

نظرًا لأنك ستعمل على صور يمكن للجميع الوصول إليها، فأنت تريد أن يكون لجميع صورنا المخزنة في هذه الحزمة عنصر التحكم نفسه في الوصول.

حدِّد خيار التحكّم في الوصول إلى Uniform.

انقر على CONTINUE.

ضبط إعدادات الحماية أو التشفير

d931c24c3e705a68.png

الإبقاء على الإعدادات التلقائية (Google-managed key)، لأنّك لن تستخدم مفاتيح التشفير الخاصة بك.)

انقر على CREATE لإكمال عملية إنشاء الحزمة.

إضافة كل المستخدمين كعارض لمساحة التخزين

الانتقال إلى علامة التبويب "Permissions":

d0ecfdcff730ea51.png

يمكنك إضافة عضو allUsers إلى الحزمة، مع دور Storage > Storage Object Viewer، على النحو التالي:

e9f25ec1ea0b6cc6.png

انقر على SAVE.

5- إنشاء الحزمة (gsutil)

يمكنك أيضًا استخدام أداة سطر الأوامر gsutil في Cloud Shell لإنشاء الحِزم.

في Cloud Shell، اضبط متغيّرًا لاسم الحزمة الفريد. سبق أن تم ضبط GOOGLE_CLOUD_PROJECT على رقم تعريف مشروعك الفريد في Cloud Shell. ويمكنك إلحاق ذلك باسم الحزمة.

مثلاً:

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 علنية:

a98ed4ba17873e40.png

اختبِر قدرتك على تحميل الصور إلى الحزمة وأن الصور المُحمَّلة متاحة للجميع، كما هو موضَّح في الخطوة السابقة.

6- اختبار إمكانية الوصول العلني إلى الحزمة

بالعودة إلى متصفّح التخزين، ستظهر الحزمة في القائمة مع عرض الخيار "علنية". الوصول (بما في ذلك علامة تحذيرية تذكِّرك بأنّ أي مستخدم لديه إذن الوصول إلى محتوى هذه الحزمة)

89e7a4d2c80a0319.png

حزمتك جاهزة الآن لاستلام الصور.

عند النقر على اسم الحزمة، ستظهر تفاصيل الحزمة.

131387f12d3eb2d3.png

من هنا، يمكنك تجربة الزر "Upload files" لاختبار إمكانية إضافة صورة إلى الحزمة. ستطلب منك نافذة منتقي الملفات المنبثقة تحديد ملف. بعد اختيار الملف، سيتم تحميله إلى حزمتك، وستظهر لك مرة أخرى إذن وصول "public" الذي تمت إحالته تلقائيًا إلى هذا الملف الجديد.

e87584471a6e9c6d.png

على تصنيف الوصول Public، سيظهر لك أيضًا رمز رابط صغير. عند النقر عليها، سينتقل المتصفح إلى عنوان URL العام لتلك الصورة، والذي سيكون بالشكل التالي:

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

بما أنّ BUCKET_NAME هو الاسم الفريد العالمي الذي اخترته لحزمتك، ثم اسم ملف صورتك.

بالنقر على مربع الاختيار الموجود على طول اسم الصورة، سيتم تفعيل الزر DELETE، ويمكنك حذف هذه الصورة الأولى.

7. إعداد قاعدة البيانات

ستخزن معلومات حول الصورة المقدّمة من Vision API في قاعدة بيانات Cloud Firestore، وهي قاعدة بيانات مستندات NoSQL سريعة ومُدارة بالكامل وبدون خادم. يمكنك إعداد قاعدة البيانات بالانتقال إلى القسم Firestore في Cloud Console:

9e4708d2257de058.png

يتوفّر خياران: Native mode أو Datastore mode. استخدم الوضع الأصلي، الذي يوفر ميزات إضافية مثل إمكانية الاستخدام بلا اتصال بالإنترنت والمزامنة في الوقت الفعلي.

انقر على SELECT NATIVE MODE.

9449ace8cc84de43.png

اختَر منطقة متعددة (هنا في أوروبا، ولكن يُفضَّل أن تكون المنطقة نفسها على الأقل تكون الوظيفة وحزمة التخزين).

انقر على الزر CREATE DATABASE.

بعد إنشاء قاعدة البيانات، من المفترض أن يظهر لك ما يلي:

56265949a124819e.png

يمكنك إنشاء مجموعة جديدة بالنقر على الزر + START COLLECTION.

تسمية مجموعة "pictures".

75806ee24c4e13a7.png

لست بحاجة إلى إنشاء مستند. ستضيفها آليًا حيث يتم تخزين الصور الجديدة في Cloud Storage وتحليلها من خلال واجهة برمجة تطبيقات Vision.

انقر على Save.

ينشئ Firestore أول مستند افتراضي في المجموعة التي تم إنشاؤها حديثًا، ويمكنك حذف هذا المستند بأمان لأنه لا يحتوي على أي معلومات مفيدة:

5c2f1e17ea47f48f.png

ستحتوي المستندات التي سيتم إنشاؤها آليًا في مجموعتنا على 4 حقول:

  • name (سلسلة): اسم ملف الصورة التي تم تحميلها، وهو أيضًا مفتاح المستند
  • labels (مصفوفة من السلاسل): تصنيفات العناصر المعروفة بواسطة Vision API
  • color (سلسلة): رمز اللون السداسي العشري للون السائد (مثل #ab12ef)
  • CREATE (التاريخ): الطابع الزمني لوقت تخزين البيانات الوصفية لهذه الصورة
  • الصورة المصغّرة (قيمة منطقية): حقل اختياري يتم عرضه ويكون صحيحًا إذا تم إنشاء صورة مصغّرة لهذه الصورة

بينما سنبحث في 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 Console من خلال النقر على Indexes في عمود التنقّل على اليمين، ثم إنشاء فهرس مركب كما هو موضّح أدناه:

ecb8b95e3c791272.png

انقر على Create. قد يستغرق إنشاء الفهرس بضع دقائق.

8. استنساخ الرمز

استنسِخ الرمز إذا لم يسبق لك إجراء ذلك في التمرين المعملي السابق للرموز:

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

يمكنك بعد ذلك الانتقال إلى الدليل الذي يحتوي على الخدمة لبدء إنشاء التمرين المعملي:

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

وسيكون لديك تنسيق الملفات التالي للخدمة:

f79613aff479d8ad.png

9. التعرّف على رمز الخدمة

عليك أن تبدأ بإلقاء نظرة على كيفية تفعيل مكتبات برامج Java في pom.xml باستخدام BOM:

أولاً، عدِّل الملف pom.xml الذي يسرد الموارد التي تعتمد عليها دالة Java. تعديل الرمز لإضافة اعتمادية Maven التالية على Cloud Vision 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 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>

يتم تنفيذ الوظيفة في الفئة 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:

...
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) {
    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. إنشاء صور التطبيق باستخدام GraalVM (اختياري)

في هذه الخطوة الاختيارية، عليك إنشاء JIT(JVM) based app image، ثم AOT(Native) Java app image باستخدام GraalVM.

لتشغيل التصميم، يجب التأكّد من أنّ لديك ملف JDK مناسب وأداة إنشاء الصور الأصلية تم تثبيتها وتهيئتها. تتوفر عدة خيارات.

To start، عليك تنزيل GraalVM 22.2.x Community Edition واتّباع التعليمات الواردة في صفحة تثبيت GraalVM.

يمكن تبسيط هذه العملية إلى حد كبير بمساعدة SDKMAN!

لتثبيت توزيع JDK المناسب باستخدام SDKman، ابدأ باستخدام الأمر install:

sdk install java 22.2.r17-grl

توجيه SDKman إلى استخدام هذا الإصدار لكل من إصدارَي JIT وAOT:

sdk use java 22.2.0.r17-grl

تثبيت native-image utility لـ GraalVM:

gu install native-image

في Cloudshell، يمكنك تثبيت GraalVM وأداة النسخة الأصلية للصور باستخدام الأوامر البسيطة التالية:

# 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 ../..

أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:

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

يمكنك بعد ذلك الانتقال إلى الدليل الذي يحتوي على الخدمة لبدء إنشاء التمرين المعملي:

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

إنشاء صورة تطبيق JIT(JVM):

./mvnw package -Pjvm

لاحظ سجل الإصدار في الوحدة الطرفية:

...
[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] ------------------------------------------------------------------------

إنشاء صورة AOT(الأصلية):.

./mvnw package -Pnative -DskipTests

راقِب سجلّ الإصدار في الوحدة الطرفية، بما في ذلك سجلّات إنشاء الصور الأصلية:

يُرجى العِلم أنّ عملية الإصدار تستغرق وقتًا أطول قليلاً حسب الجهاز الذي تختبره.

...
[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. إنشاء صور الحاويات ونشرها

لننشئ صورة حاوية في إصدارَين مختلفَين: نسخة بتنسيق JIT(JVM) image والأخرى بتنسيق AOT(Native) Java image.

أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:

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

إنشاء صورة JIT(JVM):

./mvnw package -Pjvm-image

لاحظ سجل الإصدار في الوحدة الطرفية:

[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] ------------------------------------------------------------------------

إنشاء صورة AOT(الأصلية):.

./mvnw package -Pnative-image

راقِب سجلّ الإصدار في الوحدة الطرفية، بما في ذلك سجلّات إنشاء الصور الأصلية وضغط الصور باستخدام ملف UPX.

يُرجى العِلم أنّ عملية الإصدار تستغرق وقتًا أطول قليلاً، حسب الجهاز الذي تختبره عليه.

...
[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] ------------------------------------------------------------------------

تحقَّق من أنّه تم إنشاء الصور:

docker images | grep image-analysis

ضع علامة وادفع الصورتين إلى "آراء العملاء عبر Google":

# 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. النشر إلى التشغيل في السحابة الإلكترونية

حان وقت نشر الخدمة.

وسيتم نشر الخدمة مرتين، إحداهما باستخدام صورة JIT(JVM) والمرة الثانية باستخدام صورة AOT(Native). ستعالج كلتا عمليتَي نشر الخدمتَين الصورة نفسها من الحزمة بشكل متوازٍ، وذلك لأغراض المقارنة.

أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:

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(JVM) وراجِع سجلّ النشر في وحدة التحكّم:

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

انشر صورة AOT(Native) وراجِع سجلّ النشر في وحدة التحكّم:

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. إعداد مشغّلات Eventarc

توفّر منصة Eventarc حلاً موحّدًا لإدارة تدفق تغييرات الحالة، والذي يُطلق عليه اسم الأحداث، بين الخدمات المصغّرة المنفصلة. عند تفعيل Eventarc، توجّه هذه الأحداث من خلال اشتراكات النشر/الاشتراك إلى وجهات مختلفة (في هذا المستند، اطّلِع على وجهات الفعاليات) مع إدارة التسليم والأمان والتفويض وإمكانية الملاحظة ومعالجة الأخطاء نيابةً عنك.

يمكنك إنشاء مشغِّل Eventarc لكي تتلقى خدمة Cloud Run إشعارات لحدث محدّد أو مجموعة أحداث. من خلال تحديد فلاتر للعامل المشغِّل، يمكنك ضبط توجيه الحدث، بما في ذلك مصدر الحدث وخدمة تشغيل السحابة الإلكترونية المستهدَفة.

أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:

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 لكل من صور خدمة JVM(JIT) وAOT(Native) لمعالجة الصورة:

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    

لاحظ أنه قد تم إنشاء المشغلين:

gcloud eventarc triggers list --location=eu

14. إصدارات الخدمة التجريبية

بعد اكتمال عمليات نشر الخدمة بنجاح، ستنشر صورة على Cloud Storage، لتتعرّف على ما إذا تم استدعاء خدماتنا وما إذا كانت Vision API سترجعه وما إذا كانت البيانات الوصفية محفوظة في Firestore.

انتقِل مرة أخرى إلى Cloud Storage وانقر على الحزمة التي أنشأناها في بداية التمرين المعملي:

ff8a6567afc76235.png

بعد الانتقال إلى صفحة تفاصيل الحزمة، انقر على الزر Upload files لتحميل صورة.

على سبيل المثال، يتم توفير صورة GeekHour.jpeg مع قاعدة الرموز لديك ضمن /services/image-analysis/java. اختَر صورة واضغط على الزر Open button:

347b76e8b775f2f5.png

يمكنك الآن التحقّق من تنفيذ الخدمة، بدءًا من image-analysis-jvm، يليها image-analysis-native.

من "الهامبرغر" (⇧)، وانتقِل إلى خدمة "Cloud Run > image-analysis-jvm".

انقر فوق السجلات وراقب الإخراج:

810a8684414ceafa.png

وفي قائمة السجلّات، يظهر لي أنّه تم استدعاء image-analysis-jvm لخدمة JIT(JVM).

تشير السجلات إلى بدء تنفيذ الخدمة ونهايتها. وفي المنتصف، يمكننا رؤية السجلات التي وضعناها في دالتنا مع عبارات السجل على مستوى "معلومات". يتضح لنا ما يلي:

  • تفاصيل الحدث الذي يؤدي إلى تشغيل الدالة،
  • النتائج الأولية من طلب بيانات من واجهة Vision API
  • الملصقات التي تم العثور عليها في الصورة التي قمنا بتحميلها،
  • معلومات الألوان السائدة،
  • ما إذا كانت الصورة آمنة للعرض
  • وفي النهاية، تم تخزين تلك البيانات الوصفية حول الصورة في Firestore.

ستكرّر العملية لخدمة image-analysis-native.

من "الهامبرغر" (⇧)، وانتقِل إلى خدمة "Cloud Run > image-analysis-native".

انقر فوق السجلات وراقب الإخراج:

b80308c7d0f55a3.png

ننصحك الآن بمعرفة ما إذا كان قد تم تخزين البيانات الوصفية للصورة في Fiorestore.

مرة أخرى من "الهامبرغر" (⇧)، وانتقِل إلى قسم "Firestore". في القسم الفرعي Data (المعروض تلقائيًا)، من المفترض أن تظهر لك مجموعة pictures مع إضافة مستند جديد مطابق للصورة التي حمّلتها للتو:

933a20a9709cb006.png

15. إخلاء مساحة تخزين (اختياري)

إذا كنت لا تريد مواصلة الدروس التطبيقية الأخرى ضمن هذه السلسلة، يمكنك إخلاء بعض الموارد من أجل توفير التكاليف والتحلّي بصفات المواطنين الصالحين في السحابة الإلكترونية بشكل عام. يمكنك تنظيف الموارد بشكل فردي على النحو التالي.

حذف الحزمة:

gsutil rb gs://${BUCKET_PICTURES}

حذف الدالة:

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

احذف مجموعة Firestore من خلال تحديد "حذف المجموعة" من المجموعة:

410b551c3264f70a.png

بدلاً من ذلك، يمكنك حذف المشروع بالكامل:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. تهانينا!

تهانينا! لقد قمت بتنفيذ أول خدمة إدارة مفاتيح تشفير بنجاح للمشروع!

المواضيع التي تناولناها

  • تخزين في السحابة الإلكترونية
  • Cloud Run
  • واجهة برمجة تطبيقات Cloud Vision
  • Cloud Firestore
  • صور Java أصلية

الخطوات التالية