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

1. نظرة عامة

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

427de3100de3a61e.png

أهداف الدورة التعليمية

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • 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 لإنهاء عملية إنشاء الحزمة.

إضافة allUsers كمشاهد لمساحة التخزين

انتقِل إلى علامة التبويب 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 API.

انقر على Save.

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

5c2f1e17ea47f48f.png

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

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

بما أنّنا سنبحث في 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. عدِّل الرمز البرمجي لإضافة اعتمادية Cloud Vision API Maven:

<?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 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 هذه:

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.

لتشغيل الإصدار، عليك التأكّد من توفّر حزمة تطوير Java المناسبة وتثبيت أداة إنشاء native-image وإعدادها. تتوفّر عدة خيارات.

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

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

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

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 وأداة native-image باستخدام الأوامر البسيطة التالية:

# 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

أنشئ صورة تطبيق "التجميع أثناء التنفيذ" (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. النشر على Cloud Run

حان وقت تفعيل الخدمة.

ستنفّذ الخدمة مرّتين، مرة باستخدام صورة 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 هذه الأحداث من خلال اشتراكات Pub/Sub إلى وجهات مختلفة (في هذا المستند، اطّلِع على وجهات الأحداث) مع إدارة التسليم والأمان والتفويض وإمكانية تتبّع البيانات ومعالجة الأخطاء نيابةً عنك.

يمكنك إنشاء مشغّل Eventarc لكي تتلقّى خدمة Cloud Run إشعارات بحدث محدّد أو مجموعة أحداث. من خلال تحديد فلاتر للمشغّل، يمكنك ضبط توجيه الحدث، بما في ذلك مصدر الحدث وخدمة 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.

انقر على "السجلات" (Logs) ولاحظ الناتج:

810a8684414ceafa.png

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

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

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

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

من قائمة "الخطوط الثلاثة" (☰)، انتقِل إلى خدمة Cloud Run > image-analysis-native.

انقر على "السجلات" (Logs) ولاحظ الناتج:

b80308c7d0f55a3.png

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

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

933a20a9709cb006.png

15. التنظيف (اختياري)

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

حذف الحزمة:

gsutil rb gs://${BUCKET_PICTURES}

احذف الدالة:

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

احذف مجموعة Firestore من خلال اختيار "حذف المجموعة" (Delete collection) من المجموعة:

410b551c3264f70a.png

بدلاً من ذلك، يمكنك حذف المشروع بأكمله باتّباع الخطوات التالية:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. تهانينا!

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

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

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore
  • صور Java الأصلية

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