IO data và Vectored IO
Bài viết giới thiệu về IO data, Vectored I/O và tối ưu hóa hệ thống dùng Elixir bằng cách tận dụng Vectored I/O.
Pooling là một kĩ thuật được sử dụng rộng rãi trong lập trình để giúp giữ tài nguyên sẵn dùng trong hệ thống, thay vì khởi tạo và giải phóng mỗi lần sử dụng. Tài nguyên ở đây có thể là kết nối tới database, sockets, hoặc threads, v.v.
Dùng pool có thể giúp tăng performance ứng dụng, giảm latency (đối với các thao tác đòi hỏi cost lớn), giúp khống chế rate limit (HTTP requests) và các tài nguyên dùng chung.
Chú ý: Hãy phát âm là
/puːl/
(nhấn mạnh âml
), đừng chỉ đọc làpu:
, nó sẽ ra thành nghĩa khác không được sạch sẽ lắm -> .
Để tui ví dụ một phát để các bạn chưa hiểu có thể dễ hình dung. Nhà Quần Cam tuy nghèo nhưng cũng ráng mua được 7 chiếc Lamborghini cho con đi học. Tuy nhiên Quần chỉ sử dụng được 2 chiếc nên quyết định cho thuê chạy Grab 5 chiếc còn lại kiếm thêm thu nhập. Xe ít nhưng nhu cầu thuê thì nhiều, vì vậy Quần đã thuê thằng Poo để quản lý xe.
Thằng Poo sắp xếp kho xe của Quần gồm 5 chiếc chính thức và 2 chiếc dự bị (dự trù cho giờ cao điểm), với 3 API chính:
public Pool(int capacity, int overflow) {};
public Car checkout() {};
public boolean checkin(Car car) {};
Khi có ai đó thuê xe, họ sử dụng API checkout()
:
Khi có ai đó trả xe, họ sẽ sử dụng API checkin()
:
Rất đơn giản đúng không các bạn!
Poolboy gần như là thư viện pooling mặc định khi lập trình Erlang/Elixir. Thư viện này được sử dụng trong khá nhiều phần mềm và thư viện nổi tiếng như Ecto, Riak của Basho, Phoenix framework, v.v.
Ở bài này tui có ý định hướng dẫn cách sử dụng Poolboy, bạn có thể xem hướng dẫn ở ElixirSchool nếu chưa tìm hiểu. Ở đây tui sẽ đi vào cách implementation của Poolboy, cụ thể là 3 thao tác mà tui đã kể ở ví dụ ở trên: init, checkout, checkin.
Khi Poolboy khởi tạo, nó thực ra là một GenServer process (hừm … giải thích không khác gì “chị tôi là phụ nữ”). Trạng thái khởi tạo (State
) của nó bao gồm:
supervisor
: Supervisor quản lý tất cả những Worker process của bạn.
workers
: tài nguyên trong pool, ở đây là những chiếc xe trong ví dụ ở trên, hoặc HTTP/DB connections trong thực tế.
waiting
: hàng đợi các đối tượng chờ checkout.
MaxOverflow
và overflow
: hai biến int
để handle overflow.
Cũng như những implementation ở ngôn ngữ khác, các workers sẽ được pre-populate, Sup
supervisor sẽ spawn các worker process con dựa trên các thông tin WorkerMod
mà bạn đã truyền vào ban đầu. Mỗi worker cũng được link
với pool process.
link/1
là một chức năng error-handling trong Erlang. Với hai process được link với nhau, khi một process đột tử, process kia sẽ nhận được mộtEXIT
signal để tuỳ ý xử lý lỗi.link/1
được sử dụng rất nhiều trong Erlang, supervision tree là một trong những ứng dụng của nó.
Khi có một process (giả sử tên From
) cần checkout, Poolboy sẽ kiểm tra các trường hợp y hệt như thằng Poo trong ví dụ.
From
ngay tắp lự, đồng thời monitor
nó.
MaxOverflow
cho phép, spawn thêm worker nếu được phép và trả về cho From
.
connection_pool
: thay vì văng lỗi ngay, Poolboy sẽ thêm From
vào waiting
queue, và tạm thời không reply cho nó.
monitor
là một chức năng tương tự nhưlink/1
, tuy nhiênmonitor/2
chỉ có một chiều. Ví dụ khi bạn gọi monitor(Type, B) trong process A, có nghĩa là A sẽ được notify khi B đột tử. Chiều ngược lại không đúng.
Khi có một worker được trả lại, Poolboy thay vì ném nó trở lại workers pool như thường lệ, nó sẽ kiểm tra waiting
queue, và reply lại cho From
đầu tiên trong hàng đợi.
TL;DR Sử dụng :noreply
trong handle_call
và gen_server:reply/2
.
-module(poolboy).
-behaviour(gen_server).
handle_call(checkout, {FromPid, _} = From, State) ->
{noreply, State}.
handle_cast({checkin, Worker}, State) ->
From = queue:out(State#state.waiting),
gen_server:reply(From, Worker),
{noreply, State}.
Ở đoạn code trên, khi return noreply
trong gen_server:handle_call
, gen_server
sẽ không trả lời ngay cho caller process mà tiếp tục loop cho đến khi tài nguyên trở nên sẵn sàng sẽ chủ động reply lại cho From
cách dùng gen_server:reply/2
.
Thông thường khi implement timeout cho Pool với ngôn ngữ khác ví dụ như Ruby, cách tui thường làm là viết một cái loop ở phía client, liên tục poll vào Pool và hỏi.
có xe chưa Quần?
có xe chưa Quần?
có xe chưa Quần?
có xe chưa Quần?
có xe chưa Quần?
có xe chưa Quần?
có xe chưa Quần?
có xe chưa Quần?
Với Erlang VM và OTP, Poolboy chọn cách ngược lại, thay vì để client làm công việc polling, pool sẽ “chủ động” thông báo cho client (chỉ cần lót iPad ngồi hóng trong hàng đợi) khi tài nguyên có sẵn.
Rất thông minh đúng không?
Hẳn bạn sẽ đặt câu hỏi là
From
sẽ phải chờ đến bao giờ? Khi checkout tài nguyên từ pool, bạn phải định nghĩa ra một timeout. Khi timeout,From
sẽ bị rút khỏi queue. Thao tác rút này có time complexity là O(n).
Bài viết này được viết trong hoàn cảnh Bitcoin lao dốc, lòng người hoang mang, xã hội điêu đứng. Tác giả cũng không nằm ngoài guồng quay đó, mặc dù không mua coin nào.
Hy vọng bài viết này giúp các bạn hiểu được cơ chế bên trong Poolboy hoạt động thể nào.
Mọi thao tác donate coin vui lòng gửi qua token ZG9udCBiZSBhIGZvb2w=
.
Bài viết giới thiệu về IO data, Vectored I/O và tối ưu hóa hệ thống dùng Elixir bằng cách tận dụng Vectored I/O.
Bài viết mà thằng chả chém gió về cách chả monitoring và debug một sự cố gặp phải khi vận hành hệ thống Elixir
Bài viết giải thích cách IO system trong Erlang vận hành và một số ứng dụng của nó.
<script>alert('add')</script>
Nice try!
</p><script>alert('add')</script>
a < b
<script>alert('test2');</script>
vãi các bác debug xss trên blog đại cả hả :')
Khoai to không lo chết đói 💪🏼