在 Elixir 中存储状态

Storing state in Elixir

最近我解决了一个涉及更新大量键值的问题。

当然,我考虑过使用 Map,并进行 Map.put/3 等操作。

然而,考虑到 Elixir 中数据结构的不可变特性,这似乎还不够:

iex> m = Map.put(%{}, :a, 1)
%{a: 1}
iex> Map.put(m, :b, 2)
%{a: 1, b: 2}
iex> m
%{a: 1}

然后我通过在 GenServer 中保存 Map 的状态并使用 handle_cast/3 调用更新它来解决问题。

一般来说,这是正确的方法吗,还是这里太多了?

你的第一个方法是对的,你只是错了一点。

您应该在更新地图时重新绑定变量,如下所示:

iex> m = Map.put(%{}, :a, 1)
%{a: 1}
iex> m = Map.put(m, :b, 2)
%{a: 1, b: 2}
iex> m
%{a: 1, b: 2}

但你必须明白它不会改变变量,它会创建一个新映射并将其重新绑定到同一个变量。

现在,这种方法是最简单的一种,您必须将此映射传递给使用它的每个函数。作为替代方案,您可以考虑使用 Agent 模块。关于它是什么以及它的用途的所有信息都可以在它的文档中找到。

I then solved the problem by holding the state of the Map in a GenServer [...] Generally, is this the right approach, or was this too much here?

这在很大程度上取决于您的目标。 存储状态的方法有很多种。重新绑定变量,例如:

m = Map.put(%{}, :a, 1)
#⇒ %{a: 1}
m = Map.put(m, :b, 2)
#⇒ %{a: 1, b: 2}

存储任何东西。它将局部变量 m 绑定到 RHO,一旦控制流离开作用域,这个变量就会被垃圾回收。无论您是否需要在单个范围内使用上述地图,GenServer(和其他状态持有者)都是大材小用。


OTOH,如果您需要长时间存储状态并在不同范围之间共享(例如,在不同进程之间),GenServer 是实现此目的的最简单方法。在 Elixir 中,我们有 Agent 模块来减少用作简单内存存储的 GenServer 的样板文件,但我的建议是始终使用 GenServer:迟早 Agent 对您的目的来说会变得太紧。

另外,可以使用 ets 模块来保存内存中的键值存储,在进程之间共享。

dets 是一种存储状态 进程重新启动的方法。

最后,mnesia 是一种 OTP 本机方法,用于在重启和不同节点之间共享状态 (在分布式环境中。)