OOZOU
تواصلوا معنا
العودة إلى المدونة

فهم تطبيقات OTP في Elixir، الجزء 4: استبدال الوحدات أثناء التشغيل

أستعرض في هذه السلسلة القصيرة المبادئ الأساسية للغتي Erlang وElixir واحدًا تلو الآخر. وفي هذا المقال أناقش قدرة Elixir على استبدال الكود أثناء التشغيل (hot-swap) في بيئات الإنتاج

2 يناير 2021
6 دقائق للقراءة
فهم تطبيقات OTP في Elixir، الجزء 4: استبدال الوحدات أثناء التشغيل

ناقشت في المقال السابق كيف تستحق Elixir وصف لغة الوقت الفعلي المرن بفضل إدارتها الموثوقة للعمليات. والآن سنلقي نظرة على كيف تعيد Elixir تحميل الكود أثناء التشغيل (hot code reload) على مستوى الوحدات المنفردة.

ما الاستبدال أثناء التشغيل؟

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

كيف يعمل الاستبدال أثناء التشغيل؟

صُمّمت Erlang بطريقة تسمح بتشغيل عدة إصدارات من الوحدة نفسها في الوقت ذاته. وهذا يتيح لنا تحميل إصدارات جديدة من الوحدات إلى النظام بينما لا تزال الإصدارات القديمة تعمل. يمكن للآلة الافتراضية تشغيل إصدارين من الوحدة في آن واحد، وهذا يمنح مرونة كبيرة. وحين تُحمَّل إصدارات جديدة من وحدة ما، تواصل العمليات الحالية عملها، إلا إذا كان هناك إصداران من الوحدة قيد الاستخدام بالفعل، وفي هذه الحالة تُنهى العمليات التي تشغّل الإصدار الأقدم عند بلوغ الحد. أما العمليات المُنشأة حديثًا فتحمّل الإصدار الجديد من الكود. حين يُحمَّل إصدار جديد من وحدة ما، تُوجَّه جميع الاستدعاءات البعيدة (remote calls) إلى الإصدار الجديد. لكن الاستدعاءات المحلية (local calls) في العمليات الجارية تواصل استخدام الدوال القديمة. يمكننا رؤية ذلك عند تشغيل وحدة في iex وإعادة تحميلها:

defmodule HelloWorld do
  def hello(name) do
    IO.puts("Hi, #{name}")
    :timer.sleep(1000)
    hello(name)
  end
end
<pre>iex(1)> spawn(HelloWorld, :hello, ["Joe"])
#PID<0.119.0>
Hi, Joe
Hi, Joe
Hi, Joe

والآن إذا حدّثنا الوحدة، سنرى أن العملية الحالية تواصل استخدام الكود القديم:

defmodule HelloWorld do
  def hello(name) do
    IO.puts("Hello, #{name}")
    :timer.sleep(1000)
    hello(name)
  end
end

يمكننا استخدام r/1 لإعادة تحميل الوحدة في iEX.

Hi, Joe
Hi, Joe
Hi, Joe
iex(2)> r(HelloWorld)
iex(3)> r HelloWorld
warning: redefining module HelloWorld (current version loaded from _build/dev/lib/elixir_playground/ebin/Elixir.HelloWorld.beam)
  lib/hello_world.ex:1
Hi, Joe
iex(3)> spawn(HelloWorld, :hello, ["Joe"])
#PID<0.125.0>
Hello, Joe
Hi, Joe
Hello, Joe

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

defmodule HelloWorld do
  def hello(name) do
    IO.puts("Goodbye, #{name}")
    :timer.sleep(1000)
    hello(name)
  end
end
iex(5)> r HelloWorld
warning: redefining module HelloWorld (current version defined in memory)
  lib/hello_world.ex:1
Hello, Joe
Hello, Joe
Hello, Joe
Hello, Joe
Hello, Joe
Hello, Joe
Hello, Joe

فماذا حدث؟ لماذا لم تعد لدينا سوى عملية واحدة تعمل؟ صُمّمت آلة Erlang الافتراضية لتشغّل إصدارين من الكود كحد أقصى في الوقت نفسه، لذا أُزيل الإصدار الأقدم وانهارت العملية.

لنلقِ نظرة على استبدال وحدة GenServer أثناء التشغيل

بسبب طبيعة GenServer، من المهم ملاحظة أن إعادة التحميل أثناء التشغيل تتم بطريقة مختلفة قليلًا. فكما ذكرنا سابقًا، تُستبدل جميع الاستدعاءات البعيدة لوحدة ما بمجرد تحميل إصدار جديد منها في الآلة الافتراضية. والوحدات التي تستخدم GenServer تخزّن الحالة وتحدّثها فعليًا في عمليات منفصلة. ينشئ GenServer حلقة تخزّن الحالة ثم تستدعي دوال الاستدعاء الراجع (callbacks) في وحدتنا. وهذا يعني أن جميع الاستدعاءات لوحدتنا استدعاءات بعيدة. وكما تعلّمنا سابقًا، تُستبدل الاستدعاءات البعيدة بمجرد إعادة تحميل الوحدات. وتشمل وظائف GenServer ميزات متعددة مثل إدارة الحالة ومعالجة الرسائل غير المتزامنة. لنرَ ذلك عمليًا. defmodule HelloWorld do use GenServer

defmodule HelloWorld do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, "Joe")
  end

  def init(state) do
    Process.send_after(self(), :hello, 1000)
    {:ok, state}
  end

  def handle_info(:hello, name) do
    IO.puts("Hi, #{name}")
    Process.send_after(self(), :hello, 1000)
    {:noreply, name}
  end
end

نستخدم هنا مطابقة الأنماط (pattern matching) في دالة handle_info لالتقاط استدعاءات Process.send_after/3. يتصرف هذا الكود بالطريقة نفسها التي تتصرف بها وحدة HelloWorld الأصلية. والآن إذا استدعينا HelloWorld.start_link/0 ثم حدّثنا HelloWorld وأعدنا تحميلها، سنرى كيف يتصرف GenServer:

iex(1)> HelloWorld.start_link
{:ok, #PID<0.154.0>}
Hi, Joe
Hi, Joe
Hi, Joe
defmodule HelloWorld do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, "Joe")
  end

  def init(state) do
    Process.send_after(self(), :hello, 1000)
    {:ok, state}
  end

  def handle_info(:hello, name) do
    IO.puts("Hello, #{name}")
    Process.send_after(self(), :hello, 1000)
    {:noreply, name}
  end
end
iex(2)> r HelloWorld
warning: redefining module HelloWorld (current version loaded from _build/dev/lib/elixir_playground/ebin/Elixir.HelloWorld.beam)
  lib/hello_world.ex:1

{:reloaded, HelloWorld, [HelloWorld]}
Hello, Joe
Hello, Joe

كما نرى، أُعيد تحميل الوحدة وتغيّر الناتج فورًا. ويعود ذلك إلى أن GenServer يستدعي handle_info على الوحدة، فيصبح الاستدعاء استدعاءً بعيدًا.

التعامل مع تغيّرات الحالة

في مثالنا لا ندير أي حالة فعليًا. نمرر اسمي، لكنه ليس حالة حقيقية، فقد استخدمناه لأغراض التوضيح فقط. لنتخيل أننا نحوّل خريطة (map) معقدة مليئة بالبيانات المتداخلة. في كل مرة تعيدون فيها تحميل الكود، عليكم التأكد من أن الحالة تُدار بشكل صحيح عند الانتقال من الإصدار القديم إلى الجديد. يوفر GenServer دالة استدعاء راجع يمكننا تطبيقها لتعديل الحالة عند تغيّر الكود. تستقبل code_change/3 ثلاثة وسائط: الإصدار القديم من الوحدة، والحالة الحالية، ومعلومات إضافية لازمة لإجراء التغيير. وهذه الدالة مفيدة حين تحتاجون إلى ترحيل الحالة لتتوافق مع الوحدة الجديدة، مثلًا:

defmodule EnvState do
  change_code(_old_vsn, state, _extra) do
    status = state[:stored_status]
    errors = state[:repo][:errors]
    {:ok, %{status: status, errors: errors}
  end
end

فوائد الاستبدال أثناء التشغيل في Elixir

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

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

ودمج الاستبدال أثناء التشغيل في تطبيقات Elixir لا يحسّن العمليات التشغيلية فحسب

الخلاصة ألقينا نظرة سريعة على كيف تتعامل Elixir مع الاستبدال أثناء التشغيل. هناك تفاصيل أخرى كثيرة تستحق البحث، لكنها خارج نطاق هذه السلسلة. إن كنتم مهتمين باستبدال الكود أثناء التشغيل في بيئات الإنتاج لديكم، فأنصحكم بالاطلاع على الترقيات أثناء التشغيل (hot upgrades) باستخدام distillery. تبسّط distillery عملية إنشاء الإصدارات (releases) وتساعد على استبدال الوحدات القديمة.

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

Related Articles

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

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

اقرأوا المزيد
مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

يتطور المشهد الرقمي بسرعة، وعلى الشركات أن تتكيّف لتواكب الطلب المتزايد على السرعة والكفاءة والمعالجة في الوقت الفعلي.

اقرأوا المزيد
مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

يتطور قطاع التجزئة بوتيرة غير مسبوقة بفضل الذكاء الاصطناعي (AI) والواقع المعزّز (AR) وتقنيات التحوّل الرقمي. فلم تعد المتاجر التقليدية مجرد أماكن لبيع المنتجات، بل أصبحت متاجر ذكية وتفاعلية.

اقرأوا المزيد

Have a Project in Mind?

Let's discuss how we can help bring your ideas to life with our expertise in web and mobile development.

ابدأوا المحادثةhello@oozou.com
OOZOU Logo

بانكوك · سنغافورة · هونغ كونغ

X
Facebook
Instagram
LinkedIn
OOZOU Co., Ltd.
548 One City Centre
Ploenchit Road
Bangkok 10330
Thailand
+66 2544 0000
hello@oozou.com

الخدمات

  • تطوير الويب
  • وكلاء الذكاء الاصطناعي والذكاء الاصطناعي التوليدي
  • تطوير تطبيقات الجوال
  • تحليلات البيانات وهندستها
  • تصميم تجربة المستخدم وواجهته (UX/UI) وتصميم المنتجات
  • التحوّل الرقمي

الشركة

  • من نحن
  • الوظائف
  • المدونة
  • دراسات الحالة
  • عروض حية
  • تعلّمت اليوم
  • تواصل معنا

المزيد

  • القطاعات
  • شبكة الشركاء
© 2026 OOZOU. جميع الحقوق محفوظة.
سياسة الخصوصيةمدونة قواعد السلوكسياسة مكافحة الرشوة والفساد