《Erlang/OTP并发编程实战》读书笔记- 第八章

第八章 分布式Erlang/OTP简介

8.1 Erlang分布式基础

以Erlang的两个基本特性为基础:

  • 复制式进程通信
  • 位置透明性

8.1.1 复制式进程间通信

        在解决两段并发执行的代码段时间的通信问题时,最常用的模式就是让这两段代码共享某块内存,前提它们都在同一台机器上运行。当需要每段代码都能运行在独立的机器上时,就必须换用完全不同的通信方式,很大一部分代码要被迫重写。
        要想在通信透明化的同时构建出容错的系统,要想让一台机器不至于因为相邻机器的崩溃或机器间的网络故障而宕机,就必须抛弃共享。

        Erlang的进程间通信采用的是严格的异步消息传递,接收方收到数据时实际上获取了数据的一份独立的副本;
        在Erlang中没有共享,只有消息传递,因此分布式还是单机本质上没有什么区别。大部分代码完全不关心进程最终在何处运行。但是涉足网络就不得不考虑路由过程中的消息延迟以及网络本身的故障。发送方通常无法辨别接收方到底是崩溃了还是因自身的bug而未给出应答。出于健壮性考虑,发送方应该对这些故障有所准备。

8.1.2 位置透明性

        发送方向在同一台机器上和不同机器上的接收方发送同一条消息的语法是相同的。!(发送)运算符具有位置透明的属性——接收方在哪台机器上并不重要,指引消息走向的信息统统都隐含在进程标识符中了。Erlang会确保进程标识符在多机网络上的唯一性。

8.2 节点与集群

        节点:被配置成按分布式模式运行的Erlang VM就叫作节点。每个节点都有一个节点名,其他的节点可以通过这个名字来找到该节点并与之通信。
        集群:一旦两个或两个以上的Erlang节点能够互相感知,我们就说它们形成了一个集群。

8.2.1 节点的启动

        erl 加上命令行参数-name或-sname,就可以以分布式模式启动Erlang节点.第一种适用于配有DNS的普通网络环境,需要给出节点的完全限定域名;第二种适用于完全限定域名不可用的情况,用短节点名;只有所有节点同处于一个子网,才可以使用短节点名.
        长短节点名不可以混用:采用短节点名和长节点名的节点所处的通信模式是不同的,它们之间无法形成集群;只有采用相同模式的节点才能互联

erl -name simple_cache
erl -sname simple_cache

 当Erlang VM以节点形态运行时,shell提示符会包含节点名:

 可以看出它用的是长节点名,启动参数为-name simple_cache;换用-sname simple_cache启动shell,节点名中主机部分带句点的内容就都没了

 8.2.2 节点的互联

        Erlang集群由两个以上的节点组成。就数量而言,在同一集群内启动几十个节点没什么问题,但是跑上几百个就比较悬了。其原因在于维系机器之间的联络是需要一定的通信开销的,而Erlang集群又是一个全联通网,这样一来这部分的开销会随着节点数的增加按平方规模增长。
        隐形节点:借助一些特殊的节点,我们可以将多个集群合并成更大的、非全联通的集群。这类节点经过特殊的配置,不会对外传播其他节点的信息,它们甚至可以对其他节点隐身,以便对集群进行非入侵式监控。
        一个节点不会主动搭理其他节点,你必须给它们一个呼朋唤友的理由;然而一旦探测到了别的节点,它就会持续追踪它们并与之交换已经和自己建立连接的其他节点的信息,从而促成全联通网络的形成。
只是建立连接的话,最简单的方法就是采用标准库函数net_adm:ping/1

通信成功的话会返回原子pong,否则返回原子pang。
nodes()检查节点间的互联情况.

8.2.3 Erlang节点如何定位其他节点并与之建立通信

        检查系统中运行的进程里有没有一个 叫做EPMD的进程:

ps ax |   grep -i epmd

        EPMD代表Erlang端口映射守护进程,每启动一个节点,它都会检查本地机器上是否运行着EPMD,如果没有,节点就会自行启动EPMD。EPMD会追踪在本地机器上运行的每个节点,并记录分配 给它们的端口。当一台机器上的Erlang节点试图与某远程节点通信时。本地EPMD就会联络远程机器上的EPMD(默认使用TCP/IP,端口号为4369),询问在远程机器上有没有叫相应名字的节点。如果有就会回复一个端口号,通过该端口便可直接与远程节点通信。
        更为高级的节点探测机制:现有的系统允许你通过网络多播,广播等更高明的手段去搜寻和链接Erlang节点。在EC2等就可以随时按需增删服务器的云端环境下允许Erlang时,可能用得上这些机制。
        Erlang默认的分布式模型基于集群中的所有节点都运行在一个受信网络内的假设。如果这个假设不成立,或者其中的某些机器需要与外界通信,那么就应该直接在TCP之上配合恰当的应用层协议来实现非受信网络上的通信,此外,还可以利用SSL、SSH或IPsec等技术建立加密隧道,甚至直接将Erlang的分布式通信层架设在SSL等传输协议上。(详情见Erlang/OTP SSL库和ERTS用户手册)

8.2.4 magic cookie安全系统

        成功启动过节点后,可以看到用户主目录下有一个名为.erlang.cookie的文件,打开后可以看到一个长长的字符串,它就是Erlang自动生成的cookie,在shell中可以用命令检查当前Erlang节点的cookie:

auth:get_cookie().

返回的字符串应该与.erlang.cookie文件中看到的相同。Erlang节点只有在知道其他节点的magic cookie的情况下才能与它们通信。节点在启动时会尝试读取.erlang.cookie文件,如果文件存在,就会那其中的字符串作为自己的magic cookie。如果找不到,节点会新建一个cookie文件写入一个随机字符串。
        默认情况下,每个节点都会假定所有与自己打交道的节点都拥有和自己一样的cookie。当在单台机器上启用多个节点时,所有节点都共享一个cookie文件,所以它们之间可以互相通信。要让两个运行在不同机器上的节点相互通信,最简单的办法就是把其中一台上随机生成的cookie文件复制到另一台机器上。此外除文件的所有者以外的其他用户只拥有该文件的读权限。
        也可以借助内置函数set_cookie(Node, Cookie)通过编程手段来设置cookie。采用这种方式,节点可以用不的cookie与不同的节点通信。但在实践中整个系统往往会共用同一个cookie。

8.2.5 互联节点间的消息传递

        和节点间的消息传递类似,用!和receive; 不过在用!发消息时,目标进程要用{进程注册名, Node}表示;如果是pid的话就不用再加节点消息。

8.2.6 使用远程shell

        启动一个普通的Erlang shell,实际上就是启动了一个可以与终端窗口的输入输出流进行对话的Erlang进程。此间的通信也是借由消息传递实现的,shell进程不关心与自己相连的终端是否和自己处在同一个节点。因此,完全可以在远程节点上启动一个shell进程,令它与本地节点上的终端相连,然后在其中完成各种操作。
        在shell中键入Ctrl-G进入提示符,h或?查看帮助文本:

 实现在a节点上启动一个在b节点上运行的任务,节点间无须建立连接,在a上输入

r 命令以单个节点名为参数,在该节点名对应的节点上启动一个远程shell任务,其过程与用s命令启动一个新的本地任务类似;如果节点名中含有句号,必须用上单引号。用j命令检查当前正在运行的任务:
 
任务1就是原有的本地shell,任务2:结果显示该任务运行在节点b上;*标记指示2号任务为默认任务,因此直接输入c就行.

退出远程shell时千万要小心:使用完远程shell打算退出是,千万不能使用q(),这个命令是init:stop()的简写,用于关闭执行该命令的节点;也就是远程节点. 要安全退出,要使用Ctrl-G和Ctrl-C(或Ctrl-Break),这两个组合键只对本地节点有效.可以在不影响远程节点的情况下关闭本地节点。

8.3 资源探测攻略

        使用资源探测机制,让服务的供应方和需求方无须预先知晓系统的布局便可以互相定位。集群中的每个节点都在本地运行一个该应用的实例。每个实例不断地探测和缓存集群中的各种有效资源。特点:
无单点——这是一套点对点的系统;
无硬编码的网络拓扑——可以随处添加资源
易于伸缩——可以随时加入更多资源
可以在单个节点上运行多个服务
易于升级

8.3.1 术语

1.资源
        资源是某个特定的、具体的、可以直接使用的东西,比如一个fun函数或是一段二进制数据,也可以是用于指代某个具体资源的引用,比如pid、文件 句柄、ETS表句柄等。
2.资源类型
        资源类型用于标识特定种类的资源。
3.资源元组
        资源元组是由资源类型和资源共同构成的二元组。其中资源类型指明了资源的种类和访问方式。

8.3.2 算法

        假设启动了两个互相相连的节点a和b(且二者已经实现资源信息的同步),现在集群 中又加入节点c;需要解决c与其他节点之间的资源信息同步问题。假设a和b各持有一些x类型和y类型的本地资源(用x@a来代指),同时a和b还需要z类型的本地资源,c持有一个z类型的本地资源,需要一个或多个x类型的资源,但不关注y类型的资源.
        为了与其他节点保持同步,c上的资源探测服务器会向a和b发送消息,告诉它们自己有的资源.节点a和b上的资源探测器服务器收到这些消息后会缓存契合自己需求的资源z@c; 随后它们也会把自己的本地资源状况发给c,c会缓存x类型的资源

8.3.3 实现资源探测应用
 

-module(resource_discovery).

-behaviour(gen_server).

-export([
         start_link/0,
         add_target_resource_type/1,
         add_local_resource/2,
         fetch_resources/1,
         trade_resources/0
        ]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-define(SERVER, ?MODULE).

-record(state, {target_resource_types,
                local_resource_tuples,
                found_resource_tuples}).

%% API

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

add_target_resource_type(Type) ->
    gen_server:cast(?SERVER, {add_target_resource_type, Type}).

add_local_resource(Type, Resource) ->
    gen_server:cast(?SERVER, {add_local_resource, {Type, Resource}}).

fetch_resources(Type) ->
    gen_server:call(?SERVER, {fetch_resources, Type}).

trade_resources() ->
    gen_server:cast(?SERVER, trade_resources).

%% Callbacks

init([]) ->
    {ok, #state{target_resource_types = [],
                local_resource_tuples = dict:new(),
                found_resource_tuples = dict:new()}}.

handle_call({fetch_resources, Type}, _From, State) ->
    {reply, dict:find(Type, State#state.found_resource_tuples), State}.

handle_cast({add_target_resource_type, Type}, State) ->
    TargetTypes = State#state.target_resource_types,
    NewTargetTypes = [Type | lists:delete(Type, TargetTypes)],
    {noreply, State#state{target_resource_types = NewTargetTypes}};
handle_cast({add_local_resource, {Type, Resource}}, State) ->
    ResourceTuples = State#state.local_resource_tuples,
    NewResourceTuples = add_resource(Type, Resource, ResourceTuples),
    {noreply, State#state{local_resource_tuples = NewResourceTuples}};
handle_cast(trade_resources, State) ->
    ResourceTuples = State#state.local_resource_tuples,
    AllNodes = [node() | nodes()],
    lists:foreach(
        fun(Node) ->
            gen_server:cast({?SERVER, Node},
                            {trade_resources, {node(), ResourceTuples}})
        end,
        AllNodes),
    {noreply, State};
handle_cast({trade_resources, {ReplyTo, Remotes}},
           #state{local_resource_tuples = Locals,
		  target_resource_types = TargetTypes,
		  found_resource_tuples = OldFound} = State) ->
    FilteredRemotes = resources_for_types(TargetTypes, Remotes),
    NewFound = add_resources(FilteredRemotes, OldFound),
    case ReplyTo of
        noreply ->
            ok;
        _ ->
            gen_server:cast({?SERVER, ReplyTo},
                            {trade_resources, {noreply, Locals}})
    end,
    {noreply, State#state{found_resource_tuples = NewFound}}.

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

terminate(_Reason, _State) ->
    ok.

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


%% Utilities

add_resources([{Type, Identifier}|T], Dict) ->
    add_resources(T, add_resource(Type, Identifier, Dict));
add_resources([], Dict) ->
    Dict.

add_resource(Type, Resource, Dict) ->
    case dict:find(Type, Dict) of
        {ok, ResourceList} ->
            NewList = [Resource | lists:delete(Resource, ResourceList)],
            dict:store(Type, NewList, Dict);
        error ->
            dict:store(Type, [Resource], Dict)
    end.

resources_for_types(Types, ResourceTuples) ->
    Fun =
        fun(Type, Acc) ->
            case dict:find(Type, ResourceTuples) of
                {ok, List} ->
                    [{Type, Resource} || Resource <- List] ++ Acc;
                error ->
                    Acc
            end
        end,
    lists:foldl(Fun, [], Types).

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值