opt_设计
- 格式:pdf
- 大小:265.63 KB
- 文档页数:27
OTP代表Open Telecom Platform。
这个名字容易让人误解,但是其实OTP比你想象的更加通用。
它是一个应用操作系统和一堆函数库,用以构建大型、容错和分布式的应用。
它最初是在瑞典的爱立信开发的,并且在爱立信用于构建容错系统。
(爱立信已经以EPL的形式放出了Erlang,EPL是从Mozilla许可协议继承来的)OTP包含一些有用的工具,比如一个完整的WEB服务器,一个FTP服务器,一个CORBA ORB,等等,他们都是用Erlang编写的。
OTP同时包含在电信技术中H248的技术实现标准、SNMP、和ASN.1到Erlang的交叉编译器。
这里先不谈这些,你可以从后面的附录C了解更多。
如果你想使用OTP来编写你的系统,你会发现OTP行为(behavior)的概念对你很有用。
一个行为包装了很多动作模式-可以理解为使用参数回调的应用程序框架。
OTP的强大之处在于容错、可量度的和代码热部署等等,他们都可以通过行为来提供。
在其他部分,回调作者无需担心容错的问题,因为OTP已经内置了。
在Java的思想中,你可以把行为看作是J2EE容器。
简单来说,行为解决了问题的非功能性部分,而回调函数解决功能性部分。
对大多数应用来说,非功能性部分基本是相似的(例如代码热部署)。
而功能性部分则各个应用不同。
在本章,我们来了解这些行为,gen_server模块更详细一些。
在我们讨论gen_server如何工作之前,我们首先启动一个简单的服务器(我们能想象的最简单的服务器),然后一步步gen_server r 的改变它,直到我们得到gen_server模块。
通过这种方法,你可以深入的了解gen_serve 的工作原理,并且了解足够多的底层细节。
这里是本章的计划:1编写一个很小的CS程序2一步步扩展程序并添加功能3放入真实的代码1通向通用服务器之路Note这将是本书中最重要的章节,所以首先阅读一遍,然后再阅读一遍,直到一百遍,确保知道了每一个细节。
我们即将开始编写我们的服务器,分别叫做server1、server2、…,每个只是与前一个略有不同。
目标是从程序中提取非功能性部分。
最后一句可能对你来说有点晕,不过别担心。
深呼吸……1.1例子1:基本服务器如下是我们的第一个尝试。
这是一个小服务器,我们可以参数化我们的回调模块: -module(server1).-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}->Responseend.loop(Name,Mod,State)->receive{From,Request}->{Response,State}=Mod:handle(Request,State),From!{Name,Response},loop(Name,Mod,State)end.这是一个服务器的代码精髓,我们来写server1的回调模块,如下是命名服务器回调: -module(name_server).-export([init/0,add/2,whereis/1,handle/2]).-import(server1,[rpc/2]).%%client routinesadd(Name,Place)->rpc(name_server,{add,Name,Place}).whereis(Name)->rpc(name_server,{whereis,Name}).%%callback routinesinit()->dict:new().handle({add,Name,Place},Dict)->{ok,dict:store(Name,Place,Dict)};handle({whereis,Name},Dict)->{dict:find(Name,Dict),Dict}.这段代码实际完成了两项任务。
他作为一个回调模块被服务器框架代码所调用,同时它包含可以被客户端调用的接口程序。
OTP的惯例是把相同功能的代码放在一个模块中。
我们想让它工作,只需要:1>server1:start(name_server,name_server).true2>name_server:add(joe,"at home").ok3>name_server:whereis(joe).{ok,"at home"}现在停止思考。
回调没有并发,没有spawn,没有send,没有receive,没有register。
他是纯的序列代码-这意味着什么呢?这意味着我们可以可以在并不理解底层并发模块的情况下泄CS模型。
这是一个简单服务器的通用模式。
一旦你理解的简单的结构,就可以让你更快的理解了。
1.2例子2:包含事务处理的服务器这里的服务器在遇到客户端错误查询时会挂掉:-module(server2).-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}->Responseend.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)endend.log_the_error(Name,Request,Why)->io:format("Server~p request~p~n""caused exception~p~n",[Name,Request,Why]).这个例子中使用了事务语义,在发生异常时,它使用原来的值继续循环。
但是如果处理函数执行成功,则会以NewState值来继续下一次循环。
为什么要保留原来的值呢?当处理函数失败时,错误的消息来自于客户端的错误。
而客户端无法处理,因为错误的请求已经让处理函数失败了。
但是其他客户端希望服务器仍然可用。
此外,服务器的状态在发生错误时不会改变。
注意回调模块与server1的非常像。
通过改变服务器和保持回调模块不变,我们可以改变回调模块中的非功能行为。
Note最后一个语句并不总是true。
我们在从server1到server2时还是要修改一点代码,就是把-import语句从server1改为server2。
除此之外,就没有改变了。
1.3例子3:含有代码热交换的服务器现在我们加上代码热交换:-module(server3).-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}->Responseend.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.他如何工作?我们首先交换代码消息给服务器,然后他会改变回调模块到消息中包含的新的模块。
我们可以通过启动server3来演示,然后动态交换毁掉模块。
我们不能使用name_server作为回调模块,因为我们把服务器名硬编码到服务器中了。
所以,我们需要复制一份,叫做name_server1,我们还需要这么改变代码:-module(name_server1).-export([init/0,add/2,whereis/1,handle/2]).-import(server3,[rpc/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).true2>name_server:add(joe,"at home").ok3>name_server:add(helen,"at work").ok现在假设我们想要找到名字服务器中的所有名字。
而没有函数提供此功能,name_server1模块只提供了增加和查询名字的功能。
所以,我们以迅雷不及掩耳盗铃之势启动了编辑器并写了新的回调模块:-module(new_name_server).-export([init/0,add/2,all_names/0,delete/1,whereis/1,handle/2]).-import(server3,[rpc/2]).%%接口all_names()->rpc(name_server,allNames).add(Name,Place)->rpc(name_server,{add,Name,Place}).delete(Name)->rpc(name_server,{delete,Name}).whereis(Name)->rpc(name_server,{whereis,Name}).%%回调函数init()->dict:new().handle({add,Name,Place},Dict)->{ok,dict:store(Name,Place,Dict)}; handle(allNames,Dict)->{dict:fetch_keys(Dict),Dict};handle({delete,Name},Dict)->{ok,dict:erase(Name,Dict)};handle({whereis,Name},Dict)->{dict:find(Name,Dict),Dict}.我们编译如上模块,并且叫服务器交换回调模块:4>c(new_name_server).{ok,new_name_server}5>server3:swap_code(name_server,new_name_server).ack现在我们可以运行服务器上的函数了:6>new_name_server:all_names().[joe,helen]这次我们修改模块就是热部署的-动态代码升级,在你眼前运行的,有如魔术一般。