OTP 관리자에서 종속성 주입 및 검색
23434 단어 elixirerlangsupervisorsotp
우리들의 임무
우리는 서로를 이해하기 위해 상사의 형제자매가 필요하다.가장 좋은 것은 우리의 주관 트리가'폐쇄적'이기를 희망한다. 서로 다른 옵션으로 여러 개의 트리 실례를 시작할 수 있다.
예: 네트워크 데이터를 처리하는 데 사용되는 파이프, 이 파이프는 서로 통신하는 몇 개의 프로세스로 구성되어 있으며, 이러한 프로세스는 데이터를 소모하고 변환한다.우리는 특수한 방식으로 서로 다른 데이터 원본을 위해 서로 다른 파이프를 가동하기를 희망한다. 이렇게 하면 서로 간섭하지 않고 밀봉해서는 안 된다.
이 점을 해낼 수 있는 몇 가지 방법이 있다.
우리의 선택
하드 코딩 프로세스 이름 등록
가장 간단하면서도 가장 흔히 볼 수 있는 방법은 전역 이름을 사용하여 프로세스를 간단하게 등록하는 것이다.
defmodule PipelineSupervisor do
use Supervisor
@impl Supervisor
def init([]) do
children = [
{SomeConsumer, name: ConsumerGlobalName}
{SomeWorker, %{consumer: ConsumerGlobalName}},
]
Supervisor.init(children, strategy: :rest_for_one)
end
end
defmodule SomeWorker do
use GenServer
@impl GenServer
def init(%{consumer: consumer} = options) do
{:ok, options}
end
@impl GenServer
def handle_call({:process_data_chunk, data_chunk}, _from, state) do
{:reply, :ok, do_process_data_chunk(data_chunk, state)}
end
defp do_process_data_chunk(data_chunk, state) do
case parse_data_somehow(data_chunk, state) do
{:ok, parsed_message, new_state} ->
Process.send(state.consumer, {:message_received, parsed_message})
new_state
{:more_data_necessary, new_state} ->
new_state
end
end
end
defmodule SomeConsumer do
use GenServer
def start_link(%{name: name}) do
GenServer.start_link(__MODULE__, [], name: name)
end
@impl GenServer
def init([]) do
{:ok, :undefined_state}
end
@impl GenServer
def handle_info({:message_received, parsed_message}, state) do
# ...
{:noreply, state}
end
end
erlang에서 제공하는 은밀한 이름 등록과 달리 기본적으로 DI 용기를 충당하는 프로세스 등록표를 사용할 수 있습니다. 임의의 id를 통해 의존항 (프로세스pid) 을 등록하고 찾을 수 있는 방법입니다.이러한 작업은 다음과 같습니다.
프로세스가 시작되면 자동으로 등록됩니다.다른 프로세스가 pid를 필요로 할 때, DI 등록표에 필요한 의존 관계를 요청합니다.
그러나 문제는 여전히 존재한다.이것은 닭과 알의 딜레마입니다. 등록표를 사용하려면 순서대로 pid나 이름을 알아야 합니다.
이 해결 방안은 간단하고 흔하지만 사용자의 하드코딩 프로세스 이름 때문에 감시 트리를 여러 번 실행하는 것을 막습니다.
접두어가 있는 프로세스 이름 등록
감독 트리를 밀봉하기 위해서, 모든 프로세스의 접두사로 공공 키를 사용하고, 이를 감독의 초기 매개 변수로 사용할 수 있습니다.
defmodule PipelineSupervisor do
use Supervisor
@impl Supervisor
def init(%{name_prefix: prefix}) do
consumer_name = Module.concat([__MODULE__, prefix, Consumer])
children = [
{SomeConsumer, name: consumer_name}
{SomeWorker, %{consumer: consumer_name}},
]
Supervisor.init(children, strategy: :rest_for_one)
end
end
작업을 완료했지만 프로세스 이름 공간을 어지럽히고 어색해 보일 수 있는 API를 만들었습니다. ("왜 내가 접두사를 제공해야 합니까?")스텔스 등록표는 관리자를 사용합니다.아동의 기능은 무엇입니까
만약 우리가 주관자를 더욱 세심하게 주목한다면, 우리는 어떤 주관자 자체가 기본적으로 등록 센터라는 것을 알 수 있다.모든 프로세스의 PID는 물론 모든 프로세스에 고유한 로컬 ID를 사용하여 감독자가 사용할 수 있습니다.
불행하게도 감독관은 단일한 용도로 구축된 블록이기 때문에 아이들을 찾기 위한 편리한 API가 부족하다.그러나, 우리는 여전히
Supervisor.which_children
함수를 사용하여 모니터를 레지스터로 사용할 수 있다.defmodule PipelineSupervisor do
use Supervisor
@impl Supervisor
def init([]) do
supervisor_pid = self()
children = [
SomeConsumer,
{SomeWorker, %{parent_supervisor: supervisor_pid}},
]
Supervisor.init(children, strategy: :rest_for_one)
end
def child_pid!(supervisor_pid, child_id) do
spec = Supervisor.which_children(supervisor_pid)
case List.keyfind(spec, child_id, 0) do
{_id, pid, _type, _modules} when is_pid(pid) ->
pid
nil ->
raise "no started child with id:#{id} in supervisor spec:#{inspect(spec)}"
end
end
end
defmodule SomeWorker do
use GenServer
@impl GenServer
def init(%{supervisor_pid: supervisor_pid} = options) do
{:ok, options}
end
defp do_process_data_chunk(data_chunk, state) do
# ...
consumer_pid = PipelineSupervisor.child_pid!(state.supervisor_pid, SomeConsumer)
Process.send(consumer_pid, {:message_received, parsed_message})
# ...
end
end
end
defmodule SomeConsumer do
use GenServer
def start_link([]) do
GenServer.start_link(__MODULE__, [])
end
end
어떤 경우, 모든 메시지에 대한 호출 담당자는 성능 병목을 초래할 수 있다.child_pid!
함수에서 Worker.init
을 한 번 호출하고 GenServer 상태에서 Consumer
pid를 저장하여 이 문제를 해결할 수 있습니다.defmodule SomeWorker do
use GenServer
@impl GenServer
def init(%{supervisor_pid: supervisor_pid} = options) do
consumer_pid = PipelineSupervisor.child_pid!(state.supervisor_pid, SomeConsumer)
{:ok, %{consumer_pid: consumer_pid}}
end
defp do_process_data_chunk(data_chunk, state) do
# ...
Process.send(state.consumer_pid, {:message_received, parsed_message})
# ...
end
end
end
그러나 프로세스 PID를 저장할 때, Consumer
이 붕괴되면 어떤 일이 일어날지 항상 조심해야 한다.우리의 경우 rest_for_one
주관 전략 때문에 모든 것이 좋아질 것이다.만약 Consumer
이 붕괴된다면, Worker
에 저장된pid는 효력을 상실할 것입니다.그러나 Google 주관자는rest for one 정책을 설정했습니다. Consumer
은 하위 목록에서 Worker
보다 높습니다.이에 따라 Consumer
이 붕괴되면 Worker
도 다시 시작하고 새로운 Consumer
의pid를 발견할 수 있다.임시 의존항 시작
또한, 우리는 주관자가
Supervisor.start_child
함수에서 이미 시작된 프로세스의pid를 되돌려준다는 사실을 이용할 수 있다.defmodule PipelineSupervisor do
use Supervisor
@impl Supervisor
def init([]) do
supervisor_pid = self()
children = [
{SomeWorker, %{parent_supervisor: supervisor_pid}},
]
Supervisor.init(children, strategy: :rest_for_one)
end
def start_consumer do
Supervisor.start_child(SomeConsumer)
end
end
defmodule SomeWorker do
use GenServer
@impl GenServer
def init(%{supervisor_pid: supervisor_pid} = options) do
{:ok, options, {:continue, :start_worker}}
end
def handle_continue(:start_worker, state) do
{:ok, consumer_pid} = PipelineSupervisor.start_consumer()
{:noreply, Map.put(state, :consumer_pid, consumer_pid)}
end
defp do_process_data_chunk(data_chunk, state) do
# ...
Process.send(state.consumer_pid, {:message_received, parsed_message})
# ...
end
end
end
defmodule SomeConsumer do
use GenServer
def start_link([]) do
GenServer.start_link(__MODULE__, [])
end
end
우리는 반드시 :handle_continue
리셋을 사용하여 주관자가 잠기지 않도록 해야 한다.그렇지 않으면, start_child
호출에 회답할 수 없습니다. 왜냐하면 init
함수가 돌아오기를 기다리기 때문입니다.이런 DI 방법은 매우 복잡해서 단지 자신의 이익을 위해서만 가치가 없을 수도 있다.그러나 더 복잡한 장면에서
Worker
은 Consumer
에 메시지를 보내야 할 뿐만 아니라 생존 기간(정지 또는 재시작)도 관리해야 하기 때문에 더욱 의미가 있다.결론
내가 보여준 몇 가지 옵션은 복잡해 보일 수도 있고 필요없을 수도 있다.많은 상황에서 이것은 완전히 정확하다.그러나 OTP를 사용하는 것은 GenServer뿐만 아니라 간혹 한두 명의 맞춤형 감독관도 쓴다는 것이 제 주장입니다.OTP는 우리에게 매우 기본적이지만 매우 스마트한 기본 요소 조합을 제공했고 우리는 그것을 사용하여 더욱 높은 수준의 사용 모델을 형성할 수 있다.지역 사회로서 우리의 임무는 이러한 모델을 발견하고 쉽게 사용할 수 있도록 하는 것이다.
관리자 및 사용 모델에 대한 더 많은 정보를 얻으려면 이 우수한 블로그 글 Using Supervisors to Organize Your Elixir Application을 보십시오
작성자 정보
Miroslav Malkin, Erlang/Elixir 전문가, 전화 FunBox입니다.저도 CogitoΣ의 공동 창시자입니다. 우리는 여기서 코드와 과정의 질을 조사하고 제품 성공에 미친 영향을 평가합니다.
Reference
이 문제에 관하여(OTP 관리자에서 종속성 주입 및 검색), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/miros/injecting-and-discovering-dependencies-in-otp-supervisors-352m텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)