Disruptor là gì
--- tags: LMAX Disruptor --- # 000: Tìm hiểu về LMAX Disruptor để thay thế BlockingQueue trong Java Các Java Engineer chắc hẳn không xa lạ gì với BlockingQueue, một dạng internal pub-sub trong chương trình. Đặc biệt với multi-thread programming, thread gửi/nhận message tới thread khác thông qua queue để xử lý các bài toán bất đồng bộ hoặc một số lượng cực lớn các task. Threadpool mà chúng ta hay dùng cũng implement BlockingQueue phía dưới nhằm điều phối các task đến các thread để được thực thi. Tuy nhiên như chúng ta biết, bản chất của BlockingQueue là **block**, là **lock**, là **synchronize**. Điều đó khiến [**context switch**](https://hackmd.io/@datbv/rk3pA6XK_#2-Context-switch) xảy ra, tăng latency và trực tiếp làm giảm performance của hệ thống. Các kỹ sư của LMAX đã tạo ra **Disruptor** để xử lý bài toán này. Không chỉ bài toán này mà rất nhiều bài toán khác liên quan đến internal pub-sub trong một ứng dụng. Chúng ta cùng đi tìm hiểu cụ thể hơn LMAX Disruptor là gì và vì sao nó có thể thay thế BlockingQueue trong Java qua series lần này với các chủ đề: - 001: Hiểu về Queue và BlockingQueue trong Java - 002: LMAX Disruptor là gì? - 003: LMAX Disruptor và BlockingQueue - 004: Sức mạnh của LMAX Disruptor nằm ở đâu? - 005: LMAX Disruptor nên được áp dụng thế nào? ### Reference - [LMAX Disruptor](https://lmax-exchange.github.io/disruptor) - [The LMAX Architecture](https://martinfowler.com/articles/lmax.html) - [Github LMAX Disruptor Wiki](https://github.com/LMAX-Exchange/disruptor/wiki) © [Dat Bui](https://www.linkedin.com/in/datbv/) Show Tiếp nối bài trước về Mechanical Sympathy và giới thiệu qua về LMAX Disruptor, ở bài viết này người viết sẽ phân tích sâu hơn và hướng dẫn cách sử dụng về bộ thư viện Disruptor. LMAX-Disruptor là một bộ thư viện giúp cho việc phát triển các ứng dụng với độ tải lớn (high-performance) cho phép xử lý đồng thời (concurrency) một số lượng rất lớn message mà không cần Lock (lock-free). Nếu bạn làm việc với Java thì thực tế đây là bộ thư viện về Concurrency tốt nhất và nhanh nhất hiện nay. Quay lại ví dụ ở bài trước khi ta khởi tạo một Disruptor đơn giản với 1 Producer và 1 Comsumer. Ở bài trước tui đã giải thích về ý nghĩa các tham số của Disruptor constructor, nhưng chưa bàn tới các "Waiting strategy" được đại diện bởi interface WaitStrategy. Disruptor có 4 chiến thuật chờ đợi chính là BlockingWaitStrategy:Mặc định Disruptor sẽ sử dụng Wait Strategy là SleepingWaitStrategy:Giống như YieldingWaitStrategy:
BusySpinWaitStrategy:Tốc độ thực thi của Core Concepts Về cơ bản ta đã sử dụng được Disruptor để gửi nhận Producer và Consumer đơn và hiểu được các Wait Strategy trong Disruptor, nhưng để sử dụng Multicast Events hoặc Consumer Dependency Graph, có nghĩa là ta có thể sử dụng nhiều Producer lẫn Consumer kết hợp với nhau, để làm được điều này ta phải hiểu và biết cách sử dụng Sequence:Lớp này được thiết kế để thao tác với các sequence (số thứ tự) của Ring-Buffer nhằm việc đảm bảo việc hoạt động tốt với môi trường Concurrency. Sequence không sử dụng cơ chế Locking để giải quyết vấn đề về Mutual Exclusion như race-condition mà sử dụng cơ chế CAS (Lock-free) để giải quyết bài toán đó, chính vì thế sequence luôn được Sequencer:Từ phiên bản 3.0 trở đi thì đây chính là cốt lõi (core) của Disruptor, nó được thiết kế để quản lý việc gửi và nhận dữ liệu đến Ring-Buffer giữa Producer và Consumer nhanh nhất và chính xác nhất . Sequencer là một interface và ta có hai class implement nó là EventProcessor:Interface này được sử dụng để quản lý nhiều Consumer khi ta sử dụng Multicast Events, nó giúp cho việc quản lý thứ tự sử lý của từng Consumer trong mô hình Consumer Dependency Graph. Nó chỉ có duy nhất một Implementation là lớp Sequence Barrier:Là một rào chắn dữ liệu (memory barrier) được tạo ra từ Sequencer để đảm bảo thứ tự nhận event giữa Ring-Buffer tới các Consumer chính xác nhất. Nó luôn luôn nằm giữa và điều phối dữ liệu giữa Ring-Buffer và Consumer. Để dễ hình dung ta có thể tham khảo mô hình hoạt động của Disruptor bên dưới. Ở bên dưới ta có một mô hình với 2 Producer gửi dữ liệu tới Ring-Buffer, và ta có các Consumers là Quan hệ giữa 3 consumers bên trên ta gọi nó là Consumer Dependency Graph, chúng thực hiện được nhờ vai trò của
Lý thuyết vậy là đủ, giờ ta hãy bắt tay vào thực hiện implement mô hình Consumer Dependency Graph bên trên với chỉ một Producer nhé. Mô hình tổ chức code được minh họa như bên dưới: +-----+ Đầu tiên ta phải tạo một Event Object, đây chính là thông tin dữ liệu được lưu chuyển tới các Consumer để sử lý business logic. Event Object dưới chỉ chứa một giá trị có kiểu Long. Tiếp đó là 3 Consumer là Journal Consumer: Replication Consumer: Application Consumer: Bởi vì Để tiện cho việc chạy test ta sẽ tạo một Junit Test như sau, và chúng ta sẽ đi sâu vào phân tích nó tiếp sau: Giờ hãy bắt đầu phân tích đoạn code demo bên trên. Hãy nhìn vào cách chúng ta khởi tạo RingBuffer, ta sẽ sử dụng static method được cung cấp sẵn trong class RingBuffer là Bản thân Ring-Buffer đã có một Sequence Barrier (SB1 như trên hình minh họa trên) bằng cách gọi tới hàm SequenceBarrier ngoài việc đảm bảo thứ tự cũng như quyết định gửi tới những Consumers nào, thì nó còn có tác dụng khi Consumer muốn lấy dữ liệu tiếp theo nhưng event đó chưa có (Producer chưa kịp gửi) thì nó sẽ sử dụng các wait strategy để chờ đợi các event tiếp theo tới. Nó đảm bảo được rằng việc lấy dữ liệu từ Ring-Buffer sẽ đảm bảo được sự chính xác và tốc độ mà không cần phải "đồng bộ" hóa tiến trình này (synchronization). OK vậy tiếp theo ta phải làm thế nào để Hãy để ý tới static code Về bản chất các Và start các thread pool để wake up các consumers Và cuối cùng để gửi dữ liệu tới Ring-Buffer ta tạo một vòng lặp sau đó lấy ra sequence tiếp theo bằng cách sử dụng Và cuối cùng ta có thể sử dụng hàm Ta có thể thấy việc khởi tạo các Consumer hoàn toàn khác với các truyền thống là khởi tạo thông qua Disruptor, mà ta sử dụng các |