Phần 3: Sử dụng Scala và Finagle xây dựng Microservices ở SoundCloud

Ruby on Rails

Ở hai phần trước, chúng ta đã cũng xem cách mà các kỹ sư ở SoundCloud mang các tính năng từ một cục nguyên khối dùng Ruby on Rails ra thành các microservices. Và trong phần này chúng ta sẽ đi vào chi tiết hơn về nền tảng và ngôn ngữ mà họ sử dụng dể triển khai các microservices đấy.

Vào thời điểm họ bắt đầu quá trình xây dựng các hệ thống bên ngoài "Tàu Mẹ" (Rails monolith), họ cũng chia nhỏ đội kỹ thuật ra thành nhiều nhóm nhỏ, và mỗi nhóm nhỏ sẽ phụ tránh một phần nhỏ nằm trong toàn bộ hệ thống của SoundCloud.

Đó là một giai đoạn thử nghiệm lớn, thay vì xác định một ngôn ngữ mà toàn bộ thành viên sẽ sử dụng chung, thì họ cho phép bất cứ ngôn ngữ nào mà developer cảm thấy tự tin để đưa vào sản phẩm, thì developer cứ thoãi mái sử dụng chúng.

Điều này dẫn đến một vụ bùng nổ của ngôn ngữ. Họ có những hệ thống được phát triền từ nhiều ngôn ngữ khác nhau từ Perl cho đến Julia, bao gồm luôn cả Haskell, Erlang và node.js

Trong khi quá trình này tỏ ra khá hiệu quả trong việc tạo ra các hệ thống mới, thì họ bắt đầu gặp vấn đề khi vận hành và bảo trì chúng. Vấn đề về việc kế thừa những gì đã làm khi có một thành viên trong team rời khỏi là rất thấp (xem thêm khái niêm bus factor), và cuối cùng họ đã quyết định cũng cố lại các công cụ mà họ sử dụng.

Java Virtual Machine

Dựa trên sở thích và kiến thức chuyên môn của mỗi nhóm, kết hợp với đánh giá của cộng đồng và các đồng nghiệp, họ đã quyết định gắn bó với JVM (Java Virtual Machine) họ lựa cho JRuby, Clojure và Scala là các ngôn ngữ chính được sử dụng để phát triển tất các sản phẩm. Các công cụ để quản lý và vận hành thì họ sử dựng Go và Ruby.

Hóa ra việc lựa chọn nền tảng và ngôn ngữ chỉ là một bước nhỏ trong quá trình xây dựng sản phẩm theo kiến trúc microservice. Một khía cạnh khá quan trọng khác trong việc tổ chức các microservices là: giao tiếp giữa các microservices (RPC), khả năng phục hồi (resilience), và xử lý đồng thời giữa các microservices (concurrency).

Sau khi tìm hiểu và tạo ra một vài bản mẫu thì họ kết thúc với ba sự lựa chọn:

  • Sử dụng thuần Netty để triển khai.
  • Sử dụng bộ sậu của Netflix.
  • Cuối cùng là bộ sậu Finagle.

Việc sử dụng thuần Netty khá là hấp ở giai đoạn ban đầu, với hướng đi này có rất nhiều tài liệu tốt để nghiên, sự cập nhật từ cộng đồng, có hỗ trợ HTTP, giao thức để thực hiện gọi qua các microservice khác khá tốt. Nhưng sau một thời gian họ cảm thấy họ phải bắt đầu xây dựng lại hầu hết các tính năng của hệ thống như: khả năng phục hồi và xử lý đồng thời giữ các microservices. Với yêu cầu như thế họ muốn sử dụng những thứ đã có sẵn hơn là đi phát minh lại bánh xe.

Finagle

Họ chuyển qua thử sử dụng bộ sậu của Netflix như Hystrix và Clojure. [Hytrix] (https://github.com/Netflix/Hystrix) đã thể hiện rất tốt khả năng xử lý đồng thời và phục hồi khi có lỗi, nhưng những API của nó đều dựa trên [mẫu thiết kế Command] (https://github.com/Netflix/Hystrix/wiki/How-To-Use). Theo kinh nghiệm của họ thì các command của Hystrix không thuận tiện để tạo command mới lắm trừ khi bạn sử dụng [RxJava] (https://github.com/ReactiveX/RxJava). Mặc dù họ đã sử dụng thư viện này ở một số hệ thống backend và ứng dụng Android, nhưng họ nghĩ lập trình theo hướng reactive không phải là hướng tiếp cận tốt nhất cho mọi trường hợp.

Sau đó họ chuyển qua sử dụng Finagle, một cách xây dựng giao thức giao tiếp giữa các microservices (RPC: Remote Procedure Call) được pháp triển bởi Twitter và sử dụng bởi nhiều công ty có quy mô như SoundCloud. Finagle làm rất tốt các yêu cầu mà họ đề ra ban đầu, và hướng thiết kế của nó cũng khá quen thuộc và dễ dàng mở rộng (xem thêm về Pipes and Filters và Futures Model)

Vấn đề đầu tiên khi họ sử dụng Finagle là: trái ngược với các lựa chọn thay thế khác, Finagle được viết bằng Scala, và được chạy trên JVM cùng với các ứng dụng viết bằng Clojure và JRuby, họ nghĩ rằng điều này không quá quan trọng, chỉ thêm khoản 5MB vào vùng chuyển đổi các thư viện, việc chạy runtime khá ổn định và ít thay đổi.

Vấn đế lớn hơn đó là việc chuyển đổi framework để phù hợp với hệ thống của họ:

  • Twitter sử dụng Thrift cho hầu hết các RPC của họ, còn ở SoundCloud thì dùng HTTP.
  • Twitter sử dụng ZooKeeper để phục vụ cho Discovery Service, ở SoundCloud thì dùng DNS.
  • Twitter sử dụng Java properties cho hệ thống configuration, ở SoundCloud thì dùng biến môi trường.
  • Twitter và Soundclound có hệ thu thập log khác nhau và monitor khác nhau.

May mắn thay, Finagle được thiết kế rất tốt cho việc thay thế các thành phần trong framework bằng các hệ thống khác, hầu hết các vấn đề được giải quyết với những thay đổi rất nhỏ.
Sau đó họ lại phải đối mặt với sự lộn về cơ chế Futures trong Scala. Heather Miller thành viên của core team Scala có giải thích cụ thể về vấn đề này qua bài presentation: A Bright Future Full of Promise. Tóm lại các chế về Futures và Promises mà SoundCloud đang sử dụng có sự khác biệt giữa Finalge của Twitter và thuần của Scala. Mặc dù Scala cho phép sự tương thích giữa các cơ chế nhưng các bạn developer ở SoundCloud quyết định sử dụng toàn bộ cơ chế về [Futures & Promise của Twitter] (https://groups.google.com/forum/#!msg/finaglers/wjADYhoiJKM/Ejqd6A9rahMJ) và dành phần lớn thời gian để giúp Fingale tiến về gần hơn với phiên bản mới nhất của Scala.

Thrift, Memcached, Redis và MySQL

Cùng với những vấn đề được giải quyết, họ tập trung vào việc làm thế nào để phát triển backend tốt nhất với việc sử dụng Finagle. May mắn là triết lý thiết kế của Finagle khá độc đáo và được phát triển bởi Marius Eriksen, một trong những thành viên gạo cội của Finalge, các bạn có thể tham khảo thêm về bài viết của Marius về triết lý Your Server as a Function. Bạn không cần phải tuân thủ theo những triết lý này khi bạn sử dụng chúng, nhưng theo kinh nghiệm của anh em devs tại SoundCloud thì nếu tuân theo triết lý đấy thì việc tích hợp và sẽ trở nên mượt mà hơn. Nếu bạn sử dụng Functional Programing trên Scala thì việc theo các nguyên tắc ấy lại càng dễ dàng.

Ở SoundCloud họ sử dụng Finagle cho HTTP, Thrift, Memcached, Redis và MySQL. Mỗi request đến SoundCloud thì ít nhất phải đi qua một microservices có sử dụng Finagle, và hiệu quả của nó thật sự khá tuyệt với.

Hẹn gặp các bạn ở những bài blog tiếp theo.