---
title: "فهم تطبيقات OTP في Elixir، الجزء 4: استبدال الوحدات أثناء التشغيل"
description: "سلسلة قصيرة عن المبادئ الأساسية للغتي Erlang وElixir. يناقش هذا الجزء قدرة Elixir على استبدال الكود أثناء التشغيل (hot-swap) في بيئات الإنتاج"
canonical: https://oozou.com/ar/blog/understanding-elixir-otp-applications-part-4-hot-swapping-modules-151
locale: ar
date: 2021-01-02
author: "OOZOU Team"
---

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

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

Published: 2021-01-02 · Author: OOZOU Team (Engineering) · 6 min read

ناقشت في [المقال السابق](https://oozou.com/blog/understanding-elixir-otp-applications-part-3-soft-real-time-150) كيف تستحق Elixir وصف لغة الوقت الفعلي المرن بفضل إدارتها الموثوقة للعمليات. والآن سنلقي نظرة على كيف تعيد Elixir تحميل الكود أثناء التشغيل (hot code reload) على مستوى الوحدات المنفردة.

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

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

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

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

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

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

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

يمكننا استخدام [r/1](https://hexdocs.pm/iex/IEx.Helpers.html#r/1) لإعادة تحميل الوحدة في iEX.

```elixir
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
```

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

```elixir
defmodule HelloWorld do
  def hello(name) do
    IO.puts("Goodbye, #{name}")
    :timer.sleep(1000)
    hello(name)
  end
end
```
```elixir
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

```elixir
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:

```elixir
iex(1)> HelloWorld.start_link
{:ok, #PID<0.154.0>}
Hi, Joe
Hi, Joe
Hi, Joe
```
```elixir
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
```
```elixir
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](https://hexdocs.pm/elixir/GenServer.html#c:code_change/3) ثلاثة وسائط: الإصدار القديم من الوحدة، والحالة الحالية، ومعلومات إضافية لازمة لإجراء التغيير. وهذه الدالة مفيدة حين تحتاجون إلى ترحيل الحالة لتتوافق مع الوحدة الجديدة، مثلًا:

```elixir
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) وتساعد على استبدال الوحدات القديمة.

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

---

Contact OOZOU: hello@oozou.com · +66 2544 0000 · https://oozou.com/ar/contact

More for AI agents: https://oozou.com/llms.txt · every page has a markdown twin at its URL plus ".md"
