什么是Erlang收集/汇总传入数据的方式?

时间:2018-01-13 10:19:09

标签: http erlang

尝试学习一点Erlang我很快就会遇到存储" global"数据。我明白有可变的"变量似乎并不完全是Erlang-ish方法。

计算诸如从控制台读取的数字的移动平均值之类的简单示例相对容易用尾递归和每次下一次迭代传递变量:

-module(movavg).
-export([start/0]).

start()->
    runForever(0.0).

runForever(Avg)->
    {ok, [X]} = io:fread("", "~f"),
    Avg2 = Avg - Avg / 5 + X / 5,
    io:format("avg=~p~n", [Avg2]),
    runForever(Avg2).

但是,随着我更多的工业(特别是微服务)背景,我现在想知道这应该如何在"异步"风格,通常我们将应用程序构建为http服务器并接受请求。

我已经找到了使用Erlang启动小型http服务器的示例,看起来非常不错,就像这里提到的那样:How to write a simple webserver in Erlang?

但我不确定如何处理请求之间的数据。例如。如果我只是想要将数字与请求或类似的东西相加......

我认为一种方法是使用ets表,但不确定它是否正确。当然,这不是"不可变的"存储......:)

你能告诉我吗?

3 个答案:

答案 0 :(得分:2)

您可以使用命名的gen_server来保存包含共享数据的状态。 然后你可以使用gen_server:call来修改gen_server中的状态。

答案 1 :(得分:2)

您可以使用ets(内存缓存),dets(磁盘缓存)或mnesia(基于ets和dets的关系数据库)。

如果您想使用ets或dets,请不要忘记将其与gen_fsmgen_statemgen_server相关联,然后设置api进行查询。你可以这样做:

-behaviour(gen_server).
-export([init/1]).
-export([handle_call/3]).

init(_) ->
  Ets = ets:new(?MODULE, []),
  {ok, Ets}.

% forward request to your ets table
% lookup for a key:
handle_call({lookup, Data}, From, Ets) ->
  Response = ets:lookup(Data, Ets),
  {reply, Response, Ets};
% add new data:
handle_call({insert, Data}, From, Ets) ->
  Response = ets:insert(Data, Ets),
  {reply, Response, Ets}.

% Rest of you code...

如果ETS或DETS不符合您的需求,您可以根据可用的数据结构创建自己的共享数据结构(dictorddictgb_sets,{{ 3}},gb_treesqueuedigraphsets ...),围绕标准行为。基于带有gen_server行为的dict数据结构的示例:

-module(gen_dict).
-behaviour(gen_server).
-export([start/0, start/1]).
-export([start_link/0, start_link/1]).
-export([init/1, terminate/2, code_change/3]).
-export([handle_call/3, handle_cast/2, handle_info/2]).
-export([add/2, add/3]).
-export([delete/1, delete/2]).
-export([to_list/0, to_list/1]).

% start without linking new process
start() -> 
  start([]).
start(Args) -> 
  gen_server:start({local, ?MODULE}, ?MODULE, Args, []).

% start with link on new process
start_link() -> 
  start_link([]).
start_link(Args) -> 
  gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []).

% return by default a dict as state
init(_Args) -> {ok, dict:new()}.
code_change(_,_,_) -> ok.
terminate(_,_) -> ok.

% call request, used if we want to extract data from
% current state
handle_call(size, _From, State) -> 
  {reply, dict:size(State), State};
handle_call(list, _From, State) -> 
  {reply, dict:to_list(State), State};
handle_call({find, Pattern}, _From, State) -> 
  {reply, dict:find(Pattern, State)};
handle_call({map, Fun}, _From, State) -> 
  {reply, dict:map(Fun, State), State};
handle_call({fold, Fun, Acc}, _From, State) -> 
  {reply, dict:fold(Fun, Acc, State), State};
handle_call(_Request, _From, State) -> 
  {reply, bad_call, State}.

% cast request, used if we don't want return
% from our state
handle_cast({add, Key, Value}, State) -> 
  {noreply, dict:append(Key, Value, State)};
handle_cast({update, Key, Fun}, State) -> 
  {noreply, dict:update(Key, Fun, State)};
handle_cast({delete, Key}, State) -> 
  {noreply, dict:erase(Key, State)};
handle_cast({merge, Fun, Dict}, State) -> 
  {noreply, dict:merge(Fun, Dict, State)};
handle_cast(_Request, State) -> 
  {noreply, State}.

% info request, currently do nothing
handle_info(_Request, State) -> 
  {noreply, State}.

% API
% add a new item based on key/value
-spec add(term(), term()) -> ok.
add(Key, Value) -> 
  add(?MODULE, Key, Value).
-spec add(pid()|atom(), term(), term()) -> ok.
add(Server, Key, Value) -> 
  gen_server:cast(Server, {add, Key, Value}).

% delete a key
-spec delete(term()) -> ok.
delete(Key) -> 
  delete(?MODULE, Key).
-spec delete(pid()|atom(), term()) -> ok.
delete(Server, Key) -> 
  gen_server:cast(Server, {delete, Key}).

% extract state as list
-spec to_list() -> list().
to_list() -> 
  to_list(?MODULE).
-spec to_list(pid()|atom()) -> list().
to_list(Server) -> 
  gen_server:call(Server, list).

您可以这样调用此代码:

% compile our code
c(gen_dict).
% start your process
{ok, Process} = gen_dict:start().
% add a new value
gen_dict:add(key, value).
% add another value
gen_dict:add(key2, value2).
% extract as list
List = gen_dict:list().

如果你对Erlang的概念和行为有点熟悉,你可以做很多有趣的事情,比如只允许一些进程共享一个特定的结构,或者只用一些精心设计的进程将一个结构转换成另一个结构。

答案 2 :(得分:0)

就像Tianpo说的那样,你可以使用一个进程来存储其状态的数据,但是从我读过的内容来看,ETS表的工作速度要快得多。

如果您需要事务类型锁定,请考虑通过单个进程包装对ETS表的所有访问,或者使用Mnesia或进程状态,因为ETS在该区域中没有太多。