Sống phân tán, chết cũng phân tán
Erlang
12
White

Ngoc Dao viết ngày 21/03/2016

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]).
Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Ngoc Dao

102 bài viết.
252 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
56 6
Làm thế nào để nâng cấp trang web mà không làm gián đoạn dịch vụ? Đây là câu hỏi phỏng vấn các công ty lớn thường hỏi khi bạn xin vào vị trí làm lậ...
Ngoc Dao viết gần 2 năm trước
56 6
White
32 0
Bài viết này giải thích sự khác khác nhau giữa hai ngành khoa học máy tính (computer science) và kĩ thuật phần mềm (software engineering), hi vọng ...
Ngoc Dao viết gần 2 năm trước
32 0
White
28 1
Nếu là team leader, giám đốc công ty hay tướng chỉ huy quân đội, vấn đề cơ bản bạn gặp phải là “hướng mọi người đi theo con đường bạn chỉ ra”. Thử...
Ngoc Dao viết gần 2 năm trước
28 1
Bài viết liên quan
White
3 0
Chú thích: Bài này đăng lần đầu năm 2009 để chia sẻ kinh nghiệm, sau khi tác giả viết xong thư viện closed source để bán. Hiện tại năm 2016 đã có t...
Ngoc Dao viết gần 2 năm trước
3 0
White
2 0
Trong xử lí song song, các đơn vị thực hiện nhiệm vụ xử lí thường gặp là: thread, process, core, CPU, node. Thường mỗi node ứng với một server vật ...
Ngoc Dao viết gần 2 năm trước
2 0
White
0 0
Viết lại thư viện đã có sẵn của ngôn ngữ khác ít khi nào là nhiệm vụ thú vị, nên thường có chiêu thức nối ngôn ngữ này với ngôn ngữ kia với những...
Ngoc Dao viết gần 2 năm trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
102 bài viết.
252 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!