Erlang——OTP


OTP,应用程序操作系统,用了构造容错系统。

1.服务器进化历程

原始服务器程序

服务器的基本模式(通过回调模块定制):

-module(server1).
%%最初的服务器程序
-author("").
%%%=======================EXPORT=======================
-export([start/2, rpc/2]).
start(Name, Mod) ->
	register(Name, spawn(fun() ->loop(Name, Mod, Mod:init()) end)).
rpc(Name, Request) ->
	Name ! {self(), Request},
	receive
		{Name, Response} ->Response
	end.

loop(Name, Mod, State) ->
	receive
		{From, Request} ->
			{Response, State1} = Mod:handle(Request, State),
			From ! {Name, Response},
			loop(Name, Mod, State1)
	end.

server1的回调函数:

-module(name_server).
-author("").
%%%=======================EXPORT=======================
-import(server1, [rpc/2]).
%%server1的回调程序
-export([init/0, add/2, whereis/1, handle/2]).
%%远程调用向指定的服务名发送请求
add(Name, Place) ->rpc(name_server, {add, Name, Place}).
whereis(Name)    ->rpc(name_server, {whereis, Name}).
init() ->dict:new().
handle({add, Name, Place}, Dict) ->{ok, dict:store(Name, Place, Dict)};
handle({whereis, Name}, Dict)    ->{dict:find(Name, Dict), Dict}.

他是服务器的回到程序,负责处理框架发过来的调用请求;并且定义了客户端调用的常规接口。
测试结果:

1> server1:start(name_server,name_server).
true
2> name_server:add(harry,"at home").
ok 
3> name_server:whereis(harry).
{ok,"at home"}

支持事务的服务器程序

代码演示:

-module(server2).
-author("").
%%%=======================EXPORT=======================
-export([start/2, rpc/2]).
start(Name, Mod) ->
  register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)).
rpc(Name, Request) ->
  Name ! {self(), Request},
  receive
    {Name, crash} -> exit(rpc);
    {Name, ok, Response} -> Response
  end.

loop(Name, Mod, OldState) ->
  receive
    {From, Request} ->
      try Mod:handle(Request, OldState) of
        {Response, NewState} ->
          From ! {Name, ok, Response},
          loop(Name, Mod, NewState)
      catch
        _:Why ->
          log_the_error(Name, Request, Why),
          From ! {Name, crash},
          loop(Name, Mod, OldState)
      end
  end.
log_the_error(Name, Request, Why) ->
  io:format("Server ~p request ~p ~n"
  "caused exception ~p ~n",
    [Name, Request, Why]).

在请求导致服务器程序出现异常时,会让客户端代码异常退出。
这个版本的服务器提供了一种事务机制,如果在handler函数中发生了异常,他会用之前的状态进行循环,只有当handler函数正常返回时,才会用handler函数返回的新状态进行循环。之所以要保留前一个状态,是当handler函数失效时,发起调用的客户端会收到一个让他立刻退出的消息。因为请求导致了handler函数失败,所以客户端的调用是无法成功的,但受到影响的也只有这一个客户端而已,连到同一服务器上,其他客户端不会受到任何影响。另外,当handler函数中发生错误时,服务器的状态也没有因此而发生任何改变。
相较上个版本回调模块,没有任何改动(只需要改下导入的模块名,改成了server2即可),只是改变了服务器代码本身,我们可以独立的对非功能的行为部分进行更改,功能性的部分不受影响。

支持热代码替换的服务器程序

-module(server3).
-author("").
-export([start/2, rpc/2, swap_code/2]).
start(Name, Mod) ->
  register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)).
swap_code(Name, Mod) -> rpc(Name, {swap_code, Mod}).
rpc(Name, Request) ->
  Name ! {self(), Request},
  receive
    {Name, Response} -> Response
  end.

loop(Name, Mod, OldState) ->
  receive
    {From, {swap_code, NewCallBackMod}} ->
      From ! {Name, ack},
      loop(Name, NewCallBackMod, OldState);
    {From, Request} ->
      {Response, NewState} = Mod:handle(Request, OldState),

      From ! {Name, Response},
      loop(Name, Mod, NewState)
  end.

他硬编码了服务器的名字,做一个新的name_server1

-module(name_server1).
-author("").
-import(server3, [rpc/2]).
-export([init/0, add/2, whereis/1, handle/2]).
add(Name, Place) ->rpc(name_server, {add, Name, Place}).
whereis(Name)    ->rpc(name_server, {whereis, Name}).
init() ->dict:new().
handle({add, Name, Place}, Dict) ->{ok, dict:store(Name, Place, Dict)};
handle({whereis, Name}, Dict)    ->{dict:find(Name, Dict), Dict}.

用name_server1启动server3:

1> server3:start(name_server,name_server1).
true
2> name_server:add(harry,"at home").
ok 
3> name_server:add(moka,"at work").
ok

可成功存入

同时支持事务和热代码替换的服务器程序

-module(server4).
-author("").
%%%=======================EXPORT=======================
-export([start/2, rpc/2, swap_code/2]).
start(Name, Mod) ->
  register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)).
swap_code(Name, Mod) -> rpc(Name, {swap_code, Mod}).
rpc(Name, Request) ->
  Name ! {self(), Request},
  receive
    {Name, crash} -> exit(rpc);
    {Name, Response} -> Response
  end.

loop(Name, Mod, OldState) ->
  receive
    {From, {swap_code, NewCallBackMod}} ->
      From ! {Name, ok, ack},
      loop(Name, NewCallBackMod, OldState);
    {From, Request} ->
      try Mod:handle(Request, OldState) of
        {Response, NewState} ->
          From ! {Name, ok, Response},
          loop(Name, Mod, NewState)
      catch
        _:Why ->
          log_the_error(Name, Request, Why),
          From ! {Name, crash},
          loop(Name, Mod, OldState)
      end
  end.
log_the_error(Name, Request, Why) ->
  io:format("Server ~p request ~p ~n"
  "caused exception ~p ~n",
    [Name, Request, Why]).

可以同时支持事务机制和热代码替换

支持热代码替换的服务器程序

不提供服务,直至接收到指令:

-module(server5).
-author("").
-export([start/0, rpc/2]).
start() ->
  spawn(fun() -> wait() end).
rpc(Pid, Q) ->
  Pid ! {self(), Q},
  receive
    {Pid, Reply} -> Reply
  end.
wait() ->
  receive
    {become, F} ->
      F()
  end.

定义服务器函数:

-module(my_fac_server).
-author("").
%%%=======================EXPORT=======================
-export([loop/0]).
loop() ->
  receive
    {From, {fac, N}} ->
      From ! {self(), fac(N)},
      loop();
    {become,Something} ->
      Something()
  end.
fac(0)->1;
fac(N)->N*fac(N-1).

发送可实现阶乘运算:

1> c(my_fac_server).
{ok,my_fac_server}
2> Pid=server5:start().
<0.84.0>
3> Pid!{become,fun my_fac_server:loop/0}.
{become,fun my_fac_server:loop/0}
4> server5:rpc(Pid,{fac,30}).    
265252859812191058636308480000000

2.gen_server

模块

以一个简单的支付系统为例(my_bank)

-module(my_bank).
-author("wangjiaqi").
-export([start/0,stop/0,new_account/1,deposit/2,withdraw/2]).
start()-> %  打开银行
  gen_server:start_link({local,?MODULE},?MODULE,[],[]).
stop()-> %  关闭银行
  gen_server:call(?MODULE,stop).
new_account(Who)-> %  开一个新账户
  gen_server:call(?MODULE, {new,Who}).
deposit(Who,Amount)-> %  存钱
  gen_server:call(?MODULE, {add,Who,Amount}).
withdraw(Who,Amount)-> %  取钱(判断余额是否够用)
  gen_server:call(?MODULE, {remove,Who,Amount}).

?MODULE宏展开时会对应模块名称即my_bank
gen_server:start_link启动一个本地服务器
gen_server:call函数用于发起对服务器的远程调用

回调函数

-export([start_link/0,init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,code_change/3]).
start_link()->gen_server:start_link({local,?MODULE},?MODULE,[],[]).
init([])->{ok,ets:new(?MODULE,[])}.
handle_call({new,Who},_From,Tab)->
  Reply=case ets:lookup(Tab,Who) of
          []->
            ets:insert(Tab, {Who,0}),
            {welcome,Who};
          [_]->
            {Who,you_already_are_a_customer}
        end,
  {reply,Reply,Tab};
handle_call({add,Who,X},_From,Tab)->
  Reply=case ets:lookup(Tab,Who) of
          []->
            not_a_customer;
          [{Who,Balance}]->
            NewBalance=Balance+X,
            ets:insert(Tab, {Who,NewBalance}),
            {thanks,Who,your_balance_is,NewBalance}
        end,
  {reply,Reply,Tab};
handle_call({remove,Who,X},_From,Tab)->
  Reply=case ets:lookup(Tab,Who) of
          []->
            not_a_customer;
          [{Who,Balance}] when X=<Balance->
            NewBalance=Balance-X,
            ets:insert(Tab, {Who,NewBalance}),
            {thanks,Who,your_balance_is,NewBalance};
          [{Who,Balance}] ->
            {sorry,Who,your_only_have,Balance,in_the_bank}
        end,
  {reply,Reply,Tab};
handle_call(stop,_From,Tab)->
  {stop,normal,stopped,Tab}.
handle_cast(_Msg,State)->{noreply,State}.
handle_info(_Info,State)->{noreply,State}.
terminate(_Request,_State)->ok.
code_change(_OldVsn,State,Extra)->{ok,State}.

通过gen_server:start_link调用启动服务器程序,回调模块的第一个被调用的函数是init,他返回{ok,State}.State值会作为handle_call函数的第三个参数再次出现.
handle_call(stop,_From,Tab)终止服务器程序,返回{stop,normal,stopped,Tab},是终止服务器的方法。normal作为terminate的参数,stopped会成为my_rank:stop()的返回值。
整合代码:

-module(my_bank).
%%%=======================EXPORT=======================
-export([start/0,stop/0,new_account/1,deposit/2,withdraw/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
  terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%%=======================INCLUDE======================
%%%=======================RECORD=======================
%%%=======================DEFINE=======================
%%%=======================TYPE=========================
%%%=================EXPORTED FUNCTIONS=================
%% ----------------------------------------------------
%% Description:
%% ----------------------------------------------------
start()-> %  打开银行
  gen_server:start_link({local,?MODULE},?MODULE,[],[]).
stop()-> %  关闭银行
  gen_server:call(?MODULE,stop).
new_account(Who)-> %  开一个新账户
  gen_server:call(?MODULE, {new,Who}).
deposit(Who,Amount)-> %  存钱
  gen_server:call(?MODULE, {add,Who,Amount}).
withdraw(Who,Amount)-> %  取钱(判断余额是否够用)
  gen_server:call(?MODULE, {remove,Who,Amount}).
start_link() ->
  gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

init([])->{ok,ets:new(?MODULE,[])}.
handle_call({new,Who},_From,Tab)->
  Reply=case ets:lookup(Tab,Who) of
          []->
            ets:insert(Tab, {Who,0}),
            {welcome,Who};
          [_]->
            {Who,you_already_are_a_customer}
        end,
  {reply,Reply,Tab};
handle_call({add,Who,X},_From,Tab)->
  Reply=case ets:lookup(Tab,Who) of
          []->
            not_a_customer;
          [{Who,Balance}]->
            NewBalance=Balance+X,
            ets:insert(Tab, {Who,NewBalance}),
            {thanks,Who,your_balance_is,NewBalance}
        end,
  {reply,Reply,Tab};
handle_call({remove,Who,X},_From,Tab)->
  Reply=case ets:lookup(Tab,Who) of
          []->
            not_a_customer;
          [{Who,Balance}] when X=<Balance->
            NewBalance=Balance-X,
            ets:insert(Tab, {Who,NewBalance}),
            {thanks,Who,your_balance_is,NewBalance};
          [{Who,Balance}] ->
            {sorry,Who,your_only_have,Balance,in_the_bank}
        end,
  {reply,Reply,Tab};
handle_call(stop,_From,Tab)->
  {stop,normal,stopped,Tab}.

handle_cast(_Msg, State) ->
  {noreply, State}.

handle_info(_Info, State) ->
  {noreply, State}.

terminate(_Reason, _State) ->
  ok.

code_change(_OldVsn, State, Extra) ->
  {ok, State}.

运行结果:

1> c(my_bank).                    
my_bank.erl:34: Warning: function start_link/0 is unused
my_bank.erl:81: Warning: variable 'Extra' is unused
{ok,my_bank}
2> my_bank:start().
{ok,<0.84.0>}
3> my_bank:deposit("harry",100).
not_a_customer
4> my_bank:new_account("harry").
{welcome,"harry"}
5> my_bank:deposit("harry",100).
{thanks,"harry",your_balance_is,100}
6> my_bank:deposit("harry",121).
{thanks,"harry",your_balance_is,221}
7> my_bank:withdraw("harry",10).
{thanks,"harry",your_balance_is,211}
8> my_bank:withdraw("harry",300).
{sorry,"harry",your_only_have,211,in_the_bank}

输入为非数字型会报错,所有数据丢失
改进(加输入容错):

-module(my_bank).
%% API
-export([start/0, stop/0, new_account/1, deposit/2, withdraw/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
  terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(DATA_FILE, "bank_data.txt").
-define(DATA_PATH, "./").

start() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop()  -> gen_server:call(?MODULE, stop).

new_account(Who)      -> gen_server:call(?MODULE, {new, Who}).
deposit(Who, Amount)  -> gen_server:call(?MODULE, {add, Who, Amount}).
withdraw(Who, Amount) -> gen_server:call(?MODULE, {remove, Who, Amount}).

init([]) ->
  case filelib:find_file(?DATA_FILE, ?DATA_PATH) of
    {ok, _} -> %% 文件存在,加载数据
      ets:file2tab(?DATA_PATH ++ ?DATA_FILE); %% {ok, Tab}
    {error, not_found} -> %% 文件不存在,新建文件
      {ok, ets:new(?MODULE, [named_table])}
  end.

handle_call({new, Who}, _From, Tab) ->
  Reply = case ets:lookup(Tab, Who) of
            []  ->
              ets:insert(Tab, {Who, 0}),
              {welcome};
            [_] ->
              {you_already_are_a_customer}
          end,
  {reply, Reply, Tab};

handle_call({add, Who, Amount}, _From, Tab) when Amount > 0 ->
  Reply = case ets:lookup(Tab, Who) of
            []  ->
              {not_a_customer};
            [{Who, Balance}] ->
              NewBalance = Balance + Amount,
              ets:insert(Tab, {Who, NewBalance}),
              {success, now_balance_is, NewBalance}
          end,
  {reply, Reply, Tab};
handle_call({add, _, _}, _, Tab) ->
  {reply, {error}, Tab};

handle_call({remove, Who, Amount}, _From, Tab) when Amount > 0 ->
  Reply = case ets:lookup(Tab, Who) of
            []  ->
              {not_a_customer};
            [{Who,Balance}] when Amount =< Balance ->
              NewBalance = Balance - Amount,
              ets:insert(Tab, {Who, NewBalance}),
              {success, now_balance_is, NewBalance};
            [{Who,Balance}] ->
              {error, you_only_have, Balance}
          end,
  {reply, Reply, Tab};
handle_call({remove, _, _}, _, Tab) ->
  {reply, {error}, Tab};

handle_call(stop, _From, Tab) ->
  {stop, normal, stopped, Tab}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) ->
  Result = ets:tab2file(?MODULE, ?DATA_PATH ++ ?DATA_FILE),
  io:format("~p~n", [Result]),
  ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

执行结果:

1> c(my_bank).
{ok,my_bank}
2> my_bank:start().
{ok,<0.84.0>}
3> my_bank:new_account(harry).
{welcome}
4> my_bank:deposit(harry,100).
{success,now_balance_is,100}
5> my_bank:withdraw(harry,25). 
{success,now_balance_is,75}
6> my_bank:deposit(harry,x). 
ok                                                                           
=ERROR REPORT==== 18-Sep-2023::23:33:15.983000 ===
** Generic server my_bank terminating
** Last message in was {add,harry,x}
** When Server state == my_bank
** Reason for termination ==
** {badarith,[{erlang,'+',[75,x],[]},
              {my_bank,handle_call,3,[{file,"my_bank.erl"},{line,200}]},     
              {gen_server,try_handle_call,4,
                          [{file,"gen_server.erl"},{line,661}]},
              {gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,690}]},
              {proc_lib,init_p_do_apply,3,
                        [{file,"proc_lib.erl"},{line,249}]}]}
** Client <0.77.0> stacktrace
** [{gen,do_call,4,[{file,"gen.erl"},{line,167}]},
    {gen_server,call,2,[{file,"gen_server.erl"},{line,211}]},
    {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,684}]},
    {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
    {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
    {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]

=CRASH REPORT==== 18-Sep-2023::23:33:15.983000 ===
  crasher:
    initial call: my_bank:init/1
    pid: <0.84.0>
    registered_name: my_bank
    exception error: an error occurred when evaluating an arithmetic expression
      in operator  +/2
         called as 75 + x
      in call from my_bank:handle_call/3 (my_bank.erl, line 200)
      in call from gen_server:try_handle_call/4 (gen_server.erl, line 661)
      in call from gen_server:handle_msg/6 (gen_server.erl, line 690)
    ancestors: [<0.77.0>]
    message_queue_len: 0
    messages: []
    links: [<0.77.0>]
    dictionary: []
    trap_exit: false
    status: running
    heap_size: 2586
    stack_size: 27
    reductions: 10198
  neighbours:
    neighbour:
      pid: <0.77.0>
      registered_name: []
      initial_call: {erlang,apply,2}
      current_function: {gen,do_call,4}
      ancestors: []
      message_queue_len: 0
      links: [<0.64.0>,<0.84.0>]
      trap_exit: false
      status: waiting
      heap_size: 987
      stack_size: 33
      reductions: 7427
      current_stacktrace: [{gen,do_call,4,[{file,"gen.erl"},{line,167}]},
                  {gen_server,call,2,[{file,"gen_server.erl"},{line,211}]},
                  {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,684}]},
                  {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
                  {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
                  {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]
** exception exit: badarith
     in operator  +/2
        called as 75 + x
     in call from my_bank:handle_call/3 (my_bank.erl, line 200)
     in call from gen_server:try_handle_call/4 (gen_server.erl, line 661)
     in call from gen_server:handle_msg/6 (gen_server.erl, line 690)
     in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 249)
7> my_bank:start().
{ok,<0.95.0>}
8> my_bank:deposit(harry,100).
{success,now_balance_is,175}

可见尽管输入字母会报错,但重新开启后数据仍存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值