So sánh phương pháp thiết kế cho xây vi dịch vụ  serverless 

bởi James Beswick | 4/3/2024 | AWS Lambda, Serverless

Bài đăng này được viết bởi Luca Mezzalira, Hiệu trưởng SA, và Matt Diamond,Hiệu trưởng, SA.

Việc thiết kế  một khối lượng công việc với AWS Lambda đặt vấn đề  cho nhà phát triển bởi module có thể được thể hiện ở cấp mã hoặc cơ sở hạ tầng . Việc sử dụng serverless để chạy code yêu cầu thêm kế hoạch bổ sung để mở rộng logic kinh doanh từ các thành phần chức năng bên dưới . Sự phân tách có chủ ý các mối quan ngại này đảm bảo tính module mạnh mẽ, mở đường cho các kiến trúc tiến hóa.

Bài đăng  này tập chung vào khối lượng công việc đồng bộ, nhưng những cân nhắc tương tự cũng có thể áp dụng cho các loại khối lượng công việc khác. Sau  khi xác thực bối cảnh của giới hạn API của bạn với người tiêu dùng, đã đến  lúc cấu trúc lại kiến trúc của bối cảnh bị giới hạn và cơ sở hạ tầng liên quan.

Hai cách phổ biến nhất để cấu trúc API sử dụng Lambda functions chịu trách nhiệm duy nhất và Lambda – lith. Tuy nhiêu, Blog này khám phá một giải pháp thay thế cho việc tiếp cận, cái có thể mang lại hiệu quả tốt nhất cho cả hai.

Các hàm Lambda đơn nhiệm 

Các hàm Lambda đơn nhiệm được  thiết kế để chạy một tác vụ hoặc xử lý một loạt sự kiện cụ thể trong kiến trúc serverless.

Cách tiếp cận này tạo  sự tách biệt rõ ràng  mối quan tâm giữa logic kinh doanh và năng lực . Bạn có thể thử các khả năng riêng biệt cụ thể, triển khai Lambda function một các độc lập, giảm bớt bề mặt để phát hiện lỗi, và đảm bảo dễ dàng gỡ lỗi cho vấn đề Amazon CloudWatch.

Thêm vào đó, các hàm đơn nhiệm đảm bảo phân bổ tài nguyên hiệu quả vì Lambda tự động mở rộng dựa theo nhu cầu, tối ưu tài nguyên tiêu thụ, và giảm thiểu chi phí. Điều đó có nghĩa là bạn có thể giám sát bộ nhớ, kiến trúc, và bắt cứ bất hình nào khác có sẵn cho chức năng. Hơn nữa, yêu cầu cập nhật  việc thực thi hàm đồng thời thông qua một vé hỗ trợ trở nên dễ dàng hơn vì bạn không phải gom lưu lượng vào một hàm lambda duy nhất xử lý mọi yêu cầu nhưng bạn có thể yêu cầu tăng đặc biệt dựa trên lưu lượng của một đơn nhiệm.

Một ưu điểm khác là thời gian thực thi nhanh chóng. Xem  xét Cân nhắc logic nghiệp vụ  cho một hàm lambda đơn nhiệm được thiết kế  cho một nhiệm vụ đơn lẻ, bạn có thể tối ưu  kích thước hàm lambda  một cách dễ dàng mà không cần thêm thư viện như các phương pháp khác. Điều này giúp giảm thời gian khởi động nguội  nhờ  kích thước gói  nhỏ hơn.

Bên cạnh những lợi ích này, một vài vấn đề vẫn tồn tại khi khi chỉ dựa vào các lambda function đơn nhiệm. Dù thời gian khởi động nguội được giảm thiểu, số lần khởi động nguội có thể cao hơn, đặc biệt đối với những hàm được gọi rời rạc ngẫu nhiên hoặc không thường xuyên. Ví dụ: một hàm xóa người dùng  trong bảng Amazon DynamoDB  có thể không được kích hoạt thường xuyên như tính năng đọc dữ liệu người dùng. Ngoài ra việc phụ thuộc các hàm Lambda đơn nhiệm  có thể làm  tăng độ phức tạp hệ thống, đặc biệt khi số lượng hàm tăng lên.

Việc phân tách tốt các quan ngại giúp duy trì cơ sở mã của bạn, nhưng phải trả giá bằng sự thiếu gắn  kết. Trong các hàm với nhiệm vụ tương tự, như thao tác ghi của API(POST, PUT, DELETE), bạn có thể sao chép mã và hành vi trên nhiều hàm. Hơn nữa việc cập nhật thư viện phổ biến được chia sẻ qua Lambda Layer, hoặc hệ thống quản lý độc lập khác, đòi hỏi nhiều thay đổi trên mỗi hàm thay vì một tệp duy nhất. Điều này cũng đúng cho bất kỳ thay đổi nào khác trên nhiều hàm, chẳng hạn ví dụ cập nhập phiên bản runtime.

Lambda-lith: Sử dụng một Lambda function duy nhất

Khi nhiều khối lượng công việc sử dụng cho các hàm Lambda một mục đích duy nhất, các nhà phát triển sẽ phải đối mặt với việc gia tăng các Lambda function trên một tài khoản AWS. Một trong những thử thách chính các nhà phát triển phải đối mặt là cập nhật cấu các cấu hình chức năng hoặc phần phụ thuộc chung phổ biến. Trừ khi có một chiến lược quản trị rõ ràng được triển khai để giải quyết vấn đề này (như là sử dụng Dependabot để bắt buộc cập nhật các phần phụ thuộc, hoặc tham số hóa các tham số được truy vấn trong thời gian cung cấp), các nhà phát triển có thể chọn một chiến lược khác.

Kết quả là nhiều đội phát triển đi theo  hướng ngược lại, tổng hợp tất cả mã liên quan đến API trong cùng một Lambda function.

Cách tiếp cận này thường được gọi là Lambda-lith, bởi nó thu thập tất cả hành động HTTP tạo nên một API và đôi khi thi thoảng nhiều API trong cùng một hàm.

Điều này cho phép bạn có sự  gắn  kết và  colocation  mã cao hơn trên các phần khác nhau của ứng dụng . Module trong trường hợp này được biểu diễn ở bằng mức mã, nơi các mô hình phần như đơn nhiệm trách nhiệm duy nhất, phụ thuộc và mặt tiền … được ứng dụng để cấu trúc code của bạn. Nguyên tKỷ luật và các quy ước mã được áp dụng bởi các nhóm phát triển là rất quan trọng trong việc duy trì các cơ sở mã lớn.

Tuy nhiên, cân nhắc giảm số lượng Lambda functions, cập nhập cấu hình hoặc triển khai tiêu chuẩn mới thông qua nhiều API có thể đạt được dễ dàng hơn so với tiếp cận chịu đơn nhiệm trách nhiệm duy nhất.

Hơn nữa, mỗi yêu cầu gọi liên quan đến các Lambda function giống nhau cho mỗi hành động HTTP. , Do đó, có khả năng cao hơn các phần sử dụng mã của bạn có thời gian phản hồi tốt hơn vì môi trường thực thi có khả năng sẵn sàng cao hơn để đáp ứng yêu cầu.

Nhân tố khác cần cân nhắc là kích thước hàm function. Nhân tố Điều này tăng lên khi tập hợp các hành động trong cùng một hàm  với tất cả phần phụ thuộc và logic làm việc kinh doanh của API. Điều này có thể ảnh hưởng đến sự khởi động nguội bắt đầu lạnh của Lambda function với khối lượng công việc tăng đột biến . Khách  hàng nên đánh giá lợi ích của hướng tiếp cận này. Đặc biệt khi các ứng dụng có SLA hạn chế, sẽ bị ảnh hưởng khi khởi động nguội. Nhà phát triển có thể giảm thiểu vấn đề này bằng cách chú ý đến phụ thuộc được sử dụng và triển khai công nghệ giống như tree-shaking, rút gọn và loại bỏ mã, nơi ngôn ngữ chương trình cho phép. 

Cách tiếp cận không cho phép bạn điều chỉnh cấu hình chức năng một cách riêng lẻ. Nhưng bạn phải tìm cấu hình phù hợp với toàn bộ tất cả khả năng của mã với kích thước bộ nhớ cao và các quyền bảo mật lỏng lẻo hơn với khả năng có thể xung đột với các yêu cầu do đội ngũ nhóm bảo mật xác định

Đọc và ghi hàm functions

Có  Cả hai cách tiếp cận này đều có các mặt lợi và hạisự đánh đổi, nhưng có sự lựa chọn thứ ba có thể kết hợp lợi ích giữa chúng.

Thông thường, lưu lượng API nghiêng về số lần đọc hoặc ghi nhiều hơn và điều đó buộc các nhà phát triển tối ưu mã và cấu hình nhiều hơn so ở một bên so với bên kia.

Ví dụ, cân nhắc xây dựng một người dùng API cho phép người tiêu dùng tạo, cập nhật, xóa một người dùng nhưng cũng tìm một người dùng hoặc một danh sách những người dùng. Trong trường hợp này, bạn có thể thay đổi từng người dùng một mà không cần thao tác hàng loạt, nhưng bạn có thể nhận được một hoặc nhiều người dùng cho mỗi yêu cầu API. Việc chia thiết kế API thành các hoạt động đọc và ghi dẫn đến kiến ​​trúc này:  

Sự gắn kết của mã cho ghi (create, update, and delete)  là lợi ích cho nhiều lí do. Ví dụ, bạn có thể xác thực nội dung yêu cầu, đảm bảo nó chưa tất cả tham số bắt buộc. Nếu khối lượng công việc ghi nặng thì các thao tác được sử dụng ít hơn (ví dụ xóa) sẽ được hưởng lợi từ thực thi môi trường ấm. Ví dụ: colocation cho phép tái sử  dụng mã trên các hành động tương tự, giảm tải cấu trúc của dự án với thư viện hoặc Lambda layer.

Khi nhìn vào khía cạnh hoạt động đọc, bạn có thể giảm mã đi kèm với function, giúp khởi động nguội nhanh hơn, và tối ưu hóa hiệu suất đáng kể với hoạt động ghi. Bạn cũng có thể lưu trữ một phần hoặc toàn bộ kết quả truy vấn trong bộ nhớ của môi trường thực thi để cải thiện thời gian thực thi của Lambda function.

Cách tiếp cận này giúp bạn tiến xa hơn so với bản chất tiến hóa của nó. Hãy tưởng tượng nếu nền tảng này trở nên phổ biến hơn nhiều. Bây giờ, bạn phải tối ưu hóa API hơn nữa, bằng cách cải thiện khả năng đọc và thêm mô hình bộ nhớ  cache bằng ElasticCache và Redis. Hơn nữa, bạn đã quyết định tối ưu hóa các truy vấn đọc bằng cơ sở dữ liệu thứ hai được tối ưu hóa cho khả năng đọc khi thiếu bộ nhớ tạm.

Từ góc độ của việc Về mặt viết, bạn đã đồng ý với người sử dụng tiêu dùng API rằng việc nhận và thừa nhận việc tạo hoặc xóa người dùng là phù hợp, vì họ hoàn toàn chấp nhận bản chất nhất quán cuối cùng của các hệ thống phân tán.

Giờ đây, bạn có thể cải thiện thời gian phản hồi của thao tác ghi bằng cách thêm hàng đợi SQS trước hàm Lambda. Bạn có thể cập nhật cơ sở dữ liệu ghi theo đợt để giảm số lượng lệnh gọi cần thiết để xử lý các thao tác ghi thay vì xử lý từng yêu cầu riêng lẻ.

Phân chia trách nhiệm truy vấn lệnh (CQRS)  là một mô hình mẫu được thiết lập tốt để tách đột biến dữ liệu hoặc phần lệnh của hệ thống khỏi phần truy vấn. Bạn có thể sử dụng mẫu CQRS để phân tách các bản cập nhật và truy vấn nếu chúng có các yêu cầu khác nhau về thông lượng, độ trễ hoặc tính nhất quán.

Mặc dù không bắt buộc phải bắt đầu với mẫu CQRS đầy đủ, nhưng bạn có thể phát triển từ cơ sở hạ tầng được nêu bật đánh dấu dễ dàng hơn trong quá trình triển khai đọc và ghi ban đầu mà không cần phải tái cấu trúc lớn API.

So sánh 3 cách tiếp cận

Dưới đây là so sánh giữa ba cách tiếp cận:

Đơn nhiệmLambda-lithĐọc và viết
Lợi íchTách biệt mối quan tâm mạnh mẽ.Cấu hình chi tiết.Gỡ lỗi tốt hơn.Thời gian thực hiện nhanh chóng.Ít lệnh khởi động nguội hơn.Gắn kết mã cao hơn.Bảo trì đơn giản.Sự gắn  kết mã khi cần.Kiến trúc tiến hóa.Tối ưu hóa việc đọc và ghi.
Vấn đềSao chép code.Duy trì phức tạp.Gọi khởi động nguội cao hơn.Cấu hình hạt Course.Thời gian khởi động nguội cao hơn.Sử dụng CQRS với 2 models dữ liệu.CQRS bổ sung  tính nhất quán cho hệ thống.

Kết luận

Các nhà phát triển thường chuyển đổi di chuyển từ các hàm đơn trách nhiệm đến Lambda-lith khi kiến trúc của họ phát triển, nhưng cả hai cách tiếp cận đều có sự đánh đổi. Bài đăng này cho thấy cách tận dụng tối đa cả hai phương pháp bằng cách chia khối lượng công việc của bạn cho mỗi thao tác đọc và ghi.

Cả ba phương pháp đều khả thi để thiết kế API serverless và việc hiểu những gì bạn đang tối ưu hóa là chìa khóa để đưa ra quyết định tốt nhất. Hãy nhớ rằng, việc hiểu ngữ cảnh và các yêu cầu kinh doanh để thể hiện trong ứng dụng sẽ giúp bạn đạt được sự đánh đổi có thể chấp nhận được để chỉ định bên trong một khối lượng công việc cụ thể. Hãy luôn cởi mở và tìm ra giải pháp giải quyết vấn đề, đồng thời cân bằng giữa bảo mật, trải nghiệm của nhà phát triển, chi phí và khả năng bảo trì.

Để biết thêm tài nguyên học tập serverless, hãy truy cập Serverless Land .

TAGS: đã đóng góp, serverless.

Link Blog: https://aws.amazon.com/blogs/compute/comparing-design-approaches-for-building-serverless-microservices/