代码人生的小狗窝

一行行枯燥的代码,却描绘出人生的点点滴滴

您现在的位置是:首页>_Erlang

elixir 高可用系列(3) GenEvent

发布时间:2020-01-29浏览(1133)

    elixir 高可用系列(三) GenEvent

    概述

    GenEvent 是事件处理的通用部分的抽象。
    通过 GenEvent ,我们给已有的服务 动态 的添加 事件处理。

    GenEevent 和 GenServer 的区别

    之前已经介绍了 GenServer ,GenServer 和 GenEvent 的主要区别在于:

    • GenServer 是服务器的抽象,除了封装处理 同步/异步 事件的方法之外,还封装了服务器本身的启动/停止等方法。
    • GenEvent 是事件的抽象,封装了 同步/异步 事件的处理方法,GenEvent 可以绑定到任何服务器上,从而动态的 添加 服务器的处理方法。

    基于上述的区别,GenEvent 和 GenServer 的应用场景也不同。

    • GenServer 可以帮助我们快速的创建服务,它类似于一个服务的脚手架,使用 GenServer,构建服务时,只需关注服务本身的业务即可
    • GenEvent 可以用于给现有的服务动态添加处理方法,也可以用于抽象多个服务的共通处理

    GenEevent 示例

    事件管理器

    通过 GenEvent 创建一个事件管理器,将此事件管理器添加到现有进程中,现有进程就有了处理相应事件的能力。

    简单示例如下:

    • 接收到 :hello 则返回 :world
    • 接收到 :world 则返回 :hello
    • 接收到 其他消息 则返回 "error msg"
    defmodule HelloEvent do
      use GenEvent
      def handle_event(event, parent) do
        case event do
          :hello ->
            send parent, :world
          :world ->
            send parent, :hello
          _ ->
            send parent, "error msg"
        end
        {:ok, parent}
      end
    end

    测试过程:

    # 启动一个空的事件管理器
    iex(1)> {:ok, manager} = GenEvent.start_link                     
    {:ok, #PID<0.87.0>}
    # 发送 :hello 消息
    iex(2)> GenEvent.sync_notify(manager, :hello)                    
    :ok
    # 没有任何反应,因为事件管理器中没有任何 handle 来处理消息
    iex(3)> flush                                                    
    :ok
    # 给事件管理器增加一个handle,同时将当前进程PID作为事件处理的状态
    iex(4)> GenEvent.add_handler(manager, HelloEvent, self())        
    :ok
    # 发送 :hello 消息
    iex(5)> GenEvent.sync_notify(manager, :hello)                    
    :ok
    # 事件管理器处理了 :hello 消息,并返回 :world 结果
    iex(6)> flush                                                    
    :world
    :ok
    # 发送 :world 消息
    iex(7)> GenEvent.sync_notify(manager, :world)                    
    :ok
    # 事件管理器处理了 :world 消息,并返回 :hello 结果
    iex(8)> flush                                                    
    :hello
    :ok
    # 发送 :test 消息
    iex(9)> GenEvent.sync_notify(manager, :test)                     
    :ok
    # 事件管理器对于 :hello 和 :world 以外的消息都返回 "error msg"
    iex(10)> flush                                                   
    "error msg"
    :ok

    上面测试中用的发送消息的方法都是同步方式 sync_notify ,通过异步方式 notify 发送消息也是一样的, GenEvent 的 handle_event 接收同步和异步的消息。

    事件流

    事件流就是将 GenEvent 的事件转入到流中,这样,就可以通过处理流的方式来处理事件。

    比如上面的例子,通过 GenEvent 的 stream ,可以不定义 defmodule HelloEvent 也实现上面的功能。

    上述测试过程可以改为如下:

    iex(1)> {:ok, manager} = GenEvent.start_link
    {:ok, #PID<0.59.0>}
    iex(2)> stream = GenEvent.stream(manager)
    %GenEvent.Stream{manager: #PID<0.59.0>, timeout: :infinity}
    iex(3)>
    nil
    iex(4)> spawn_link fn ->
    ...(4)>   for x <- stream do
    ...(4)>     case x do
    ...(4)>       :hello -> IO.inspect :world
    ...(4)>       :world -> IO.inspect :hello
    ...(4)>       _ -> IO.inspect "error msg"
    ...(4)>     end
    ...(4)>   end
    ...(4)> end
    #PID<0.71.0>
    iex(5)> GenEvent.sync_notify(manager, :hello)
    :world
    :ok
    iex(6)> GenEvent.sync_notify(manager, :world)
    :hello
    :ok
    iex(7)> GenEvent.sync_notify(manager, :test)
    "error msg"
    :ok

    可以看出,我们没有给 GenEvent 绑定任何的 handler,而是在 GenEvent 的事件流中对所有消息进行了处理。

    GenEvent 中事件流的特性是 erlang 中所没有的。

    总结

    除了上面用的 handle_event 和 stream, GenEvent 中还有其他的实用的 Functios 和 Callbacks

    具体参见:http://elixir-lang.org/docs/stable/elixir/GenEvent.html

    来源:http://blog.iotalabs.io/