حملة Spring Native على Google Cloud

1. نظرة عامة

في هذا الدرس التطبيقي حول الترميز، سنتعرف على مشروع Spring Native وتصميم تطبيق يستخدمه ونشره على Google Cloud.

سنراجع مكوناته، والتاريخ الحديث للمشروع، وبعض حالات الاستخدام، وبالطبع الخطوات المطلوبة لك لاستخدامه في مشروعاتك.

لا يزال مشروع Spring Native في المرحلة التجريبية حاليًا، لذا سيتطلب بعض الإعدادات المحددة للبدء. ومع ذلك، كما أعلنّا في SpringOne 2021، من المقرر دمج Spring Native في Springframe 6.0 وSpring Boot 3.0 مع دعم من الدرجة الأولى، لذا هذا هو الوقت المثالي لإلقاء نظرة عن كثب على المشروع قبل بضعة أشهر من إطلاقه.

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

ستتعرَّف على كيفية إجراء ما يلي:

  • استخدام Cloud Shell
  • تفعيل Cloud Run API
  • إنشاء تطبيق Spring Native ونشره
  • نشر مثل هذا التطبيق في Cloud Run

المتطلبات

استطلاع

كيف ستستخدم هذا البرنامج التعليمي؟

القراءة فقط اقرأها وأكمِل التمارين

كيف تقيّم تجربتك في استخدام Java؟

حديث متوسط بارع

ما هو تقييمك لتجربتك في استخدام خدمات Google Cloud؟

حديث متوسط بارع

2. الخلفية

يستفيد مشروع Spring Native من العديد من التقنيات في تقديم أداء التطبيقات الأصلية للمطورين.

لفهم كيفية استخدام Spring Native بشكل كامل، من المفيد التعرّف على بعض هذه التكنولوجيات والمكوّنات التي توفّرها لنا وطريقة عملها معًا.

تجميع AOT

عندما يقوم المطورون بتشغيل javac بشكل طبيعي في وقت التجميع، يتم تجميع رمز المصدر .java في ملفات .class التي تمت كتابتها برمز بايت. يهدف رمز البايت هذا فقط إلى فهمه على جهاز Java الافتراضي، لذلك يجب على JVM تفسير هذا الرمز على الأجهزة الأخرى لنتمكّن من تشغيل الرمز.

هذه العملية هي التي تعطينا إمكانية نقل توقيع Java - والتي تتيح لنا "الكتابة مرة واحدة والتشغيل في كل مكان"، ولكنها تكون مكلفة عند مقارنتها بتشغيل رمز أصلي.

ولحسن الحظ، فإن معظم عمليات تنفيذ آلة متجه الدعم تستخدم تقنية التحويل في الوقت المناسب للتخفيف من تكلفة التفسير هذه. ويتم تحقيق ذلك من خلال حساب عدد عمليات الاستدعاء لدالة، وإذا تم استدعاؤه كثيرًا بما يكفي لاجتياز حد ( 10,000 تلقائيًا)، يتم تجميع هذه العمليات إلى رموز برمجية أصلية في وقت التشغيل لمنع التفسير الأعلى تكلفة.

تنتهج عملية التجميع السابقة نهجًا معاكسًا، إذ يتم فيها تجميع كل الرموز البرمجية التي يمكن الوصول إليها في ملف أصلي قابل للتنفيذ في وقت التجميع. وهذا يستبدل قابلية النقل بكفاءة الذاكرة وغيرها من المكاسب في الأداء في وقت التشغيل.

5042e8e62a05a27.png

هذه مقايضة بالطبع، ولا تستحق دائمًا المشاركة. مع ذلك، يمكن أن تظهر الفيديوهات المجمّعة في AOT في بعض حالات الاستخدام، مثل:

  • التطبيقات التي تدوم لفترة قصيرة والتي يكون فيها وقت بدء التشغيل مهمًا
  • البيئات المحدودة للغاية في الذاكرة التي قد يكون فيها التجميع (JIT) مكلفًا جدًا

كحقيقة ممتعة، تم تقديم تجميع AOT باعتباره ميزة تجريبية في الإصدار JDK 9، رغم أنّ صيانته كان مكلفًا ولم يتم اكتشافه بعد، لذلك تمت إزالته تمامًا في Java 17 لصالح المطوّرين الذين يستخدمون GraalVM فقط.

GraalVM

GraalVM هي إحدى توزيعات JDK المفتوحة المصدر والمحسَّنة بشكل كبير والتي تتميز بأوقات بدء تشغيل سريعة للغاية وميزة تجميع الصور الأصلية لنظام AOT وإمكانات متعددة اللغات تتيح للمطوّرين مزج لغات متعددة في تطبيق واحد.

لا تزال GraalVM قيد التطوير، وتكتسب إمكانات جديدة وتعمل على تحسين الإمكانات الحالية طوال الوقت، لذا أشجّع المطوّرين على متابعة أخبارنا باستمرار.

في ما يلي بعض المعالم الرئيسية الحديثة:

  • نتيجة جديدة لإنشاء صور مدمجة مع المحتوى سهلة الاستخدام ( 18-01-2021)
  • دعم Java 17 ( 18-01-2022)
  • تفعيل ميزة التجميع المتعدد الطبقات بشكل تلقائي لتحسين أوقات التجميع المتعدد اللغات ( 2021-04-20)

الإعلانات المدمجة مع المحتوى في الربيع

باختصار، تتيح Spring Native استخدام برنامج تجميع الصور الأصلي من GraalVM لتحويل تطبيقات Spring إلى ملفات أصلية قابلة للتنفيذ.

تتضمن هذه العملية إجراء تحليل ثابت لتطبيقك في وقت التجميع لإيجاد جميع الطرق في تطبيقك التي يمكن الوصول إليها من نقطة الدخول.

وهذا يخلق في الأساس "عالم مغلق" تطبيقك، حيث يُفترض أن تكون جميع الرموز البرمجية معروفة وقت التجميع ولا يُسمح بتحميل أي رمز جديد في وقت التشغيل.

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

وفي بعض الحالات، لا يلزم إجراء أي تغييرات على الرموز البرمجية لكي يعمل أحد التطبيقات مع Spring Native. مع ذلك، تتطلب بعض الحالات ضبط إعدادات أصلية محدّدة لتعمل بشكل سليم. وفي هذه الحالات، غالبًا ما توفّر Spring Native تلميحات أصلية لتبسيط هذه العملية.

3- الإعداد/التمهيد

قبل البدء في تنفيذ Spring Native، سنحتاج إلى إنشاء تطبيق ونشره لإنشاء أساس للأداء يمكننا مقارنته بالإصدار الأصلي لاحقًا.

1. إنشاء المشروع

سنبدأ بالحصول على تطبيقنا من start.spring.io:

curl https://start.spring.io/starter.zip -d dependencies=web \
           -d javaVersion=11 \
           -d bootVersion=2.6.4 -o io-native-starter.zip

يستخدم تطبيق المبتدئين هذا الإصدار 2.6.4 من Spring Boot، وهو الإصدار الأحدث الذي يتوافقه المشروع الأصلي في فصل الربيع في وقت الكتابة.

تجدر الإشارة إلى أنّه بعد إصدار GraalVM 21.0.3، أصبح بإمكانك استخدام Java 17 لهذا النموذج أيضًا. سنواصل استخدام Java 11 في هذا البرنامج التعليمي لتقليل الإعدادات المعنية.

بمجرد الضغط على سطر الأوامر، يمكننا إنشاء دليل فرعي لمشروعنا وفك ضغط المجلد هناك:

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. التغييرات في الرمز

بعدما نفتح المشروع، سنضيف بسرعة إشارة إلى الحياة ونعرض أداء مشروع Spring Native بعد تشغيله.

عدّل DemoApplication.java ليتطابق مع هذا:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.Instant;

@RestController
@SpringBootApplication
public class DemoApplication {
    private static Instant startTime;
    private static Instant readyTime;

    public static void main(String[] args) {
        startTime = Instant.now();
                SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("/")
    public String index() {
        return "Time between start and ApplicationReadyEvent: "
                + Duration.between(startTime, readyTime).toMillis()
                + "ms";
    }

    @EventListener(ApplicationReadyEvent.class)
    public void ready() {
                readyTime = Instant.now();
    }
}

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

لإنشاء صورتنا:

mvn spring-boot:build-image

يمكنك أيضًا استخدام docker images demo للحصول على فكرة عن حجم الصورة الأساسية: 6ecb403e9af1475e.png.

لتشغيل تطبيقنا:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

3. نشر تطبيق أساسي

والآن بعد أن أصبح لدينا تطبيقنا، سننشره ونأخذ في الاعتبار الأوقات، التي سنقارنها بأوقات بدء تشغيل تطبيقنا المحلي لاحقًا.

واستنادًا إلى نوع التطبيق الذي تنشئه، هناك العديد من المواقع المختلفة التي تستضيف المحتوى الخاص بك.

ومع ذلك، نظرًا لأن المثال الذي ذكرناه هو تطبيق ويب بسيط ومباشر للغاية، يمكننا إبقاء الأمور بسيطة والاعتماد على Cloud Run.

في حال المتابعة على جهازك الخاص، يُرجى التأكّد من تثبيت أداة gcloud CLI وتحديثها.

إذا كنت تستخدم Cloud Shell الذي سيتم الاهتمام به بالكامل ويمكنك ببساطة تشغيل ما يلي في دليل المصدر:

gcloud run deploy

4. إعدادات التطبيق

1. إعداد مستودعات Maven

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

يشمل ذلك إضافة العناصر التالية إلى ملف pom.xml، وهو ما يمكنك تنفيذه في المحرِّر الذي تختاره.

يُرجى إضافة أقسام مستودعات و عمل الإضافي Repositories التالية إلى البودكاست الخاص بنا:

<repositories>
    <repository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </repository>
</repositories>

<pluginRepositories>
    <pluginRepository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </pluginRepository>
</pluginRepositories>

2. إضافة تبعياتنا

بعد ذلك، أضِف التبعية الأصلية لـ Spring، وهي مطلوبة لتشغيل تطبيق Spring كصورة أصلية. ملاحظة: هذه الخطوة غير ضرورية إذا كنت تستخدم Gradle

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.2</version>
    </dependency>
</dependencies>

3. إضافة/تفعيل المكوّنات الإضافية

يمكنك الآن إضافة المكوّن الإضافي AOT لتحسين التوافق مع الصور الأصلية وبصمتها الأصلية ( مزيد من المعلومات):

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-aot-maven-plugin</artifactId>
        <version>0.11.2</version>
        <executions>
            <execution>
                <id>generate</id>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

سنقوم الآن بتحديث المكوّن الإضافي spring-boot-maven- والذي يتيح إمكانية التوافق مع الصور الأصلية، واستخدام أداة إنشاء paketo لإنشاء صورتنا الأصلية:

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <image>
                <builder>paketobuildpacks/builder:tiny</builder>
                <env>
                    <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
                </env>
            </image>
        </configuration>
    </plugin>    
</plugins>

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

على سبيل المثال، إذا كنت تنشئ تطبيقًا يحتاج إلى الوصول إلى بعض مكتبات C الشائعة أو لم تكن متأكدًا بعد من متطلبات تطبيقك، قد يكون الإصدار الكامل مناسبًا بشكل أفضل.

5- إنشاء تطبيق محلي وتشغيله

وبعد تنفيذ كل ذلك، من المفترض أن نتمكن من إنشاء صورتنا وتشغيل تطبيقنا الأصلي الذي تم تجميعه.

قبل تنفيذ عملية الإنشاء، إليك بعض الأمور التي يجب أخذها في الاعتبار:

  • ستستغرق هذه العملية وقتًا أطول مقارنةً بأي إصدار عادي (بضع دقائق) d420322893640701.png
  • يمكن أن تستهلك عملية التصميم هذه مساحة كبيرة من الذاكرة (بضعة غيغابايت) cda24e1eb11fdbea.png
  • تتطلّب عملية التصميم هذه الوصول إلى البرنامج الخفي لـ Docker
  • سنجري هذه العملية يدويًا في هذا المثال، ولكن يمكنك أيضًا ضبط مراحل التصميم لتشغيل ملف شخصي مدمج مع المحتوى تلقائيًا.

لإنشاء صورتنا:

mvn spring-boot:build-image

وبعد الانتهاء من إنشاء التطبيق المحلي، أصبحنا جاهزين لتجربة التطبيق الأصلي.

لتشغيل تطبيقنا:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

ونكون في وضع جيد عند هذا الحد لرؤية طرفَي معادلة التطبيق الأصلي.

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

إذا أجرينا docker images demo لمقارنة حجم الصورة الأصلية بحجم الصورة الأصلية، يمكننا ملاحظة انخفاض كبير:

e667f65a011c1328.png

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

6- نشر تطبيقنا المحلي

لنشر تطبيقنا في Cloud Run، سنحتاج إلى الحصول على صورتنا الأصلية في أداة إدارة حزم، مثل Artifact Registry.

1. تحضير مستودع Docker

يمكننا بدء هذه العملية بإنشاء مستودع:

gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"

بعد ذلك، سنحتاج إلى التأكد من المصادقة لإرسال إلى قاعدة بيانات المسجّلين الجديدة.

يمكن لـ gcloud CLI تبسيط هذه العملية قليلاً:

gcloud auth configure-docker us-central1-docker.pkg.dev

2. إرسال صورتنا إلى Artifact Registry

بعد ذلك، سنضع علامة على الصورة:

export PROJECT_ID=$(gcloud config list --format 'value(core.project)')


docker tag  demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

بعد ذلك يمكننا استخدام docker push لإرساله إلى Artifact Registry:

docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

3- النشر إلى التشغيل في السحابة الإلكترونية

نحن الآن جاهزون لنشر الصورة التي خزّنناها في Artifact Registry على Cloud Run:

gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

وبما أنّنا صمّمنا تطبيقنا ونشرناه كصورة أصلية، يمكننا الاطمئنان إلى أنّ تطبيقنا يستفيد على نحو ممتاز من تكاليف البنية الأساسية أثناء تشغيله.

لا تتردد في مقارنة أوقات بدء تشغيل تطبيقنا الأساسي بهذا التطبيق الأصلي الجديد بنفسك!

6dde63d35959b1bb.png

7. الملخّص/تنظيف البيانات

تهانينا على إنشاء تطبيق Spring Native ونشره على Google Cloud.

نأمل أن يحثّك هذا الدليل التوجيهي على التعرّف أكثر على مشروع Spring Native مع أخذه في الاعتبار إذا كان يلبي احتياجاتك في المستقبل.

اختياري: حذف الخدمة و/أو إيقافها

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

يمكنك حذف أو إيقاف خدمات تشغيل السحابة الإلكترونية التي أنشأناها، أو حذف الصورة التي استضفناها، أو إيقاف المشروع بأكمله.

8. مراجع إضافية

على الرغم من أنّ مشروع Spring Native هو حاليًا مشروع جديد وتجريبي، إلا أن هناك ثروة من الموارد الجيدة لمساعدة المستخدمين في المراحل المبكرة في تحديد المشاكل وحلّها والمشاركة فيها:

مراجع إضافية

في ما يلي موارد على الإنترنت قد تكون ذات صلة بهذا البرنامج التعليمي:

الترخيص

هذا العمل مرخّص بموجب رخصة المشاع الإبداعي 2.0 مع نسب العمل إلى مؤلف عام.