Cơ sở hạ tầng (Infrastructure)

Chúng ta sẽ bắt đầu nói về nhiều cách khác nhau mà bạn có thể tổ chức một ứng dụng phần mềm, bắt đầu với cách bạn có thể tổ chức cơ sở hạ tầng phía sau dự án của mình.

1. Kiến trúc nguyên khối (Monolithic Architecture)

Để dễ hình dung, chúng ta giả định một ứng dụng tên là Notflix (không liên quan gì Netflix nhé) sẽ là một ứng dụng truyền phát video điển hình, trong đó người dùng sẽ có thể xem phim, phim bộ, phim tài liệu, v.v. Người dùng sẽ có thể sử dụng ứng dụng này trong các trình duyệt web, trong ứng dụng dành cho thiết bị di động và cả trên ứng dụng TV.

Các dịch vụ chính bao gồm xác thực – authentication (để mọi người có thể tạo tài khoản, đăng nhập, v.v.), thanh toán – payments (để mọi người có thể đăng ký và truy cập nội dung … vì bạn không nghĩ rằng tất cả đều miễn phí, đúng không ?) và tất nhiên là phát trực tuyến – streaming  (để mọi người thực sự có thể xem những gì họ đang trả tiền).

Bản phác thảo nhanh về kiến ​​trúc ứng dụng có thể trông như thế này:

Nguồn freeCodeCamp

Ở bên trái, chúng ta có ba ứng dụng giao diện người dùng khác nhau (Web, Mobile, TV) sẽ hoạt động như những ứng dụng khách trong hệ thống này. Ví dụ, chúng có thể được phát triển với React và React-native.

Chúng ta có một máy chủ duy nhất sẽ nhận yêu cầu từ cả ba ứng dụng khách, giao tiếp với cơ sở dữ liệu (Database) khi cần thiết và phản hồi cho từng giao diện người dùng tương ứng. Giả sử như back-end có thể được phát triển với Node và Express.

Loại kiến ​​trúc này được gọi là nguyên khối (monolith) vì có một ứng dụng máy chủ duy nhất chịu trách nhiệm về tất cả các tính năng của hệ thống. Trong trường hợp của chúng ta, nếu người dùng muốn xác thực, thanh toán hoặc xem một trong các bộ phim, tất cả các yêu cầu sẽ được gửi đến cùng một ứng dụng máy chủ.

Lợi ích chính của thiết kế nguyên khối là sự đơn giản của nó. Hoạt động của nó và yêu cầu thiết lập rất đơn giản và dễ làm theo, và đây là lý do tại sao hầu hết các ứng dụng bắt đầu theo cách này.

2. Kiến trúc dịch vụ vi mô (Microservices)

Notflix nhận được hàng chục nghìn người dùng mới mỗi tháng từ khắp nơi trên thế giới, điều này rất tốt cho doanh nghiệp nhưng không quá nhiều đối với ứng dụng nguyên khối của chúng ta.

Gần đây, chúng ta đã gặp phải sự chậm trễ trong thời gian phản hồi của máy chủ và mặc dù chúng ta đã mở rộng máy chủ theo chiều dọcvertically scaled (đặt thêm RAM và GPU vào đó) nhưng điều tồi tệ là dường như không thể chịu được tải mà nó đang đảm nhận.

Hơn nữa, chúng ta đã tiếp tục phát triển các tính năng mới vào hệ thống của mình (chẳng hạn như công cụ đề xuất (recommendation) đọc sở thích của người dùng và đề xuất phim phù hợp với hồ sơ người dùng) và cơ sở mã của chúng ta bắt đầu trông rất lớn và rất phức tạp để làm việc.

Phân tích sâu vấn đề này, chúng ta nhận thấy tính năng chiếm nhiều tài nguyên nhất là phát trực tuyến, trong khi các dịch vụ khác như xác thực và thanh toán không thể hiện mức tải quá lớn.

Để giải quyết vấn đề này, chúng ta sẽ triển khai một kiến ​​trúc microservices trông giống như sau:

Nguồn freeCodeCamp

Chúng ta có thể định nghĩa kiến trúc dịch vụ vi mô (microservices) là khái niệm chia các tính năng phía máy chủ thành nhiều máy chủ nhỏ chỉ chịu trách nhiệm cho một hoặc một vài tính năng cụ thể.

Theo ví dụ, trước đây chúng ta chỉ có một máy chủ duy nhất chịu trách nhiệm cho tất cả các tính năng (một kiến ​​trúc nguyên khối). Sau khi triển khai microservices, chúng ta sẽ có một máy chủ chịu trách nhiệm xác thực, một máy chủ khác chịu trách nhiệm thanh toán, một máy chủ khác để phát trực tuyến và máy chủ cuối cùng cung cấp các đề xuất.

Các ứng dụng phía máy khách sẽ giao tiếp với máy chủ xác thực khi người dùng muốn đăng nhập, với máy chủ thanh toán khi người dùng muốn thanh toán và với máy chủ phát trực tuyến khi người dùng muốn xem nội dung nào đó.

Tất cả giao tiếp này diễn ra thông qua các API giống như với một máy chủ nguyên khối thông thường (hoặc thông qua các hệ thống giao tiếp khác như Kafka hoặc RabbitMQ). Sự khác biệt duy nhất là bây giờ chúng ta có các máy chủ khác nhau chịu trách nhiệm cho các hành động khác nhau thay vì một máy chủ duy nhất thực hiện tất cả.

Điều này nghe có vẻ phức tạp hơn một chút nhưng microservices mang lại cho chúng ta những lợi ích sau:

  • Bạn có thể mở rộng các dịch vụ cụ thể khi cần thiết, thay vì mở rộng toàn bộ back end cùng một lúc. Theo ví dụ trên, khi chúng ta bắt đầu gặp vấn đề về hiệu suất, chúng ta đã mở rộng quy mô toàn bộ máy chủ của mình theo chiều dọc – nhưng thực ra tính năng yêu cầu nhiều tài nguyên hơn chỉ là phát trực tuyến. Bây giờ chúng ta có tính năng phát trực tuyến được tách thành một máy chủ duy nhất, chúng ta chỉ có thể mở rộng quy mô một máy chủ đó và để yên phần còn lại miễn là chúng tiếp tục hoạt động bình thường.
  • Các tính năng sẽ được kết hợp lỏng hơn (loosely coupled), có nghĩa là chúng ta sẽ có thể phát triển và triển khai chúng một cách độc lập.
  • Cơ sở mã cho mỗi máy chủ sẽ nhỏ hơn và đơn giản hơn nhiều. Điều này thật tốt cho những nhà phát triển đã làm việc với chúng ta ngay từ đầu và cũng dễ dàng hơn và nhanh chóng hơn cho các nhà phát triển mới.

Microservices là một kiến ​​trúc phức tạp hơn để thiết lập và quản lý, đó là lý do tại sao nó chỉ có ý nghĩa đối với các dự án rất lớn. Hầu hết các dự án sẽ bắt đầu dưới dạng nguyên khối và chỉ chuyển sang microservices khi cần thiết vì lý do hiệu suất.

3. Back-end cho front-end (BFF)

Một vấn đề nảy sinh khi triển khai microservices là giao tiếp với các ứng dụng front-end trở nên phức tạp hơn. Giờ đây, chúng ta có nhiều máy chủ chịu trách nhiệm về những việc khác nhau, có nghĩa là các ứng dụng front-end sẽ cần theo dõi thông tin đó để biết ai cần đưa ra yêu cầu.

Thông thường, vấn đề này được giải quyết bằng cách triển khai một lớp trung gian giữa các ứng dụng front-end và microservices. Lớp này sẽ nhận tất cả các yêu cầu của front-end, chuyển hướng chúng đến microservice tương ứng, nhận phản hồi của microservice và sau đó chuyển hướng phản hồi đến ứng dụng front-end tương ứng.

Lợi ích của mô hình BFF là chúng ta nhận được những lợi ích của kiến ​​trúc microservices mà không làm phức tạp quá trình giao tiếp với các ứng dụng front-end

Nguồn freeCodeCamp

4. Cách sử dụng bộ cân bằng tải (load balancers) và mở rộng theo chiều ngang (horizontal scaling)

Ứng dụng phát trực tuyến Notflix của chúng ta không ngừng phát triển và phát triển với tốc độ cấp số nhân. Chúng ta có hàng triệu người dùng trên khắp thế giới xem phim của chúng ta 24/7 và sớm hơn dự kiến, chúng ta bắt đầu gặp lại các vấn đề về hiệu suất.

Một lần nữa, chúng ta nhận thấy rằng dịch vụ phát trực tuyến là dịch vụ chịu nhiều áp lực nhất và chúng ta đã mở rộng máy chủ đó theo chiều dọc (vertically scale)  tất cả những gì có thể (thêm RAM, GPU). Việc chia nhỏ dịch vụ đó thành nhiều dịch vụ nhỏ hơn không có ý nghĩa gì, vì vậy chúng ta đã quyết định mở rộng quy mô dịch vụ đó theo chiều ngang (horizontally scale).

Ở trên chúng ta đã đề cập rằng mở rộng máy chủ theo chiều dọc có nghĩa là thêm nhiều tài nguyên hơn (RAM, dung lượng đĩa, GPU, v.v.) vào một máy chủ / máy tính duy nhất. Mặt khác, mở rộng máy chủ theo chiều ngang, có nghĩa là thiết lập nhiều máy chủ hơn để thực hiện cùng một tác vụ.

Thay vì có một máy chủ duy nhất chịu trách nhiệm phát trực tuyến, giờ đây chúng ta sẽ có ba máy chủ. Sau đó, các yêu cầu được thực hiện bởi các máy khách sẽ được cân bằng giữa ba máy chủ đó để tất cả đều xử lý tải có thể chấp nhận được.

Việc phân phối các yêu cầu này thường được thực hiện bởi một thứ gọi là bộ cân bằng tải (Load balancer). Bộ cân bằng tải hoạt động chặn các yêu cầu của máy khách trước khi chúng đến máy chủ và chuyển hướng yêu cầu đó đến máy chủ tương ứng.

Mô hình máy khách-máy chủ điển hình có thể trông như thế này:

Nguồn freeCodeCamp

Sử dụng bộ cân bằng tải, chúng ta có thể phân phối các yêu cầu của khách hàng trên nhiều máy chủ:

Nguồn freeCodeCamp

Bạn nên biết rằng mở rộng theo chiều ngang cũng có thể thực hiện được với cơ sở dữ liệu (DB) cũng như với máy chủ. Một cách để thực hiện điều này là với mô hình bản sao nguồn (source-replica model), trong đó một DB nguồn cụ thể sẽ nhận tất cả các truy vấn ghi và sao chép dữ liệu của nó cùng với một hoặc nhiều DB bản sao. Replica DBs sẽ nhận và trả lời tất cả các truy vấn đã đọc.

Ưu điểm của việc sao chép DB là:

  • Hiệu suất tốt hơn: Mô hình này cải thiện hiệu suất cho phép nhiều truy vấn được xử lý song song hơn.
  • Độ tin cậy và tính khả dụng: Nếu một trong các máy chủ cơ sở dữ liệu của bạn bị phá hủy hoặc không thể truy cập được vì bất kỳ lý do gì, dữ liệu vẫn được lưu giữ trong các DB khác.

Vì vậy, sau khi triển khai bộ cân bằng tải, mở rộng theo chiều ngang và sao chép DB, kiến ​​trúc của chúng ta có thể trông giống như sau:

Nguồn freeCodeCamp