مشاركة البيانات بين الخوادم المتعددة من خلال AWS S3
نشرت: 2022-03-10عند توفير بعض الوظائف لمعالجة ملف تم تحميله بواسطة المستخدم ، يجب أن يكون الملف متاحًا للعملية طوال فترة التنفيذ. عملية تحميل وحفظ بسيطة لا تقدم أي مشاكل. ومع ذلك ، إذا كان يجب معالجة الملف بالإضافة إلى ذلك قبل حفظه ، وكان التطبيق يعمل على عدة خوادم خلف موازن تحميل ، فنحن بحاجة إلى التأكد من أن الملف متاح لأي خادم يقوم بتشغيل العملية في كل مرة.
على سبيل المثال ، قد تتطلب وظيفة "تحميل الصورة الرمزية للمستخدم" متعددة الخطوات من المستخدم تحميل صورة رمزية في الخطوة 1 ، واقتصاصها في الخطوة 2 ، ثم حفظها في الخطوة 3. بعد تحميل الملف إلى خادم في الخطوة 1 ، يجب أن يكون الملف متاحًا لأي خادم يتعامل مع طلب الخطوتين 2 و 3 ، والتي قد تكون أو لا تكون هي نفسها للخطوة 1.
تتمثل الطريقة الساذجة في نسخ الملف الذي تم تحميله في الخطوة 1 إلى جميع الخوادم الأخرى ، لذلك سيكون الملف متاحًا عليها جميعًا. ومع ذلك ، فإن هذا النهج ليس فقط معقدًا للغاية ولكنه أيضًا غير عملي: على سبيل المثال ، إذا كان الموقع يعمل على مئات الخوادم ، من عدة مناطق ، فلا يمكن تحقيق ذلك.
يتمثل أحد الحلول الممكنة في تمكين "الجلسات الثابتة" على موازن التحميل ، والذي سيعين دائمًا نفس الخادم لجلسة معينة. بعد ذلك ، سيتم التعامل مع الخطوات 1 و 2 و 3 بواسطة نفس الخادم ، وسيظل الملف الذي تم تحميله إلى هذا الخادم في الخطوة 1 موجودًا للخطوتين 2 و 3. ومع ذلك ، لا يمكن الاعتماد على الجلسات الثابتة بشكل كامل: إذا كانت بين الخطوات 1 و 2 تعطل هذا الخادم ، ثم سيتعين على موازن التحميل تعيين خادم مختلف ، مما يؤدي إلى تعطيل الوظيفة وتجربة المستخدم. وبالمثل ، قد يؤدي تعيين نفس الخادم دائمًا لجلسة ما ، في ظل ظروف خاصة ، إلى أوقات استجابة أبطأ من خادم مثقل بالأعباء.
الحل الأكثر ملاءمة هو الاحتفاظ بنسخة من الملف على مستودع يمكن لجميع الخوادم الوصول إليه. بعد ذلك ، بعد تحميل الملف إلى الخادم في الخطوة 1 ، سيقوم هذا الخادم بتحميله إلى المستودع (أو ، بدلاً من ذلك ، يمكن تحميل الملف إلى المستودع مباشرة من العميل ، متجاوزًا الخادم) ؛ ستعمل خطوة معالجة الخادم 2 على تنزيل الملف من المستودع ومعالجته وتحميله هناك مرة أخرى ؛ وأخيرًا ، ستقوم الخطوة 3 الخاصة بمعالجة الخادم بتنزيله من المستودع وحفظه.
في هذه المقالة ، سأصف هذا الحل الأخير ، استنادًا إلى تطبيق WordPress لتخزين الملفات على Amazon Web Services (AWS) Simple Storage Service (S3) (حل تخزين كائنات سحابية لتخزين البيانات واستردادها) ، والتي تعمل من خلال AWS SDK.
ملاحظة 1: للحصول على وظيفة بسيطة مثل اقتصاص الصور الرمزية ، هناك حل آخر يتمثل في تجاوز الخادم تمامًا وتنفيذه مباشرةً في السحابة من خلال وظائف Lambda. ولكن نظرًا لأن هذه المقالة تتعلق بتوصيل تطبيق يعمل على الخادم بـ AWS S3 ، فإننا لا نفكر في هذا الحل.
ملاحظة 2: من أجل استخدام AWS S3 (أو أي من خدمات AWS الأخرى) ، سنحتاج إلى امتلاك حساب مستخدم. تقدم أمازون فئة مجانية هنا لمدة عام واحد ، وهو أمر جيد بما يكفي لتجربة خدماتهم.
ملاحظة 3: هناك مكونات إضافية تابعة لجهات خارجية لتحميل الملفات من WordPress إلى S3. أحد هذه المكونات الإضافية هو WP Media Offload (يتوفر الإصدار البسيط هنا) ، والذي يوفر ميزة رائعة: فهو ينقل بسلاسة الملفات التي تم تحميلها إلى مكتبة الوسائط إلى حاوية S3 ، مما يسمح بفصل محتويات الموقع (مثل كل شيء موجود أسفل / wp-content / uploads) من كود التطبيق. من خلال فصل المحتويات والرمز ، يمكننا نشر تطبيق WordPress الخاص بنا باستخدام Git (وإلا لا يمكننا ذلك نظرًا لأن المحتوى الذي تم تحميله بواسطة المستخدم غير مستضاف على مستودع Git) ، واستضافة التطبيق على خوادم متعددة (وإلا ، سيحتاج كل خادم إلى الاحتفاظ به نسخة من جميع المحتويات المحملة بواسطة المستخدم.)
خلق دلو
عند إنشاء الحاوية ، نحتاج إلى إيلاء الاعتبار لاسم الحاوية: يجب أن يكون كل اسم مجموعة فريدًا عالميًا على شبكة AWS ، لذلك على الرغم من أننا نود تسمية الحاوية الخاصة بنا بشيء بسيط مثل "الصور الرمزية" ، فقد يكون هذا الاسم مأخوذًا بالفعل ، فقد نختار شيئًا أكثر تميزًا مثل "avatars-name-of-company".
سنحتاج أيضًا إلى تحديد المنطقة التي يوجد بها الحاوية (المنطقة هي الموقع الفعلي حيث يوجد مركز البيانات ، مع مواقع في جميع أنحاء العالم.)
يجب أن تكون المنطقة هي نفس المنطقة التي يتم فيها نشر تطبيقنا ، بحيث يكون الوصول إلى S3 أثناء تنفيذ العملية سريعًا. خلاف ذلك ، قد يضطر المستخدم إلى الانتظار ثوانٍ إضافية من تحميل / تنزيل صورة إلى / من مكان بعيد.
ملاحظة: من المنطقي استخدام S3 كحل لتخزين الكائنات السحابية فقط إذا استخدمنا خدمة Amazon للخوادم الافتراضية على السحابة ، EC2 ، لتشغيل التطبيق. إذا كنا نعتمد بدلاً من ذلك على بعض الشركات الأخرى لاستضافة التطبيق ، مثل Microsoft Azure أو DigitalOcean ، فيجب علينا أيضًا استخدام خدمات تخزين الكائنات السحابية الخاصة بهم. خلاف ذلك ، سيعاني موقعنا من عبء نقل البيانات بين شبكات الشركات المختلفة.
في لقطات الشاشة أدناه ، سنرى كيفية إنشاء الحاوية حيث يتم تحميل الصور الرمزية للمستخدم من أجل الاقتصاص. نتوجه أولاً إلى لوحة معلومات S3 ونضغط على "إنشاء دلو":
ثم نكتب اسم المجموعة (في هذه الحالة ، "تحطيم الصور الرمزية") ونختار المنطقة ("الاتحاد الأوروبي (فرانكفورت)"):
لا يلزم سوى اسم المستودع والمنطقة. بالنسبة للخطوات التالية ، يمكننا الاحتفاظ بالخيارات الافتراضية ، لذلك نضغط على "التالي" حتى نضغط أخيرًا على "إنشاء دلو" ، وبهذا ، سننشئ الحاوية.
إعداد أذونات المستخدم
عند الاتصال بـ AWS من خلال SDK ، سيُطلب منا إدخال بيانات اعتماد المستخدم الخاصة بنا (زوج من معرف مفتاح الوصول ومفتاح الوصول السري) ، للتحقق من أننا نتمكن من الوصول إلى الخدمات والكائنات المطلوبة. يمكن أن تكون أذونات المستخدم عامة جدًا (يمكن لدور "المسؤول" أن يفعل كل شيء) أو شديد الدقة ، ما عليك سوى منح الإذن للعمليات المحددة المطلوبة ولا شيء آخر.
كقاعدة عامة ، كلما كانت الأذونات الممنوحة أكثر تحديدًا ، كان ذلك أفضل لتجنب مشكلات الأمان . عند إنشاء مستخدم جديد ، سنحتاج إلى إنشاء سياسة ، وهي عبارة عن مستند JSON بسيط يسرد الأذونات الممنوحة للمستخدم. في حالتنا ، ستمنح أذونات المستخدم الخاصة بنا الوصول إلى S3 ، من أجل "تحطيم الصور الرمزية" ، لعمليات "وضع" (لتحميل كائن) ، و "الحصول" (لتنزيل كائن) ، و "قائمة" ( لسرد جميع العناصر الموجودة في الحاوية) ، مما أدى إلى السياسة التالية:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:Put*", "s3:Get*", "s3:List*" ], "Resource": [ "arn:aws:s3:::avatars-smashing", "arn:aws:s3:::avatars-smashing/*" ] } ] }
في لقطات الشاشة أدناه ، يمكننا معرفة كيفية إضافة أذونات المستخدم. يجب أن نذهب إلى لوحة معلومات إدارة الهوية والوصول (IAM):
في لوحة التحكم ، نضغط على "المستخدمون" وبعد ذلك مباشرة بعد "إضافة مستخدم". في صفحة إضافة مستخدم ، نختار اسم مستخدم ("تجسيدات المحاصيل") ، ونضع علامة على "الوصول البرمجي" كنوع الوصول ، والذي سيوفر معرف مفتاح الوصول ومفتاح الوصول السري للاتصال عبر SDK:
ثم نضغط على الزر "التالي: أذونات" ، ثم انقر على "إرفاق السياسات الحالية مباشرةً" ، ثم انقر على "إنشاء سياسة". سيؤدي هذا إلى فتح علامة تبويب جديدة في المتصفح ، مع صفحة إنشاء سياسة. نضغط على علامة التبويب JSON ، وأدخل رمز JSON للسياسة المحددة أعلاه:
ثم نضغط على مراجعة السياسة ، ونعطيها اسمًا ("CropAvatars") ، ثم نقر في النهاية على إنشاء سياسة. بعد إنشاء السياسة ، نعود إلى علامة التبويب السابقة ، ونحدد سياسة CropAvatars (قد نحتاج إلى تحديث قائمة السياسات لرؤيتها) ، ثم انقر فوق التالي: مراجعة ، وأخيراً على إنشاء مستخدم. بعد الانتهاء من ذلك ، يمكننا أخيرًا تنزيل معرف مفتاح الوصول ومفتاح الوصول السري (يرجى ملاحظة أن بيانات الاعتماد هذه متوفرة لهذه اللحظة الفريدة ؛ إذا لم نقوم بنسخها أو تنزيلها الآن ، فسيتعين علينا إنشاء زوج جديد ):
الاتصال بـ AWS من خلال SDK
SDK متاح من خلال عدد لا يحصى من اللغات. بالنسبة لتطبيق WordPress ، نطلب SDK لـ PHP والذي يمكن تنزيله من هنا ، وتوجد هنا إرشادات حول كيفية تثبيته.
بمجرد الانتهاء من إنشاء الحاوية ، وبيانات اعتماد المستخدم جاهزة ، وتثبيت SDK ، يمكننا البدء في تحميل الملفات إلى S3.
رفع وتنزيل الملفات
للراحة ، نحدد بيانات اعتماد المستخدم والمنطقة على أنها ثوابت في ملف wp-config.php:
define ('AWS_ACCESS_KEY_ID', '...'); // Your access key id define ('AWS_SECRET_ACCESS_KEY', '...'); // Your secret access key define ('AWS_REGION', 'eu-central-1'); // Region where the bucket is located. This is the region id for "EU (Frankfurt)"
في حالتنا ، نقوم بتنفيذ وظيفة تجسيد المحصول ، والتي سيتم تخزين الصور الرمزية لها في دلو "تحطيم الصور الرمزية". ومع ذلك ، قد يكون لدينا في تطبيقنا العديد من الحاويات الأخرى لوظائف أخرى ، والتي تتطلب تنفيذ نفس عمليات التحميل والتنزيل وإدراج الملفات. ومن ثم ، فإننا ننفذ الطرق الشائعة على فئة مجردة AWS_S3
، ونحصل على المدخلات ، مثل اسم الحاوية المحدد من خلال الوظيفة get_bucket
، في الفئات الفرعية المنفذة.
// Load the SDK and import the AWS objects require 'vendor/autoload.php'; use Aws\S3\S3Client; use Aws\Exception\AwsException; // Definition of an abstract class abstract class AWS_S3 { protected function get_bucket() { // The bucket name will be implemented by the child class return ''; } }
تعرض فئة S3Client
واجهة برمجة التطبيقات للتفاعل مع S3. نقوم بإنشاء مثيل لها فقط عند الحاجة (من خلال التهيئة البطيئة) ، وحفظ مرجع لها تحت $this->s3Client
لمواصلة استخدام نفس المثيل:
abstract class AWS_S3 { // Continued from above... protected $s3Client; protected function get_s3_client() { // Lazy initialization if (!$this->s3Client) { // Create an S3Client. Provide the credentials and region as defined through constants in wp-config.php $this->s3Client = new S3Client([ 'version' => '2006-03-01', 'region' => AWS_REGION, 'credentials' => [ 'key' => AWS_ACCESS_KEY_ID, 'secret' => AWS_SECRET_ACCESS_KEY, ], ]); } return $this->s3Client; } }
عندما نتعامل مع $file
في تطبيقنا ، يحتوي هذا المتغير على المسار المطلق للملف في القرص (على سبيل المثال /var/app/current/wp-content/uploads/users/654/leo.jpg
) ، ولكن عند تحميل الملف ملف إلى S3 لا ينبغي لنا تخزين الكائن تحت نفس المسار. على وجه الخصوص ، يجب علينا إزالة الجزء الأولي المتعلق بمعلومات النظام ( /var/app/current
) لأسباب أمنية ، واختيارياً يمكننا إزالة /wp-content
bit (نظرًا لأن جميع الملفات مخزنة ضمن هذا المجلد ، فهذه معلومات زائدة عن الحاجة ) ، مع الاحتفاظ فقط بالمسار النسبي للملف ( /uploads/users/654/leo.jpg
). بشكل ملائم ، يمكن تحقيق ذلك عن طريق إزالة كل شيء بعد WP_CONTENT_DIR
من المسار المطلق. get_file
و get_file_relative_path
أدناه على التبديل بين مسارات الملفات المطلقة والنسبية:
abstract class AWS_S3 { // Continued from above... function get_file_relative_path($file) { return substr($file, strlen(WP_CONTENT_DIR)); } function get_file($file_relative_path) { return WP_CONTENT_DIR.$file_relative_path; } }
عند تحميل كائن إلى S3 ، يمكننا تحديد من يتم منحه حق الوصول إلى الكائن ونوع الوصول ، وذلك من خلال أذونات قائمة التحكم في الوصول (ACL). الخيارات الأكثر شيوعًا هي الاحتفاظ بالملف خاصًا (ACL => “خاص”) وإتاحة الوصول إليه للقراءة على الإنترنت (ACL => “public-read”). نظرًا لأننا سنحتاج إلى طلب الملف مباشرةً من S3 لعرضه للمستخدم ، فإننا نحتاج إلى ACL => “public-read”:
abstract class AWS_S3 { // Continued from above... protected function get_acl() { return 'public-read'; } }
أخيرًا ، نطبق طرق تحميل كائن إلى حاوية S3 وتنزيل كائن منها:
abstract class AWS_S3 { // Continued from above... function upload($file) { $s3Client = $this->get_s3_client(); // Upload a file object to S3 $s3Client->putObject([ 'ACL' => $this->get_acl(), 'Bucket' => $this->get_bucket(), 'Key' => $this->get_file_relative_path($file), 'SourceFile' => $file, ]); } function download($file) { $s3Client = $this->get_s3_client(); // Download a file object from S3 $s3Client->getObject([ 'Bucket' => $this->get_bucket(), 'Key' => $this->get_file_relative_path($file), 'SaveAs' => $file, ]); } }
بعد ذلك ، في فئة التطبيق الفرعي ، نحدد اسم الحاوية:
class AvatarCropper_AWS_S3 extends AWS_S3 { protected function get_bucket() { return 'avatars-smashing'; } }
أخيرًا ، نقوم ببساطة بإنشاء مثيل للفصل لتحميل الصور الرمزية إلى S3 أو التنزيل منه. بالإضافة إلى ذلك ، عند الانتقال من الخطوات 1 إلى 2 ومن 2 إلى 3 ، نحتاج إلى إيصال قيمة $file
. يمكننا القيام بذلك عن طريق إرسال حقل "file_relative_path" بقيمة المسار النسبي لملف $file
من خلال عملية POST (لا نمرر المسار المطلق لأسباب أمنية: لا داعي لتضمين "/ var / www / current" "معلومات ليراها الغرباء):
// Step 1: after the file was uploaded to the server, upload it to S3. Here, $file is known $avatarcropper = new AvatarCropper_AWS_S3(); $avatarcropper->upload($file); // Get the file path, and send it to the next step in the POST $file_relative_path = $avatarcropper->get_file_relative_path($file); // ... // -------------------------------------------------- // Step 2: get the $file from the request and download it, manipulate it, and upload it again $avatarcropper = new AvatarCropper_AWS_S3(); $file_relative_path = $_POST['file_relative_path']; $file = $avatarcropper->get_file($file_relative_path); $avatarcropper->download($file); // Do manipulation of the file // ... // Upload the file again to S3 $avatarcropper->upload($file); // -------------------------------------------------- // Step 3: get the $file from the request and download it, and then save it $avatarcropper = new AvatarCropper_AWS_S3(); $file_relative_path = $_REQUEST['file_relative_path']; $file = $avatarcropper->get_file($file_relative_path); $avatarcropper->download($file); // Save it, whatever that means // ...
عرض الملف مباشرة من S3
إذا أردنا عرض الحالة الوسيطة للملف بعد التلاعب في الخطوة 2 (على سبيل المثال ، الصورة الرمزية للمستخدم بعد اقتصاصها) ، فيجب علينا الرجوع إلى الملف مباشرة من S3 ؛ لا يمكن أن يشير عنوان URL إلى الملف الموجود على الخادم لأننا ، مرة أخرى ، لا نعرف الخادم الذي سيتعامل مع هذا الطلب.
أدناه ، نضيف الوظيفة get_file_url($file)
والتي تحصل على عنوان URL لهذا الملف في S3. في حالة استخدام هذه الوظيفة ، يرجى التأكد من أن قائمة التحكم في الوصول (ACL) للملفات التي تم تحميلها هي "قراءة عامة" ، وإلا فلن يتمكن المستخدم من الوصول إليها.
abstract class AWS_S3 { // Continue from above... protected function get_bucket_url() { $region = $this->get_region(); // North Virginia region is simply "s3", the others require the region explicitly $prefix = $region == 'us-east-1' ? 's3' : 's3-'.$region; // Use the same scheme as the current request $scheme = is_ssl() ? 'https' : 'http'; // Using the bucket name in path scheme return $scheme.'://'.$prefix.'.amazonaws.com/'.$this->get_bucket(); } function get_file_url($file) { return $this->get_bucket_url().$this->get_file_relative_path($file); } }
بعد ذلك ، يمكننا ببساطة الحصول على عنوان URL للملف على S3 وطباعة الصورة:
printf( "<img src='%s'>", $avatarcropper->get_file_url($file) );
سرد الملفات
إذا كنا نريد في تطبيقنا السماح للمستخدم بمشاهدة جميع الصور الرمزية التي تم تحميلها مسبقًا ، فيمكننا القيام بذلك. لذلك ، نقدم وظيفة get_file_urls
التي تسرد عنوان URL لجميع الملفات المخزنة ضمن مسار معين (في مصطلحات S3 ، تسمى بادئة):
abstract class AWS_S3 { // Continue from above... function get_file_urls($prefix) { $s3Client = $this->get_s3_client(); $result = $s3Client->listObjects(array( 'Bucket' => $this->get_bucket(), 'Prefix' => $prefix )); $file_urls = array(); if(isset($result['Contents']) && count($result['Contents']) > 0 ) { foreach ($result['Contents'] as $obj) { // Check that Key is a full file path and not just a "directory" if ($obj['Key'] != $prefix) { $file_urls[] = $this->get_bucket_url().$obj['Key']; } } } return $file_urls; } }
بعد ذلك ، إذا كنا نقوم بتخزين كل صورة رمزية تحت المسار "/ users / $ {user_id} /" ، فباجتياز هذه البادئة سنحصل على قائمة بجميع الملفات:
$user_id = get_current_user_id(); $prefix = "/users/${user_id}/"; foreach ($avatarcropper->get_file_urls($prefix) as $file_url) { printf( "<img src='%s'>", $file_url ); }
خاتمة
في هذه المقالة ، اكتشفنا كيفية استخدام حل تخزين كائنات سحابية للعمل كمستودع مشترك لتخزين الملفات لتطبيق تم نشره على خوادم متعددة. بالنسبة للحل ، ركزنا على AWS S3 ، وشرعنا في إظهار الخطوات اللازمة لدمجها في التطبيق: إنشاء الحاوية ، وإعداد أذونات المستخدم ، وتنزيل SDK وتثبيته. أخيرًا ، شرحنا كيفية تجنب المزالق الأمنية في التطبيق ، ورأينا أمثلة على التعليمات البرمجية توضح كيفية تنفيذ العمليات الأساسية على S3: تحميل وتنزيل وإدراج الملفات ، والتي بالكاد تتطلب بضعة أسطر من التعليمات البرمجية لكل منها. تُظهر بساطة الحل أن دمج الخدمات السحابية في التطبيق ليس بالأمر الصعب ، ويمكن أيضًا تحقيقه بواسطة المطورين الذين ليس لديهم خبرة كبيرة في السحابة.