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

فهم تطبيقات OTP في Elixir، الجزء 1: التوزيع

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

25 نوفمبر 2020
5 دقائق للقراءة
فهم تطبيقات OTP في Elixir، الجزء 1: التوزيع

بُنيت Elixir فوق Erlang. وErlang لغة طُوّرت في شركة الاتصالات Ericsson، التي عُرفت لاحقًا باسم Sony Ericsson. حين صمّم المهندسون الأوائل Open Telecom Protocol الخاص بـ Erlang، كانت لديهم تصورات عن الخصائص التي يحتاجها النظام ليتعامل مع بنية الاتصالات لديهم بموثوقية. صُمّمت OTP لتكون موزّعة، ومتحمّلة للأعطال، وتعمل في الوقت الفعلي، وعالية التوفر، وقابلة للاستبدال أثناء التشغيل (hot-swappable). هذا كثير على الاستيعاب، فماذا يعني كل ذلك؟ سأشرح في هذا المقال كيف يعمل الجانب الموزّع من هذه المنظومة. مفاهيم Erlang وElixir متبادلة إلى حد كبير. فكثير من الوحدات (modules) في Elixir هي تجريدات فوق تجريدات Erlang، لذا يمكنكم اعتبارها المفهوم نفسه. سأناقش في هذا المقال كيف تعمل Processes وGenServer في Elixir، وهما في صميم أنظمتها الموزعة.

التواصل مع العمليات الموزعة عبر صناديق البريد

صُمّمت OTP لتعمل على الشبكات الموزعة. وهي تحقق ذلك عبر نظام صناديق البريد (mailbox). يمكن لأي عملية (Process) في Elixir أن تُنشئ عمليات فرعية، وهذه العمليات لا تتشارك أي ذاكرة، لذا فهي معزولة تمامًا. وللتواصل فيما بينها، ترسل الرسائل إلى صندوق بريد العملية الذي يُعرَّف بمعرّف العملية (PID). يمكن لكل عملية أن ترسل رسائل إلى عمليات أخرى، وأن تضبط مستمعًا على صندوق بريدها لمعالجة الرسائل الواردة. لنبدأ بإنشاء عملية بسيطة تعيد رسالة إلى صندوق بريد العملية الأم:

parent = self()
spawn fn -> send(parent, {:ok, self()}) end
Process.info(parent, :messages)
=> {:messages, [ok: #PID<0.000.0>]}

أولًا نحصل على PID العملية الحالية لنستخدمه داخل الدالة المجهولة التي نمررها إلى spawn. وداخل الدالة المُنشأة، نستدعي send/3. الوسيط الأول هو العملية التي نرسل إليها الرسالة، والثاني هو الرسالة نفسها. في هذه الحالة نرسل tuple يحتوي على :ok وPID العملية المُنشأة. يمكن أن تكون الرسالة بأي صيغة تريدونها، ومن الشائع في Elixir استخدام tuples حتى نطابق العنصر الأول ونعرف كيف نعالج العنصر الثاني. يمكننا فحص الرسائل باستخدام Process.info/2، وسنرى أن رسالتنا قد عادت بالفعل. والآن لنعالج هذه الرسائل:

parent = self()
spawn fn -> send(parent, {:ok, self()}) end
receive do
  {:ok, pid} -> "Got ok from #{inspect pid}"
  _ -> "Unknown message type"
end

تتيح لنا الدالة receive/1 إنشاء مستمع ينتظر وصول رسالة إلى صندوق بريد العملية الحالية. وداخل كتلة receive يمكننا مطابقة نوع الرسالة التي استقبلناها (pattern matching). إن بدت لكم صياغة receive مربكة، فذلك لأنها macro من نوع SpecialForm. حسنًا، لننشئ الآن حلقة تكرار ونطلق عملية مرتبطة (linked)، حتى نضمن ألا تنتهي عمليتنا ببساطة. بهذا الإعداد البسيط يمكننا تخزين الحالة (state)، أو في هذا المثال البسيط مجرد إعادة رسالة تحاكي روبوت محادثة بدائيًا.

defmodule Bot do
  def start_link do
    Task.start_link(fn -> loop() end)
  end

  defp loop() do
    receive do
      {:help, caller} ->
        send caller, "You can ask me what today's date is by passing :date_today"
        loop()
      {:date_today, caller} ->
        send caller, "Today's date is #{Date.utc_today()}"
        loop()
    end
  end
end

{:ok, pid} = Bot.start_link
send pid, {:help, self()}
flush()
=> "You can ask me what today's date is by passing :date_today"
=> :ok
send pid, {:date_today, self()}
=> "Today's date is 2020-11-20"
=> :ok

لاحظوا كيف تعيد المهمة المُنشأة تشغيل الحلقة في كل مرة تُعالَج فيها رسالة داخل receive. هذا هو الأساس الذي يقوم عليه GenServer.

فهم واجهة GenServer

حين بنى المطورون مكتبات OTP، أدركوا أن هذا النمط سيكون شائعًا، فأنشأوا تجريدًا يتولى عنا الحلقة ومعالجة الرسائل. يمكن للوحدة التي تطبّق GenServer أن تطبّق عددًا من دوال الاستدعاء الراجع (callbacks). سنستخدم init/1 وhandle_cast/2 وhandle_call/3. بعد ذلك يمكننا استخدام GenServer لبدء عملية تعمل في حلقة، وتنتظر cast/2 أو call/3. وحين تستقبل العملية cast/2 أو call/3، تستدعي الدالتين handle_cast/2 وhandle_call/3 على التوالي. لننشئ مخزنًا للمفاتيح والقيم (key-value store)، نسخة مبسطة من Redis أو Memcached. ولتسهيل الاستخدام وتحسين قابلية القراءة، سنضيف الدوال start/1 وset/3 وget/2 وdelete/2 لتغليف استدعاءات GenServer.cast/2 وGenServer.call/3

defmodule KeyValueStore do
  use GenServer

  def start(initial_state \\ %{}) do
    GenServer.start(__MODULE__, initial_state)
  end

  def set(server, key, value) do
    GenServer.cast(server, {:set, key, value})
  end

  def get(server, key) do
    GenServer.call(server, {:get, key})
  end

  def delete(server, key) do
    GenServer.cast(server, {:delete, key})
  end

  def init(state) do
    {:ok, state}
  end

  def handle_cast({:set, key, value}, state) do
    {:noreply, Map.put(state, key, value)}
  end

  def handle_cast({:delete, key}, state) do
    {:noreply, Map.delete(state, key)}
  end

  def handle_call({:get, key}, _from, state) do
    value = case Map.fetch(state, key) do
      {:ok, v} -> v
      :error -> nil
    end
    {:reply, value, state}
  end
end

الآن يمكننا تشغيل KeyValueStore، وتمرير أزواج المفاتيح والقيم إليه، واسترجاعها لاحقًا.

{:ok, pid} = KeyValueStore.start
Process.register(pid, :primary_store)
:primary_store
:primary_key_value_store |> KeyValueStore.set(:foo, :bar)
=> :ok
:primary_store
|> KeyValueStore.get(:foo)
=> :bar
:primary_store
|> KeyValueStore.delete(:foo)
=> :ok
:primary_store
|> KeyValueStore.get(:foo)
=> nil

ما التالي؟

سأشرح في المقال القادم كيف تتعامل Elixir مع تحمّل الأعطال عبر المشرفين (supervisors).

هل تحتاجون إلى مساعدة؟

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

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. جميع الحقوق محفوظة.
سياسة الخصوصيةمدونة قواعد السلوكسياسة مكافحة الرشوة والفساد