Phần 1: Xử lý kiến trúc cũ để xây dựng backend ở SoundCloud

SoundCloud’s

Hầu hết các thành phần backend của SoundCloud’s đều được viết bằng ngôn ngữ Scala, Clojure, hay JRuby, nhưng không phải tất các trường hợp đều như thế. Cũng giống như các công ty công nghệ khởi nghiệp khác, Soundcloud đã xây dựng một backend duy nhất bằng ngôn ngữ Ruby on Rails chạy trên MRI, một trình thông dịch chính thức của Ruby. Họ sử dụng MySQL để lưu trữ cơ sở dữ liệu, tăng tốc độ truy vấn bằng cách sử dụng Memcached để caching dữ liệu.

Ở SoundCloud trong các buổi nói chuyện họ hay gọi hệ thống này bằng một cái tên khá kêu: “Tàu Mẹ” (Mothership). Một kiến trúc như thế đã tỏ ra là một giải pháp tốt để xây dựng một sản phẩm mới được sử dụng bởi hàng trăm nghìn nghệ sĩ trên khắp thế giới để chia sẽ những bài hát, những bản nháp ra cộng đồng yêu âm nhạc.

Mã nguồn lập trình trên Rail chứa tất cả các API Public, mở ra để hàng nghìn các dịch vụ khác sử dụng, và luôn cả phần web application của Soundcloud.com. Với việc ra mắt phiên bản kế tiếp The Next SoundCloud vào năm 2012, họ đã đưa một tập API và được sử dụng chung cho tất cả các phiên bản iOS/Android, ứng dụng web trên trang soundcloud.com, bao gồm luôn các dịch vụ khác của đối tác và các nhà phát triển.

alt text

Vào thời điểm đấy, cứ mỗi phút trôi qua thì có 12 giờ âm nhạc được tải lên, và hàng trăm triệu người dùng nền tảng này mỗi ngày. Soundcloud phải đối mặt với những thách thức mở rộng hệ thống cả về mạng xã hội lẫn nền tảng phân phối âm nhạc.
Để mở rộng nền tảng dưa trên Rails đến mức đấy, họ phát triển và đóng góp cho cộng đồng khá nhiều thư viện cũng như các công cụ để giúp xử lý:

Mô hình microservices

Sự tái thiết kế lớn như thế đã mang đến rất nhiều khó khăn cho họ trong quá khứ, vì thế cả team quyết định cách tốt nhất để tiếp cận để đối phó với những thay đổi về kiến trúc là sẽ không chia “Tàu Mẹ” ra ngay lúc đấy, nhưng cũng sẽ không thêm bất cứ tính năng gì mới vào. Tất cả những tính năng mới sẽ được xây dựng theo mô hình microservices, và khi có một tính năng cần có sự thay đổi lớn của “Tàu Mẹ” thì họ sẽ cố gắn mang thành phần đó ra từ “Tàu Mẹ” trong nổ lực chia nhỏ hệ thống thành từng phần nhỏ.

Một sự bắt đầu khá tốt, nhưng chẳng bao lâu sau họ phát hiện ra một vấn đề: Bởi vì hầu hết các nghiệp vụ của họ vẫn còn nằm trong khối Rails, và các thành phần được xây dựng mới phải giao tiếp với cục backend cũ theo một cách nào đó.

Một trong những lựa chọn xung quanh vấn đề này là để các microservices truy cập trực tiếp vào cơ sơ dữ liệu của “Tàu Mẹ”. Đây là một trong những cách tiếp cận rất phổ biến , bởi vì cơ sở dữ liệu là dùng chung, nhưng nó không phải là như nhau đối với các service khác nhau. Đều này dẫn đến khá nhiều vấn đề khi chúng ta cần thay đổi cấu trúc của table dùng chung đó.

Thay vào đó, họ đã đưa ra một bộ API dùng chúng, và các microservices nội bộ cũng sẽ hành xử tương tự nhưng các ứng dụng của bên thứ ba tích hợp vào hệ thống của Soundcloud.

alt text

Một vấn đề lớn nữa họ phải đối mặt sau khí xử dụng mô hình như trên, đó là các microservice của họ cần phải phản ứng lại đối với những hành động của người dùng. Ví dụ như hệ thống push-notification, nó cần phải biết khi có bất bình luận nào một ca khúc và nó sẽ thông báo cho các nghệ sĩ sở hữu ca khúc đấy. Với nhu cầu mở rộng lớn như vậy thì các polling sẽ không thể là một giải pháp tốt. Họ cần phải tạo ra một mô hình tốt hơn để giải quyết bài toán này.

Và như thế họ đã sử dụng AMQP (một hệ thống nhận message và push cho worker), cụ thể đó là RabbitMQ - Với một ứng dụng viết bằng Rails bạn cần một cách nào đáy đẩy message một cách chậm rãi tới các worker để tránh các vấn đề về xử lý đồng thời (một điểm yếu của trình thông dịch Ruby) bạn có thể tham khảo thêm bài viết chi tiết về vấn đề xử dụng AMQP ở đây (https://www.infoq.com/presentations/amqp-soundcloud), thông qua nhiều phát triển và thử nghiệm họ tạo ra một mô hình gọi là: Semantic Events, khi có sự thay đổi trên dữ liệu được xác định sự thay đổi đấy sẽ được gửi đến một worker trung gian, sau đó worker trung gian sẽ gửi sự thay đổi này đến các microservices liên quan.

alt text

Kiến trúc này kích hoạt mô hình Event Sourcing (http://martinfowler.com/eaaDev/EventSourcing.html), đó là cách mà họ xử lý vấn đề chia sẽ dữ liệu, nhưng khi dùng kiến trúc này nó vẫn cần sử dụng các API từ “Tàu Mẹ”. Ví dụ như nó vấn cần sử dụng API để lấy danh sách các fan của nghệ sĩ cùng địa chỉ email để thống báo cho họ biết khi nghệ sĩ của họ cập nhật một bài hát mới.

Trong khi hầu hết các dữ liệu đã có sẵn thông qua các API, họ lại bị ràng buộc bởi những qui tắc mà họ đặt ra cho các dịch vụ của bên thứ 3. Ví dụ như đối với một microservice để thông báo cho người dùng về những hoạt động mà được thiết lập ở mức riêng tư thì nó không thể truy xuất được bởi vị các API đấy chỉ có thể truy xuất thông tin công cộng.

Và họ đã tìm ra một số giải pháp để xử lý vấn đề đấy, một trong những cách phổ biến đó là tách những models của Rails từ “Tàu Mẹ” và đưa nó trở thành phần chia sẽ. Một số vấn đề quan trọng trong cách tiếp cận này đó là chi phí quản lý sự đồng bộ của các models đấy ở các microservices khác nhau, và rõ ràng là khi các microservices đấy viết bằng các ngôn ngữ khác thì chi phí đó càng cao lên. Do đó họ cần phải suy nghĩ về một giải pháp khác tốt hơn.

Cuối cùng họ quyết định sử dụng tính năng của Rails engine để triển khai các API nội bộ và chỉ các ứng dụng trong mạng nội bộ mới có thể truy xuất được. Để điều khiển việc này họ sử dụng Oauth 2.0 để xác thực các ứng dụng.

alt text

Họ đã nổ lực không ngừng để mang các tính năng từ “Tàu Mẹ” ra ngoài microservice bằng cách xử dụng cả 2 giải pháp là push và pull để tương tác với hệ thống củ. Các kiến trúc của microservice đã tỏ ra rất quan trọng để phát triển các tính nắng mới với chu trình phát triển ngắn hơn rất nhiều.