---
title: "فهم تطبيقات OTP في Elixir، الجزء 2: تحمّل الأعطال"
description: "سلسلة قصيرة عن المبادئ الأساسية للغتي Erlang وElixir. يناقش هذا الجزء نهج Elixir في تحمّل الأعطال من خلال فلسفة \"let it crash\" (دعها تنهار)."
canonical: https://oozou.com/ar/blog/understanding-elixir-otp-applications-part-2-fault-tolerance-138
locale: ar
date: 2020-11-27
author: "OOZOU Team"
---

# فهم تطبيقات OTP في Elixir، الجزء 2: تحمّل الأعطال

> أستعرض في هذه السلسلة القصيرة المبادئ الأساسية للغتي Erlang وElixir واحدًا تلو الآخر. وفي هذا المقال أناقش نهج Elixir في تحمّل الأعطال من خلال فلسفة تُعرف باسم "let it crash" (دعها تنهار).

Published: 2020-11-27 · Author: OOZOU Team (Engineering) · 5 min read

ناقشت في [المقال السابق](https://oozou.com/blog/understanding-elixir-otp-applications-part-1-distribution-79) كيف تتعامل Elixir مع التوزيع عبر العمليات المعزولة وصناديق بريد الرسائل. والآن سنلقي نظرة على كيف تجعل Elixir هذه العمليات متحمّلة للأعطال.
أنشأنا سابقًا KeyValueStore يطبّق واجهة [GenServer](https://hexdocs.pm/elixir/GenServer.html). ولم نتعامل مع الأخطاء في تلك الوحدة. ففي بعض الحالات تكون البرمجة الدفاعية فكرة جيدة، وفي حالات أخرى تأتي بنتائج عكسية. لا يمكننا أن نعرف مسبقًا كل المشكلات التي قد تحدث في عملياتنا، لذا فإن التحصّن ضد جميع الاستثناءات (exceptions) مهمة مرهقة. في Elixir يُشجَّع المطور على مبدأ "let it crash"، أي ترك العملية تنهار ثم إعادة تشغيلها بحالة جديدة. وهذا منطقي في سياقات كثيرة، فمثلًا إذا انقطعت الشبكة، نعيد تشغيل العملية حتى يعود الاتصال. وبالطبع هناك أيضًا حالات يكون من الأفضل فيها معالجة بعض الحالات الحدّية (edge cases) المعروفة.
يمكننا استخدام [Supervisors](https://hexdocs.pm/elixir/Supervisor.html) لمراقبة عملياتنا وإعادة تشغيلها إن انهارت. المشرف (Supervisor) نوع من العمليات يراقب عملية أخرى، ثم يتخذ إجراءً ما إذا انهارت العملية الفرعية. ويتولى المشرف إدارة جميع أحداث دورة حياة العملية الفرعية، بما في ذلك بدء التشغيل والإيقاف.

# لنُضِف Supervisor لمراقبة KeyValueStore

```elixir
defmodule KeyValueStore.Supervisor do
  use Supervisor

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, :ok, opts)
  end

  def init(:ok) do
    children = [KeyValueStore]

    Supervisor.init(children, strategy: :one_for_one)
  end
end
```

قد تلاحظون هنا أمرين.
**ما هذه الأبناء (children)؟** يمكن للمشرف أن يدير أكثر من عملية واحدة. وكل عملية يديرها المشرف تُسمى ابنًا (child).**ما استراتيجية one for one؟** تحدد الاستراتيجية الإجراء الذي يحدث عند انتهاء أحد الأبناء:

- :one_for_one تعيد تشغيل العملية التي انتهت فقط.
- :one_for_all تنهي جميع الأبناء ثم تعيد تشغيلهم عند انتهاء أي عملية فرعية.
- :rest_for_one تعيد تشغيل العملية التي انتهت، وتنهي جميع العمليات الشقيقة التي تليها ثم تعيد تشغيلها، فمثلًا لو كان لدينا ما يلي

```elixir
children = [Module1, Module2, Module3]
```

إذا انهارت Module1، ستُعاد تشغيل جميع العمليات. أما إذا انهارت عملية Module2، فلن تُعاد إلا آخر عمليتين.
بدء تشغيل Supervisor
حين نبدأ تشغيل المشرف، يستدعي [child_spec/1](https://hexdocs.pm/elixir/Supervisor.html#module-child_spec-1) على كل واحد من أبنائه. تعيد الدالة child_spec/1 مواصفات توضح كيف ينبغي بدء تشغيل العملية. وهذه الدالة معرّفة لنا تلقائيًا لأننا نستخدم الوحدة GenServer في KeyValueStore.

```elixir
def child_spec(arg) do
  %{
    id: __MODULE__,
    start: {__MODULE__, :start_link, [arg]}
  }
end
```

إذا أردنا بدء تشغيل العملية بطريقة مختلفة، يمكننا إعادة تعريف هذه الدالة (override) لتخصيص الوسائط التي تُمرَّر عند البدء. يتوقع المشرف أن تعيد child_spec خريطة (map) تحتوي على الأقل على :id لتعريف العملية، و:start الذي يحدد كيف ستبدأ الوحدة. ويمكنكم أيضًا تمرير :restart و:shutdown و:type، لكنني لن أتناولها هنا.
نرى من الدالة child_spec/1 أعلاه أن المفتاح start يحدد كيف ستبدأ الوحدة. ستُمرَّر محتويات الخريطة إلى [apply/3](https://hexdocs.pm/elixir/Kernel.html#apply/3). وسنحدّث لاحقًا مواصفات الابن في المشرف، ونمرر اسمًا إلى GenServer الخاص بـ KeyValueStore.

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

```elixir
%{
  id: KeyValueStore,
  start: %{KeyValueStore, :start_link, [name: KeyValueStore]}
}
```

# لنجرّب KeyValueStore

نحتاج إلى تعديل صغير على GenServer الخاص بـ KeyValueStore. استخدمنا سابقًا [GenServer.start/3](https://hexdocs.pm/elixir/GenServer.html#start/3) لإنشاء العملية، فأنشأنا عملية غير مرتبطة تعيش خارج شجرة الإشراف (supervision tree). نريد إنشاء عملية مرتبطة بالمشرف، لذا لنغيّر الدالة GenServer.start/3 إلى [GenServer.start_link/3](https://hexdocs.pm/elixir/GenServer.html#start_link/3). يتوقع المشرف أن تتيح وحدتنا ذلك عبر دالة start_link تأخذ وسيطًا واحدًا هو خريطة الخيارات.

```
defmodule KeyValueStore do
  def start_link(opts) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end
end
langauge-elixir
```

يتيح لنا هذا التغيير أيضًا تسمية عملياتنا من داخل المشرف. لنفعل ذلك أيضًا.

```
children = [
  {KeyValueStore, name: :primary_key_value_store}
]
langauge-elixir
```

حسنًا، لنشغّله الآن ونجعله ينهار.

```elixir
KeyValueStore.Supervisor.start_link([])
:primary_key_value_store
|> KeyValueStore.set(:foo, :bar)
=> :ok
:primary_key_value_store
|> KeyValueStore.get(:foo)
=> :bar
GenServer.call(:primary_key_value_store, :invalid_call)
=> [error] GenServer :primary_key_value_store terminating
=> Last message (from #PID<0.501.0>): :invalid_call
=> State: %{foo: :bar}
=> Client #PID<0.501.0> is alive
:primary_key_value_store
|> KeyValueStore.get(:foo)
=> nil
```

رائع! أصبحت عمليتنا الآن تعيد تشغيل نفسها تلقائيًا حين تنهار.

# ما التالي؟

تناولنا كيف تدير Elixir الأنظمة الموزعة وتحمّل الأعطال، وسنلقي نظرة في المقال القادم على سبب اعتبار Elixir لغة تعمل في الوقت الفعلي المرن (soft real-time).

# هل تحتاجون إلى مساعدة؟
هل لديكم مشروع Elixir تحتاجون فيه إلى مساعدة؟ تواصلوا معنا لنرى كيف يمكننا مساعدتكم.

---

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"
