1. نظرة عامة
في هذا الدرس التطبيقي حول الترميز، سنتعرّف على مشروع Spring Native وإنشاء تطبيق يستخدمه ونشره على Google Cloud.
سنطّلع على مكوّناته، وآخر ما تمّ في المشروع، وبعض حالات الاستخدام، وبالطبع الخطوات المطلوبة لاستخدامه في مشاريعك.
إنّ مشروع Spring Native لا يزال في مرحلة تجريبية، لذا سيتطلب بعض الإعدادات المحدّدة للبدء. ومع ذلك، وكما أعلنّا في مؤتمر SpringOne 2021، من المقرر دمج Spring Native في Spring Framework 6.0 وSpring Boot 3.0 مع دعم من الدرجة الأولى، لذا هذا هو الوقت المثالي للاطّلاع عن كثب على المشروع قبل بضعة أشهر من إصداره.
على الرغم من أنّه تم تحسين ميزة "التجميع أثناء التنفيذ" بشكلٍ جيد جدًا لعمليات مثل العمليات التي تستغرق وقتًا طويلاً، هناك حالات استخدام معيّنة تحقّق فيها التطبيقات المجمّعة مسبقًا أداءً أفضل، وسنناقش هذه الحالات خلال ورشة عمل رموز البرامج.
ستتعرّف على كيفية
- استخدام Cloud Shell
- تفعيل Cloud Run API
- إنشاء تطبيق Spring Native ونشره
- نشر تطبيق مماثل على Cloud Run
المتطلبات
- مشروع على Google Cloud Platform يتضمّن حساب فوترة نشطًا على Google Cloud Platform
- تثبيت gcloud cli أو الوصول إلى Cloud Shell
- المهارات الأساسية في Java وXML
- معرفة جيدة بالأوامر الشائعة في نظام التشغيل Linux
استطلاع
كيف ستستخدم هذا الدليل التعليمي؟
كيف تقيّم تجربتك مع Java؟
ما مدى رضاك عن تجربتك في استخدام خدمات Google Cloud؟
2. الخلفية
يستخدم مشروع Spring Native عدة تقنيات لتقديم أداء التطبيقات الأصلية للمطوّرين.
لفهم Spring Native بالكامل، من المفيد فهم بعض تكنولوجيات المكوّنات هذه ومعرفة ما تتيحه لنا وكيفية عملها معًا.
تجميع AOT
عندما يشغّل المطوّرون javac بشكلٍ طبيعي في وقت الترجمة، يتم تحويل رمز المصدر .java إلى ملفات .class مكتوبة بترميز برمجي. لا يمكن فهم رمز التشغيل هذا إلا من خلال Java Virtual Machine، لذا سيكون على JVM تفسير هذا الرمز على الأجهزة الأخرى لنتمكّن من تشغيل الرمز.
وتوفّر لنا هذه العملية إمكانية نقل توقيع Java، ما يسمح لنا "بكتابة التوقيع مرة واحدة وتشغيله في كل مكان"، ولكنّها عملية مكلفة مقارنةً بتشغيل الرمز البرمجي الأصلي.
لحسن الحظ، تستخدم معظم عمليات تنفيذ JVM عملية الترجمة أثناء التشغيل للتخفيف من تكلفة التفسير هذه. ويتم تحقيق ذلك من خلال احتساب عدد عمليات استدعاء الدالة، وإذا تم استدعاؤها بشكل متكرر بما يكفي لتجاوز الحدّ الأدنى ( 10,000 عملية استدعاء تلقائيًا)، يتم تجميعها إلى رمز أصلي في وقت التشغيل لمنع المزيد من التفسيرات المُكلّفة.
يعتمد التجميع المُسبَق النهج المقابل، من خلال تجميع كل الرموز البرمجية التي يمكن الوصول إليها في ملف قابل للتنفيذ أصلي في وقت التجميع. ويؤدي ذلك إلى استبدال سهولة النقل بكفاءة الذاكرة ومكاسب الأداء الأخرى في وقت التشغيل.
هذا بالطبع إجراء موازنة، وقد لا يكون مفيدًا في بعض الأحيان. ومع ذلك، يمكن أن يُبرز أسلوب AOT للترجمة البرمجية ميزاته في حالات استخدام معيّنة، مثل:
- التطبيقات التي تُستخدَم لفترة قصيرة ويكون فيها وقت بدء التشغيل مهمًا
- البيئات التي تفرض قيودًا صارمة على الذاكرة حيث قد تكون تقنية JIT باهظة التكلفة
من المعلومات الممتعة أنّه تم تقديم عملية الترجمة المسبقة للغة الآلة كـ ميزة تجريبية في JDK 9، على الرغم من أنّ تكلفة صيانة هذا التنفيذ كانت مرتفعة، ولم يصبح رائجًا على الإطلاق، لذلك تم إزالته بهدوء في Java 17 لصالح المطوّرين الذين يستخدمون GraalVM فقط.
GraalVM
GraalVM هو توزيع JDK مفتوح المصدر محسَّن للغاية يتميز بوقت بدء تشغيل سريع للغاية وتجميع الصور الأصلية باستخدام AOT وإمكانات متعددة اللغات التي تسمح للمطوّرين بدمج لغات متعددة في تطبيق واحد.
إنّ GraalVM قيد التطوير النشط، ويكتسب إمكانات جديدة ويُحسّن الإمكانات الحالية طوال الوقت، لذا أشجّع المطوّرين على متابعتنا باستمرار.
في ما يلي بعض الإنجازات الأخيرة:
- نتيجة جديدة لبناء الصور الأصلية وسهلة الاستخدام ( 2021-01-18)
- توفُّر Java 17 ( 18-01-2022)
- تفعيل الترجمة المتعدّدة المستويات تلقائيًا لتحسين أوقات الترجمة المتعدّدة اللغات ( 2021-04-20)
Spring Native
ببساطة، يتيح 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
يستخدم هذا التطبيق المبتدئ Spring Boot 2.6.4، وهو أحدث إصدار يتوافق معه مشروع spring-native في وقت كتابة هذه المقالة.
يُرجى العِلم أنّه منذ إصدار GraalVM 21.0.3، يمكنك استخدام Java 17 لهذا العيّنة أيضًا. سنستمر في استخدام Java 11 في هذا الدليل التعليمي لتقليل الإعدادات المطلوبة.
بعد أن يتوفّر ملف zip في سطر الأوامر، يمكننا إنشاء دليل فرعي لمشروعنا وفك ضغط المجلد فيه:
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
للحصول على فكرة عن حجم الصورة الأساسية:
لتشغيل تطبيقنا:
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 وpluginRepositories التاليَين إلى pom:
<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-native، وهي مطلوبة لتشغيل تطبيق 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-plugin لتفعيل ميزة استخدام الصور الأصلية ونستخدم أداة إنشاء 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- إنشاء تطبيق أصلي وتشغيله
بعد الانتهاء من كل ذلك، من المفترض أن نتمكّن من إنشاء صورتنا وتشغيل تطبيقنا المُجمَّع والمتوافق مع الأجهزة الجوّالة.
قبل تشغيل الإصدار، إليك بعض النقاط التي يجب أخذها في الاعتبار:
- سيستغرق هذا الإجراء وقتًا أطول من عملية الإنشاء العادية (بضع دقائق).
- يمكن أن تستغرق عملية الإنشاء هذه قدرًا كبيرًا من الذاكرة (بضعة غيغابايت).
- تتطلّب عملية الإنشاء هذه إمكانية الوصول إلى برنامج Docker الخدمي.
- في هذا المثال، سننفّذ العملية يدويًا، ولكن يمكنك أيضًا ضبط مراحل الإنشاء لبدء ملف تعريف الإصدار الأصلي تلقائيًا.
لإنشاء صورتنا:
mvn spring-boot:build-image
بعد إنشاء التطبيق، سنكون جاهزين لتجربة التطبيق المتوافق مع الأجهزة الجوّالة.
لتشغيل تطبيقنا:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
في هذه المرحلة، أصبحنا في وضع جيد للاطّلاع على جانبَي معادلة التطبيقات المضمّنة.
لقد تخلّينا عن بعض الوقت واستخدام ذاكرة إضافية في وقت الترجمة، ولكن في المقابل، نحصل على تطبيق يمكنه بدء التشغيل بسرعة أكبر بكثير، ويستهلك ذاكرة أقل بكثير (حسب حجم العمل).
إذا أجرينا docker images demo
لمقارنة حجم الصورة الأصلية بالصورة الأصلية، يمكننا ملاحظة انخفاض كبير:
تجدر الإشارة أيضًا إلى أنّه في حالات الاستخدام الأكثر تعقيدًا، هناك تعديلات إضافية مطلوبة لإعلام مُجمِّع 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- النشر على Cloud Run
نحن الآن جاهزون لنشر الصورة التي حفظناها في Artifact Registry على Cloud Run:
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
بما أنّنا أنشأنا تطبيقنا ونشرناه كصورة أصلية، يمكننا الاطمئنان إلى أنّ تطبيقنا يستفيد بشكلٍ رائع من تكاليف البنية الأساسية أثناء تشغيله.
يمكنك مقارنة أوقات بدء تشغيل تطبيقنا الأساسي بهذا التطبيق الأصلي الجديد.
7- الملخّص/التنظيف
نشكرك على إنشاء تطبيق Spring Native ونشره على Google Cloud.
نأمل أن يشجّعك هذا الدليل التعليمي على التعرّف أكثر على مشروع Spring Native والتفكير فيه إذا كان يلبي احتياجاتك في المستقبل.
اختياري: تنظيف الخدمة و/أو إيقافها
سواء أكنت قد أنشأت مشروعًا على Google Cloud لهذا الدليل التعليمي حول رموز البرامج أو كنت تعيد استخدام مشروع حالي، احرِص على تجنُّب الرسوم غير الضرورية الناتجة عن الموارد التي استخدمناها.
يمكنك حذف أو إيقاف خدمات Cloud Run التي أنشأناها أو حذف الصورة التي استضفناها أو إيقاف المشروع بأكمله.
8. مراجع إضافية
على الرغم من أنّ مشروع Spring Native هو مشروع جديد وتجريبي حاليًا، تتوفّر حاليًا مجموعة كبيرة من المراجع الجيدة لمساعدة المستخدمين الأوائل في تحديد المشاكل وحلّها والمشاركة:
مراجع إضافية
في ما يلي مراجع على الإنترنت قد تكون ذات صلة بهذا الدليل التعليمي:
- مزيد من المعلومات عن "الإشارات الأصلية"
- مزيد من المعلومات حول GraalVM
- كيفية المشاركة
- خطأ "نفاد الذاكرة" عند إنشاء صور أصلية
- خطأ تعذُّر بدء التطبيق
الترخيص
يخضع هذا العمل للترخيص العام Creative Commons Attribution 2.0.