Vinova tuyển lập trình viên Mobile & Web ở Hà Nội, lương $300-1000

Article: Sống Phân Tán, Chết Cũng Phân Tán 890

ngocdaothanh.myopenid.com 172
Updated over 2 years ago

Khi xây dựng hệ thống server phân tán chạy song song trên nhiều node, ta thường gặp bài toán có pattern sau:

  • Có 3 node, mỗi node chạy một process của cùng loại server module.
  • 3 process bắt buộc phải làm việc hợp tác với nhau thì hệ thống 3 node mới chạy đúng. Ta muốn nếu process nào đó chết thì 2 thằng còn lại phải tạm thời ngưng hoạt động, chờ khi thằng kia sống lại thì cả 3 mới hoạt động trở lại.

Giả sử hệ thống viết bằng Erlang, chúng ta hãy đề ra design để giải pattern trên.

Thiết Kế

Để hệ thống robust, ta theo tinh thần peer-to-peer không dùng master node chuyên dùng để monitor các node khác:

  • Trên mỗi node đều có sẵn danh sách tên của tất cả các node.
  • Khi server process được khởi động trên node nào đó, nó sẽ chủ động kết nối đến các node khác trong danh sách, thông báo cho server process trên các node đó là nó sống lại, rồi đợi trả lời. Nhận được trả lời từ process ở node nào thì nó đánh dấu là process trên node đó sống và monitor process đó. Nếu không nhận được, nó đánh dấu là process trên node đó chết.
  • Trong thông báo có kèm name của process gửi. Khi nhận thông báo, process nhận trả lời là nó đã nhận được, đồng thời dùng name trong thông báo để monitor process gửi.
  • Mấu chốt ở trên là thiết kế "push" chứ không "pull". Process nào sống lại thì phải chủ động push thông báo đến các process khác. Khi process này thấy process kia chết thì nó chỉ đánh dấu là process kia đã chết, chứ không liên tục pull xem process kia đã sống lại chưa.

Thực Hiện

Dưới đây là ví dụ. Sau khi dịch xong, mở console rồi chạy Eshell:

erl -sname n1 -setcookie cookie

Ở Eshell này, chạy:

updown:start_link(['n1@dhcp-10-30-255-66', 'n2@dhcp-10-30-255-66', 'n3@dhcp-10-30-255-66']).
Ups: ['n1@dhcp-10-30-255-66']
Downs: ['n2@dhcp-10-30-255-66','n3@dhcp-10-30-255-66']
updown is disabled

Mở thêm 2 console nữa để chạy thêm node n2 và n3. Chạy start_link tương tự sẽ thấy trạng thái sống chết được thể hiện trên màn hình.

-module(updown).

-compile(export_all).

-define(SERVER, ?MODULE).

% * all, ups: ordered nodes, ordered for easy comparison
% * disabled: false iff all == ups, it is here as a cache to avoid the cost of
%             comparing ups with all all the time.
-record(state, {all, ups, disabled}).

%-------------------------------------------------------------------------------

%% Starts server on current node, even if it is not included in NodeList:
%% 1. When a server process is started, it tries to tell server processes on
%%    other nodes that it is up.
%% 2. When a server process is down, server processes on other nodes know, but
%%    they do not periodically check if it is up again.
start_link(Nodes) ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, Nodes, []).

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

%-------------------------------------------------------------------------------

init(Nodes) ->
    This     = node(),
    Others   = lists:usort(Nodes -- [This]),
    All      = lists:usort(Nodes ++ [This]),  % Make sure local node is included
    Ups      = [This],
    Disabled = not (Ups == All),
    State1   = #state{all = All, ups = Ups, disabled = Disabled},

    State2 = lists:foldl(
        fun(Node, State3) ->
            case net_adm:ping(Node) of
                pang -> State3;

                pong ->
                    % Try to tell the server on the remote node that we are up
                    case catch gen_server:call({?SERVER, Node}, {serverup, This}, infinity) of
                        pong -> serverup(Node, State3);
                        _    -> State3
                    end
            end
        end,
        State1,
        Others
    ),

    print_state(State2),
    {ok, State2}.

%% Called by server processes on other nodes to notify that they are up.
handle_call({serverup, Node}, _From, State) ->
    State2 = serverup(Node, State),

    io:format("Up: ~p~n", [Node]),
    print_state(State2),
    {reply, pong, State2}.

handle_cast(print_state, _From, State) ->
    print_state(State),
    {noreply, State}.

handle_info({'DOWN', _MonitorRef, process, {?SERVER, Node}, _Info}, State) ->
    State2 = serverdown(Node, State),

    io:format("Down: ~p~n", [Node]),
    print_state(State2),
    {noreply, State2}.

%-------------------------------------------------------------------------------

%% Returns new state.
serverup(Node, State) ->
    erlang:monitor(process, {?SERVER, Node}),
    Ups      = lists:usort([Node | State#state.ups]),
    Disabled = not (Ups == State#state.all),
    State#state{ups = Ups, disabled = Disabled}.

%% Returns new state.
serverdown(Node, State) ->
    Ups      = lists:usort(State#state.ups -- [Node]),
    Disabled = not (Ups == State#state.all),
    State#state{ups = Ups, disabled = Disabled}.

%% Prints local state.
print_state(State) ->
    Ups   = State#state.ups,
    Downs = lists:usort(State#state.all -- Ups),
    io:format("Ups:   ~p~n", [Ups]),
    io:format("Downs: ~p~n", [Downs]),
    DisabledS = case State#state.disabled of
        true  -> disabled;
        false -> enabled
    end,
    io:format("~p is ~p~n", [?SERVER, DisabledS]).

Comments

You must login to be able to comment

Uploaded files

No file uploaded yet

You must login to be able to upload

Nhà tài trợ:

Mọi người đều tự do viết bài, sửa bài của người khác, và bình luận ở trang web này. Bạn muốn chủ động tạo bài mới để chia sẻ kinh nghiệm với mọi người? Xin click link ở dưới.

Create new content