Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Pada masa lalu artikel Kami mengkaji asas teori seni bina reaktif. Sudah tiba masanya untuk bercakap tentang aliran data, cara untuk melaksanakan sistem Erlang/Elixir reaktif dan corak pemesejan di dalamnya:

  • Permintaan-tindak balas
  • Respon Dipotong Permintaan
  • Balas dengan Permintaan
  • Terbit-langgan
  • Terbitan terbalik-langganan
  • Pengagihan tugas

SOA, MSA dan Pemesejan

SOA, MSA ialah seni bina sistem yang mentakrifkan peraturan untuk membina sistem, manakala pemesejan menyediakan primitif untuk pelaksanaannya.

Saya tidak mahu mempromosikan seni bina sistem ini atau itu. Saya menggunakan amalan yang paling berkesan dan berguna untuk projek dan perniagaan tertentu. Walau apa pun paradigma yang kita pilih, adalah lebih baik untuk mencipta blok sistem dengan memerhatikan Unix-way: komponen dengan ketersambungan minimum, bertanggungjawab untuk entiti individu. Kaedah API melakukan tindakan paling mudah yang mungkin dengan entiti.

Pemesejan adalah, seperti namanya, broker mesej. Tujuan utamanya adalah untuk menerima dan menghantar mesej. Ia bertanggungjawab untuk antara muka untuk menghantar maklumat, pembentukan saluran logik untuk menghantar maklumat dalam sistem, penghalaan dan pengimbangan, serta pengendalian kerosakan pada peringkat sistem.
Pemesejan yang kami bangunkan bukanlah cuba untuk bersaing dengan atau menggantikan rabbitmq. Ciri-ciri utamanya:

  • Pengagihan.
    Mata pertukaran boleh dibuat pada semua nod kluster, sedekat mungkin dengan kod yang menggunakannya.
  • Kesederhanaan.
    Fokus pada meminimumkan kod boilerplate dan kemudahan penggunaan.
  • Persembahan terbaik.
    Kami tidak cuba mengulangi fungsi rabbitmq, tetapi hanya menyerlahkan lapisan seni bina dan pengangkutan, yang kami muatkan ke dalam OTP semudah mungkin, meminimumkan kos.
  • Kelenturan.
    Setiap perkhidmatan boleh menggabungkan banyak templat pertukaran.
  • Ketahanan mengikut reka bentuk.
  • Kebolehskalaan.
    Pemesejan berkembang dengan aplikasi. Apabila beban bertambah, anda boleh mengalihkan mata pertukaran ke mesin individu.

Komen. Dari segi organisasi kod, projek meta sangat sesuai untuk sistem Erlang/Elixir yang kompleks. Semua kod projek terletak dalam satu repositori - projek payung. Pada masa yang sama, perkhidmatan mikro diasingkan secara maksimum dan melaksanakan operasi mudah yang bertanggungjawab untuk entiti yang berasingan. Dengan pendekatan ini, ia adalah mudah untuk mengekalkan API keseluruhan sistem, ia adalah mudah untuk membuat perubahan, ia adalah mudah untuk menulis unit dan ujian integrasi.

Komponen sistem berinteraksi secara langsung atau melalui broker. Dari perspektif pemesejan, setiap perkhidmatan mempunyai beberapa fasa hayat:

  • Inisialisasi perkhidmatan.
    Pada peringkat ini, proses dan kebergantungan yang melaksanakan perkhidmatan dikonfigurasikan dan dilancarkan.
  • Mencipta titik pertukaran.
    Perkhidmatan ini boleh menggunakan titik pertukaran statik yang ditentukan dalam konfigurasi nod, atau mencipta titik pertukaran secara dinamik.
  • Pendaftaran perkhidmatan.
    Agar perkhidmatan dapat melayani permintaan, ia mesti didaftarkan di tempat pertukaran.
  • Berfungsi normal.
    Perkhidmatan ini menghasilkan kerja yang berguna.
  • Penyelesaian kerja.
    Terdapat 2 jenis penutupan yang mungkin: biasa dan kecemasan. Semasa operasi biasa, perkhidmatan diputuskan sambungan dari titik pertukaran dan berhenti. Dalam situasi kecemasan, pemesejan melaksanakan salah satu skrip failover.

Ia kelihatan agak rumit, tetapi kod itu tidak begitu menakutkan. Contoh kod dengan ulasan akan diberikan dalam analisis templat sedikit kemudian.

Pertukaran

Titik pertukaran ialah proses pemesejan yang melaksanakan logik interaksi dengan komponen dalam templat pemesejan. Dalam semua contoh yang dibentangkan di bawah, komponen berinteraksi melalui titik pertukaran, yang gabungannya membentuk pemesejan.

Corak pertukaran mesej (MEP)

Di peringkat global, corak pertukaran boleh dibahagikan kepada dua hala dan sehala. Yang pertama membayangkan tindak balas kepada mesej masuk, yang kedua tidak. Contoh klasik corak dua hala dalam seni bina pelayan-pelanggan ialah corak Permintaan-tindak balas. Mari lihat templat dan pengubahsuaiannya.

Permintaan-tindak balas atau RPC

RPC digunakan apabila kita perlu menerima respons daripada proses lain. Proses ini mungkin berjalan pada nod yang sama atau terletak di benua yang berbeza. Di bawah ialah gambar rajah interaksi antara klien dan pelayan melalui pemesejan.

Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Oleh kerana pemesejan adalah tidak segerak sepenuhnya, untuk pelanggan pertukaran dibahagikan kepada 2 fasa:

  1. menghantar permintaan

    messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).

    Pasaran Kripto β€’ nama unik tempat pertukaran
    ResponseMatchingTag β€’ label tempatan untuk memproses tindak balas. Sebagai contoh, dalam kes menghantar beberapa permintaan yang sama kepunyaan pengguna yang berbeza.
    RequestDefinition - badan permintaan
    Proses Pengendali β€’ PID pengendali. Proses ini akan menerima respons daripada pelayan.

  2. Memproses tindak balas

    handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)

    ResponsePayload - tindak balas pelayan.

Untuk pelayan, proses ini juga terdiri daripada 2 fasa:

  1. Memulakan titik pertukaran
  2. Memproses permintaan yang diterima

Mari kita ilustrasikan templat ini dengan kod. Katakan kita perlu melaksanakan perkhidmatan ringkas yang menyediakan satu kaedah masa tepat.

Kod pelayan

Mari kita tentukan API perkhidmatan dalam api.hrl:

%% =====================================================
%%  entities
%% =====================================================
-record(time, {
  unixtime :: non_neg_integer(),
  datetime :: binary()
}).

-record(time_error, {
  code :: non_neg_integer(),
  error :: term()
}).

%% =====================================================
%%  methods
%% =====================================================
-record(time_req, {
  opts :: term()
}).
-record(time_resp, {
  result :: #time{} | #time_error{}
}).

Mari kita takrifkan pengawal perkhidmatan dalam time_controller.erl

%% Π’ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ ΠΏΠΎΠΊΠ°Π·Π°Π½ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π·Π½Π°Ρ‡ΠΈΠΌΡ‹ΠΉ ΠΊΠΎΠ΄. Вставив Π΅Π³ΠΎ Π² шаблон gen_server ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Ρ€Π°Π±ΠΎΡ‡ΠΈΠΉ сСрвис.

%% инициализация gen_server
init(Args) ->
  %% ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ Ρ‚ΠΎΡ‡ΠΊΠ΅ ΠΎΠ±ΠΌΠ΅Π½Π°
  messaging:monitor_exchange(req_resp, ?EXCHANGE, default, self())
  {ok, #{}}.

%% ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° события ΠΏΠΎΡ‚Π΅Ρ€ΠΈ связи с Ρ‚ΠΎΡ‡ΠΊΠΎΠΉ ΠΎΠ±ΠΌΠ΅Π½Π°. Π­Ρ‚ΠΎ ΠΆΠ΅ событиС ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚, Ссли Ρ‚ΠΎΡ‡ΠΊΠ° ΠΎΠ±ΠΌΠ΅Π½Π° Π΅Ρ‰Π΅ Π½Π΅ Π·Π°ΠΏΡƒΡΡ‚ΠΈΠ»Π°ΡΡŒ.
handle_info(#exchange_die{exchange = ?EXCHANGE}, State) ->
  erlang:send(self(), monitor_exchange),
  {noreply, State};

%% ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° API
handle_info(#time_req{opts = _Opts}, State) ->
  messaging:response_once(Client, #time_resp{
result = #time{ unixtime = time_utils:unixtime(now()), datetime = time_utils:iso8601_fmt(now())}
  });
  {noreply, State};

%% Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΠ΅ Ρ€Π°Π±ΠΎΡ‚Ρ‹ gen_server
terminate(_Reason, _State) ->
  messaging:demonitor_exchange(req_resp, ?EXCHANGE, default, self()),
  ok.

Kod pelanggan

Untuk menghantar permintaan kepada perkhidmatan, anda boleh menghubungi API permintaan pemesejan di mana-mana dalam klien:

case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
    ok -> ok;
    _ -> %% repeat or fail logic
end

Dalam sistem yang diedarkan, konfigurasi komponen boleh menjadi sangat berbeza dan pada masa permintaan, pemesejan mungkin belum bermula atau pengawal perkhidmatan tidak akan bersedia untuk memberi perkhidmatan kepada permintaan. Oleh itu, kita perlu menyemak respons pemesejan dan mengendalikan kes kegagalan.
Selepas berjaya menghantar, pelanggan akan menerima respons atau ralat daripada perkhidmatan tersebut.
Mari kita kendalikan kedua-dua kes dalam handle_info:

handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time{unixtime = Utime}}}, State) ->
  ?debugVal(Utime),
  {noreply, State};

handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time_error{code = ErrorCode}}}, State) ->
  ?debugVal({error, ErrorCode}),
  {noreply, State};

Respon Dipotong Permintaan

Adalah lebih baik untuk mengelak daripada menghantar mesej yang besar. Responsif dan operasi stabil keseluruhan sistem bergantung pada ini. Jika respons kepada pertanyaan mengambil banyak memori, maka membahagikannya kepada beberapa bahagian adalah wajib.

Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Biar saya berikan anda beberapa contoh kes sedemikian:

  • Komponen bertukar data binari, seperti fail. Memecahkan respons kepada bahagian kecil membantu anda bekerja dengan cekap dengan fail dalam sebarang saiz dan mengelakkan limpahan memori.
  • Penyenaraian. Sebagai contoh, kita perlu memilih semua rekod daripada jadual besar dalam pangkalan data dan memindahkannya ke komponen lain.

Saya memanggil tindak balas ini lokomotif. Walau apa pun, 1024 mesej 1 MB adalah lebih baik daripada satu mesej 1 GB.

Dalam gugusan Erlang, kami mendapat faedah tambahan - mengurangkan beban pada titik pertukaran dan rangkaian, memandangkan respons segera dihantar kepada penerima, memintas titik pertukaran.

Balas dengan Permintaan

Ini adalah pengubahsuaian yang agak jarang berlaku pada corak RPC untuk membina sistem dialog.

Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Terbit-langganan (pokok pengedaran data)

Sistem dipacu acara menyampaikannya kepada pengguna sebaik sahaja data sedia. Oleh itu, sistem lebih cenderung kepada model tolak daripada model tarik atau tinjauan pendapat. Ciri ini membolehkan anda mengelakkan pembaziran sumber dengan sentiasa meminta dan menunggu data.
Rajah menunjukkan proses pengedaran mesej kepada pengguna yang melanggan topik tertentu.

Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Contoh klasik penggunaan corak ini ialah pengedaran keadaan: dunia permainan dalam permainan komputer, data pasaran di bursa, maklumat berguna dalam suapan data.

Mari lihat kod pelanggan:

init(_Args) ->
  %% подписываСмся Π½Π° ΠΎΠ±ΠΌΠ΅Π½Π½ΠΈΠΊ, ΠΊΠ»ΡŽΡ‡ = key
  messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
  {ok, #{}}.

handle_info(#exchange_die{exchange = ?SUBSCRIPTION}, State) ->
  %% Ссли Ρ‚ΠΎΡ‡ΠΊΠ° ΠΎΠ±ΠΌΠ΅Π½Π° нСдоступна, Ρ‚ΠΎ пытаСмся ΠΏΠ΅Ρ€Π΅ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒΡΡ
  messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
  {noreply, State};

%% ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ ΠΏΡ€ΠΈΡˆΠ΅Π΄ΡˆΠΈΠ΅ сообщСния
handle_info(#'$msg'{exchange = ?SUBSCRIPTION, message = Msg}, State) ->
  ?debugVal(Msg),
  {noreply, State};

%% ΠΏΡ€ΠΈ остановкС потрСбитСля - ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌΡΡ ΠΎΡ‚ Ρ‚ΠΎΡ‡ΠΊΠΈ ΠΎΠ±ΠΌΠ΅Π½Π°
terminate(_Reason, _State) ->
  messaging:unsubscribe(?SUBSCRIPTION, key, tag, self()),
  ok.

Sumber boleh memanggil fungsi untuk menerbitkan mesej di mana-mana tempat yang mudah:

messaging:publish_message(Exchange, Key, Message).

Pasaran Kripto - nama mata pertukaran,
Utama - kunci penghalaan
Mesej Anda - muatan

Terbitan terbalik-langganan

Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Dengan mengembangkan pub-sub, anda boleh mendapatkan corak yang mudah untuk pengelogan. Set sumber dan pengguna boleh berbeza sama sekali. Angka tersebut menunjukkan kes dengan satu pengguna dan berbilang sumber.

Corak pengagihan tugas

Hampir setiap projek melibatkan tugas pemprosesan tertunda, seperti menjana laporan, menyampaikan pemberitahuan dan mendapatkan semula data daripada sistem pihak ketiga. Daya tampung sistem yang melaksanakan tugasan ini boleh ditingkatkan dengan mudah dengan menambahkan pengendali. Apa yang tinggal untuk kami ialah membentuk sekumpulan pemproses dan mengagihkan tugas secara sama rata di antara mereka.

Mari kita lihat situasi yang timbul menggunakan contoh 3 pengendali. Malah pada peringkat pengagihan tugas, persoalan keadilan pengagihan dan limpahan pengendali timbul. Pengedaran round-robin akan bertanggungjawab ke atas keadilan, dan untuk mengelakkan situasi limpahan pengendali, kami akan memperkenalkan sekatan prefetch_limit. Dalam keadaan sementara prefetch_limit akan menghalang seorang pengendali daripada menerima semua tugasan.

Pemesejan menguruskan baris gilir dan keutamaan pemprosesan. Pemproses menerima tugas apabila mereka tiba. Tugas boleh diselesaikan dengan jayanya atau gagal:

  • messaging:ack(Tack) - dipanggil jika mesej berjaya diproses
  • messaging:nack(Tack) - dipanggil dalam semua situasi kecemasan. Sebaik sahaja tugasan dikembalikan, pemesejan akan menyampaikannya kepada pengendali lain.

Blok bangunan aplikasi yang diedarkan. Pendekatan pertama

Katakan kegagalan kompleks berlaku semasa memproses tiga tugas: pemproses 1, selepas menerima tugasan, ranap tanpa sempat melaporkan apa-apa ke tempat pertukaran. Dalam kes ini, titik pertukaran akan memindahkan tugas kepada pengendali lain selepas tamat masa ack telah tamat tempoh. Atas sebab tertentu, pengendali 3 telah meninggalkan tugasan dan menghantar nack; akibatnya, tugasan itu turut dipindahkan kepada pengendali lain yang berjaya menyelesaikannya.

Ringkasan awal

Kami telah merangkumi blok binaan asas sistem teragih dan mendapat pemahaman asas tentang penggunaannya dalam Erlang/Elixir.

Dengan menggabungkan corak asas, anda boleh membina paradigma yang kompleks untuk menyelesaikan masalah yang timbul.

Di bahagian akhir siri ini, kita akan melihat isu umum mengatur perkhidmatan, penghalaan dan pengimbangan, dan juga bercakap tentang sisi praktikal kebolehskalaan dan toleransi kesalahan sistem.

Akhir bahagian kedua.

Photo Marius Christensen
Ilustrasi disediakan menggunakan websequencediagrams.com

Sumber: www.habr.com

Tambah komen