# Cloud Design Pattern ## CQRS ![image](https://hackmd.io/_uploads/BJHxc7Qlyg.png) ### Vấn đề: - Do nhu cầu giữa việc truy vấn dữ liệu và thao tác với dữ liệu khác nhau. Một ứng dụng có thể thực hiện cả triệu câu query GET hằng ngày nhưng chỉ thao tác insert vài câu. - Dữ liệu có thể bị xung đột khi thực hiện song song cả truy vấn và thao tác. - Sự khác biệt về quyền được thực hiện Commands và Queries khác nhau đôi khi cũng làm services trở nên phức tạp. ### Giải pháp: - Chia tách Command và Query ra thành 2 phần khác biệt. Command bao gồm HTTP method: POST, PUT, DELETE và PATCH. Query chỉ có GET method - lấy data từ DB. - Trường hợp có nhiều commands, các commands sẽ đem vào hàng đợi để thực hiện bất đồng bộ. - Phần Queries chỉ thực hiện truy vấn dữ liệu, không bao giờ thao tác trên dữ liệu. Dữ liệu trả về theo kiểu DTO không giới hạn. Tuỳ thuộc dữ liệu muốn lấy. ## Throttling ### Vấn đề Lượng truy cập có thể ít hoặc nhiều tùy thời điểm, nếu lượng request vượt quá số lượng tài nguyên hiện có có thể gây ra hiệu năng kém. ### Giải pháp - sử dụng autoscaling. - Giới hạn ngưỡng yêu cầu tài nguyên, nếu ứng dụng sử dụng tài nguyên quá ngưỡng đặt ra, giới hạn lại truy cập. - Từ chối yêu cầu của người dùng đã yêu cầu quá nhiều lần. - Vô hiệu hóa hoặc giảm chức năng của các dịch vụ không quá quan trọng để nhường tài nguyên. - Delay xử lý request, báo cho người dùng thử lại sau. - Priority Queue: Ưu tiên xử lý công việc của nhóm người dùng có giá trị cao. - Priority Queue, Queue-based Load Leveling pattern. ## Ambassador ![image](https://hackmd.io/_uploads/BJAglXQeye.png) - Một số chức năng phân tán với service thì làm riêng. - Lời gọi các service đều được thực hiện qua ambassador này. - Nằm ở phía với client - Mục đích: Cung cấp cho ta một interface thống nhất để các ứng dụng của ta có thể truy cập được các ứng dụng bên ngoài một cách dễ dàng nhất. - Vấn đề: - Khi ta cần truy cập các ứng dụng khác mà có các cách truy cập khác nhau, thì ta phải viết các đoạn code riêng lẻ để truy cập được nó. - Ví dụ là khi ta cần truy cập ứng dụng cache, đối với memcached thì ta không cần phải thực hiện authentication, nhưng với redis thì ta lại cần authentication. - Giải pháp: - Thay vì ta phải viết code để thực hiện các giao thức khác nhau cho các ứng dụng khác nhau bên trong application, thì ta nên tách việc đó cho một container khác. Sau đó bên trong ứng dụng ta chỉ cần kết nối tới container này thông qua một interface duy nhất, còn công việc kết nối với các ứng dụng khác như thế nào thì để container này nó lo, container này được gọi là Ambassador Container. - Ambassador cũng giống như Sidecar pattern, nhưng khác biệt ở điểm là Ambassador sẽ không hỗ trợ các chức năng cho main container mà nó đóng vai trò là một proxy để main container truy cập các ứng dụng bên ngoài một cách dễ dàng. ## Anti-Corruption Layer ![image](https://hackmd.io/_uploads/S17qW77gyl.png) - ACL giúp đảm bảo rằng sự phức tạp, không nhất quán, hoặc các vấn đề phát sinh từ hệ thống bên ngoài không ảnh hưởng xấu đến hệ thống nội bộ. - ACL đóng vai trò như một lớp đệm giữa hệ thống và các hệ thống bên ngoài, giúp tránh việc phải thay đổi hệ thống khi hệ thống bên ngoài thay đổi. - ACL đảm bảo việc giao tiếp giữa hệ thống A và B thuận lợi. ## Backend for frontend ![image](https://hackmd.io/_uploads/BkJ0QQ7lkl.png) - Vấn đề: - Mỗi loại ứng dụng người dùng (như web, mobile, hay các ứng dụng khác) có những yêu cầu đặc thù về dữ liệu hoặc cách thức giao tiếp với backend khác nhau. - Mong muốn hỗ trợ giao diện trên nhiều nền tảng thì ở phía backend tạo ra các API generic để dễ dàng tích hợp sau này, ở phía góc độ người triển khai thấy rằng điều đó sẽ làm tiết kiệm thời gian và giảm đi chi phí triển khai hỗ trợ nền tảng mới. - Nhưng càng phát triển thì chúng ta càng nhồi nhét nhiều thứ vào một nơi. Vì lúc này hệ thống đòi hỏi phải hỗ trợ được nhiều nền tảng hơn, mà mỗi nền tảng lại yêu cầu đặc điểm dữ liệu trả về có những yêu cầu cụ thể - Ví dụ: Trên thiết bị di động không cần thiết trả về nhiều dữ liệu giống như trên nền tảng web desktop vì điều đó đơn giản là tài nguyên trên thiết bị di động không có nhiều giống như trên desktop. - Giải pháp: - Sử dụng 1 backend chuyên biệt cho từng loại ứng dụng frontend => hỗ trợ đa nền tảng. ## Bulkhead * ![image](https://hackmd.io/_uploads/S1mwVQ7g1e.png) - Bulkhead chia các thành phần hoặc tài nguyên thành các nhóm khác nhau để giới hạn lỗi hoặc quá tải ở 1 khu vực lên toàn bộ hệ thống. - Giải pháp: - Mô hình vách ngăn là một thiết kế giúp một hệ thống vẫn hành tốt khi lỗi xảy ra ở một service con. Trong một kiến trúc vách ngăn, các yếu tố của một hệ thống được phân lập thành các nhóm để nếu một service bị lỗi, những services khác sẽ tiếp tục hoạt động. - Nếu thân tàu bị xâm nhập, chỉ có phần bị hư hỏng chứa nước, ngăn tàu bị chìm. ## Cache-aside (Redis) ![image](https://hackmd.io/_uploads/S1JgI77eJx.png) - Sử dụng bộ nhớ đệm (cache) để tăng hiệu năng và giảm tải cho hệ thống. - Mọi operation read-through write-through phải qua cache trước khi làm việc với database. - Cách hoạt động: - Kiểm tra data có trong cache không. - Nếu cache không có, lấy dữ liệu từ database, và rồi lưu lại trong cache. ### Ưu điểm - Yêu cầu tài nguyên khó đoán trước. ## Queue-based load leveling ![image](https://hackmd.io/_uploads/SJfVYQQlke.png) - Vấn đề: Load request lúc thấp lúc cao. Nếu 1 service phải chịu tải quá nặng, có thể gây ra vấn đề hiệu năng và an toàn. - Giải pháp: - Sử dụng một queue giữa các tác vụ và service. - Đưa hết request vào message queue để xử lý đều đặn hơn, giữ cân bằng tải. - Service giải quyết tác vụ tùy vào khả năng chính nó, không phụ thuộc vào số lượng request nhận vào đang nhiều hay ít. - Client có thể thấy hơi chậm nhưng chắc chắn sẽ được xử lý. ## Event Sourcing ![image](https://hackmd.io/_uploads/SkG357XgJx.png) - Vấn đề: - Hầu hết ứng dụng quản lý data bằng cách lưu trạng thái hiện tại của data đó -> CRUD. - Vấn đề của CRUD: việc update có thể làm giảm hiệu năng, tốc độ phản hồi, và có thể bị conflict. - Giải pháp: - Thay vì lưu trạng thái cuối cùng thì lưu các sự kiện dẫn tới trạng thái đó. - Mỗi thay đổi trong hệ thống coi là một sự kiện và lưu vào một event store. - Lưu lại các sự kiện theo dạng append -> xem lại được. ### Ưu điểm - Lưu trữ lịch sử theo dõi mọi sự kiện trong quá khứ. - Khả năng khôi phục trạng thái: Cho phép hệ thống quay lại trạng thái tại một thời điểm cụ thể bằng cách phát lại các sự kiện. - Cải thiện tính nhất quán: Mỗi thay đổi được ghi nhận dưới dạng sự kiện độc lập, từ đó dễ dàng đảm bảo tính nhất quán cho các thay đổi theo trình tự thời gian. ## Sharding ![image](https://hackmd.io/_uploads/SJ8BgNXxJg.png) ### Vấn đề - Sử dụng một cơ sở dữ liệu duy nhất phải lo nhiều vấn đề: Không gian lưu trữ bị giới hạn, tài nguyên tính toán, băng thông, vị trí địa lý. ### Giải pháp - Chia cơ sở dữ liệu thành các shard, mỗi shard sẽ lưu trữ một phần dữ liệu của toàn hệ thống, giúp giảm tải cho từng máy chủ lưu trữ. ### Lợi ích - Mở rộng hệ thống dễ dàng bằng cách thêm các shard. - Tăng hiệu suất truy vấn dữ liệu. ## Gateway Aggregation * ![image](https://hackmd.io/_uploads/rkZI_QclJl.png) - Nằm giữa client-service, gateway nằm về phía service. - 1 ông CPhi gọi về VN 10 lần -> chỉ báo về 1 lần. - Chỉ cần gọi 1 lần ở gateway. ### Vấn đề - Client cần gửi nhiều yêu cầu độc lập đến từng dịch vụ để lấy đủ dữ liệu cần thiết, điều này làm tăng số lượng yêu cầu, và việc xử lí từng yêu cầu này có thể gây lỗi và request xử lí không đầy đủ. ### Giải pháp - Nằm giữa client-service, gateway nằm về phía service. - Ứng dụng gửi yêu cầu tới Gateway, gateway bóc tách yêu cầu này và chia thành các yêu cầu nhỏ hơn đưa tới các service tương ứng. Các service này trả lại phản hồi về gateway, gateway sẽ gộp các phản hồi và trả về phản hồi đầy đủ cho ứng dụng. ### Dùng khi nào - Một client cần giao tiếp với nhiều dịch vụ backend để thực hiện một thao tác. - Client có thể sử dụng các mạng có độ trễ đáng kể, chẳng hạn như mạng di động. ## Gateway Routing ![image](https://hackmd.io/_uploads/r1eQTQQxyx.png) ### Giải pháp - Cho phép định tuyến (route) các yêu cầu từ client đến các dịch vụ backend thích hợp thông qua một API Gateway. - Chọn instance phù hợp nhất để chuyển request qua. - Mọi request chuyển tới gateway -> gateway sẽ định vị tới service tương ứng. ### Lợi ích - Quản lý tất cả request tại một điểm duy nhất. - Client không cần biết chi tiết cách thức backend hoạt động, chỉ cần gửi yêu cầu tới gateway. - Khi muốn mở rộng hệ thống hoặc thay đổi dịch vụ backend, chỉ cần chỉnh sửa ở API gateway. ## Gateway Offloading - Di chuyển một số xử lý hoặc chức năng chung từ các dịch vụ backend sang một API Gateway. - Tổng hợp nhiều chức năng thành 1, đặt ở gateway -> chỉ cần gọi vào chức năng đó thay vì gọi từng chức năng. - Client gửi yêu cầu, yêu cầu sẽ đến gateway. Gateway thực hiện ngay một số xử lý chung của các backend phía sau, như xác thực, chuyển định dạng, throttling, quản lý giám sát,... ### Lợi ích - Giảm thiểu công việc cho các dịch vụ backend. - Cho phép 1 team chỉ tập trung làm các vấn đề liên quan tới quản lý như bảo mật, 1 team kia chỉ tập trung vào các chức năng chính, cách hoạt động của backend. - Giúp dễ dàng quản lý các chính sách, giới hạn lưu lượng, và các thao tác bảo trì mà không ảnh hưởng đến dịch vụ backend. ## Gatekeeper ![image](https://hackmd.io/_uploads/B1ikGNqx1e.png) - Chức năng kiểm soát và quản lý quyền truy cập vào các dịch vụ hoặc tài nguyên trong hệ thống. - Có 1 service trung gian để bảo vệ service bên trong, quyết định xem một yêu cầu có được phép truy cập dịch vụ backend hay không. - Kiểm tra thông tin xác thực của client, kiểm soát rủi ro và rò rỉ dữ liệu. - Thiên hướng security, đóng vai trò như một firewall. ## Circuit Breaker ![image](https://hackmd.io/_uploads/H1nLu7mekl.png) Trường hợp: Service bị lỗi, và cần thời gian sửa -> client gọi phí tài nguyên -> k nên gọi liên tục. - Ngăn application thực hiện 1 request nhiều khả năng thất bại. - Cài đặt phía client. - Cài đặt như 1 state machine 3 trạng thái: - Khi ở trạng thái bình thường thì mạch được đóng, tức là mạch đang ở trạng thái Closed, các yêu cầu đang được xử lý tự do và mọi thứ hoạt động như mong đợi. Trạng thái Closed sẽ chuyển qua Open nếu như số lượng các yêu cầu được xử lý tăng đột biến hoặc một lỗi xảy ra vượt quá ngưỡng mà mạch đề ra. - Khi đó, mạch sẽ tự ngắt, lúc này mạch bị “hở” và nó đang ở trạng thái Open. Các yêu cầu sau đó sẽ không thể tiếp tục thực hiện cuộc gọi đến dịch vụ phân tán. Thay vào đó mạch sẽ trả về lỗi ngay lập tức để báo cho chúng biết để không mất thời gian chờ xử lý. - Sau một khoảng thời gian nhất định, mạch sẽ thử mở lại cho một vài yêu cầu để kiểm tra tính khả dụng của các dịch vụ. Trạng thái này gọi là Half-Open. Nếu mọi thứ bình thường trở lại, mạch sẽ trở về trạng thái Closed, lúc này hệ thống của chúng ta sẽ trở lại như ban đầu. Còn nếu không, mạch sẽ tiếp tục Open cho đến lần kiểm tra tiếp theo. ## Rate Limiting ![image](https://hackmd.io/_uploads/Sk92A7XxJx.png) - Trường hợp: Service chỉ handle được 1 lượng request nhất định. ### GIải pháp - Kiểm soát số lượng yêu cầu mà một client hoặc một nhóm client có thể gửi đến một dịch vụ trong một khoảng thời gian nhất định. - Điều chỉnh 1 rate để gọi service ấy. - Ví dụ 1 dịch vụ có thể throttle dựa trên: - Số lượng operations (VD 20 request/sec) - Số lượng data (VD 2Gb/minute) - Chi phí của operation (20,000 RUs per second). - Cài đặt phía client. ## Sidecar * Vấn đề dẫn đến pattern ấy * Thiết kế như nào * Ưu/nhược ### Khái niệm Sidecar Pattern: sử dụng một container phụ bao gồm các chức năng phụ (thu thập, xử lí log, quản lí container) để hỗ trợ cho container chính ### Thiết kế như nào? Ứng dụng chính thực hiện các chức năng cốt lõi, trong khi sidecar bổ sung các chức năng không liên quan trực tiếp đến logic của ứng dụng chính, nhưng cần thiết để đảm bảo hoạt động ổn định và bảo mật cho hệ thống (quản lý cấu hình, ghi log, bảo mật, quản lý mạng). Sidecar triển khai trên cùng môi trường với ứng dụng chính, hoạt động song song, giao tiếp với nhau dễ dàng. ### Ưu/Nhược Ưu: - Bằng cách tách các chức năng phụ vào các container sidecar, mỗi microservice có thể tập trung hoàn toàn vào logic chính của mình. - Sidecar có thể được triển khai hoặc thay đổi mà không làm gián đoạn hoạt động của dịch vụ chính. - Sidecar và ứng dụng chính có thể sử dụng ngôn ngữ và framework khác nhau. Nhược: - Primary Application bị sao -> Sidecar ảnh hưởng - Chi phí deploy sidecar có thể đáng kể đối với ứng dụng nhỏ - Giao tiếp giữa ứng dụng chính và sidecar có thể có độ trễ, đặc biệt là trong lời gọi hàm, API. - Khi có dịch vụ scale độc lập với ứng dụng chính -> triển khai như 1 dịch vụ riêng biệt ## Competing Comsumers ![image](https://hackmd.io/_uploads/SJR4_XmgJg.png) ### Vấn đề: - 1 instance có thể xử lí rất nhiều requests và bị quá tải -> chạy rất chậm. - Có nhiều tác vụ có thể chạy song song. - Workload có thể được chia ra thành các task chạy cùng lúc. ### Giải pháp Mô hình Competing Consumers cho phép nhiều consumer đồng thời xử lý các thông điệp nhận được trên cùng một kênh truyền thông tin. Có thể có nhiều instance của consumer C, cùng cạnh tranh để nhận các thông điệp từ cùng một hàng đợi (queue). Chúng sẽ xử lý đồng thời nhiều thông điệp hơn để giải phóng hàng đợi nhanh hơn. (Ông nào có khả năng xử lý thì nhận thêm việc mà xử lý) Khi một thông điệp có sẵn trên hàng đợi, bất kỳ consumer nào cũng có thể nhận được nó. Cách thức mà hệ thống truyền thông tin quyết định consumer nào sẽ nhận thông điệp phụ thuộc vào việc triển khai hệ thống, nhưng về bản chất, các consumer sẽ cạnh tranh với nhau để trở thành người nhận thông điệp. ### Ưu điểm - Scalability: Có thể scale bằng cách tăng giảm độ dài của queue - Reliability: Nếu tất cả comsumers đều đã có việc, thông điệp vẫn sẽ được gửi vào queue -> hệ thống vẫn chạy 1 phần - Nếu 1 message bị xử lí lỗi bởi 1 comsumer -> gửi trả lại vào queue để 1 comsumer khác xử lí ## Priority Queue ![image](https://hackmd.io/_uploads/SJlK677lye.png) ![image](https://hackmd.io/_uploads/SJoqp7Xekl.png) - Yêu cầu có mức độ ưu tiên cao hơn được đẩy lên xử lý trước, bất kể khi nào đến. - Có 2 cách implement: - Single queues - Multiple queues ### Single queue - Ứng dụng gán mức độ ưu tiên cho từng thông điệp và gửi vào hàng đợi ưu tiên. - Hàng đợi đảm bảo các consumers xử lý thông điệp mức độ ưu tiên cao trước. ### Multiple queue - Ứng dụng gán mức độ ưu tiên cho thông điệp và gửi nó đến hàng đợi gắn với độ ưu tiên đó. - Multiple consumer pools: - Mỗi hàng đợi có các consumers riêng của nó. Hàng đợi có độ ưu tiên cao hơn có nhiều consumers hơn hoặc consumers năng lực xử lý cao hơn. - Single consumer pools: - Mọi hàng đợi chia sẻ chung 1 nhóm consumers. - Chỉ xử lý yêu cầu trong hàng đợi thấp khi đã xử lý xong hết yêu cầu trong hàng đợi cao. ## Pub/Sub ![image](https://hackmd.io/_uploads/ryHj5cDryg.png) - Vấn đề: các thành phần hệ thống thường cần cung cấp thông tin cho các thành phần khác khi có các sự kiện diễn ra trong hệ thống. - Sử dụng message queue ban đầu ổn, nhưng không scale tốt khi sử dụng mỗi message queue cho 1 consumer. Và 1 consumer đôi khi chỉ cần 1 phần thông tin. - Thành phần: - Publisher: Thành phần gửi message. - Subcriber: Thành phần nhận thông tin (consumers). - Input channel: Publisher đóng gói events thành các message rồi gửi vào đây. - Message broker: routing cho messages, copy message từ publishers và gửi đến đúng subcribers cần nó. - Mỗi consumer có 1 output channel. - Ưu điểm: - Khả năng mở rộng - Đáng tin cậy - Loose Coupling: giao tiếp thông qua thành phần trung gian (Topic) giúp chúng hoạt động một cách độc lập và không ảnh hưởng tới nhau. ## Claim-Check ![image](https://hackmd.io/_uploads/HyTpzoPHke.png) - Vấn đề: Các kênh giao tiếp truyền thông tin như message queue có thể giới hạn kích thước message, việc gửi một dữ liệu lớn (payload lớn) có thể vượt quá giới hạn này, làm nghẽn băng thông, giảm performance của hệ thống khi message queue chứa dữ liệu lớn đó. - Giải pháp: - Lưu dữ liệu thực (payload) hoặc thông tin chi tiết ở một database bên ngoài. - Tạo 1 claim-check token chứa reference to payload đó và gửi nó qua message queue cho ứng dụng cần. - Ứng dụng nhận được token dùng tham chiếu trên nó để lấy payload từ database bên ngoài. # Quality Attribute ## Usability - Tính dễ sử dụng của hệ thống, đáp ứng trải nghiệm người dùng. - Ví dụ: Cần quá nhiều tương tác để thực hiện 1 hành động là 1 trải nghiệm ko tốt. ## Availability - Sự có mặt, có thể phục vụ khi cần của hệ thống. - Thể hiện ở khoảng thời gian hệ thống hoạt động / năm. ## Performance - Khả năng đáp ứng của hệ thống với yêu cầu bên ngoài. ## Reliability - Khả năng hệ thống hoạt động ổn định khi các hệ thống liên quan bên ngoài có vấn đề (database, network,...) ## Security - Khả năng hệ thống giữ bảo mật và giảm thiểu rủi ro ảnh hưởng. - Ví dụ: Khả năng hệ thống nhận biết và phản hồi với DDoS, authentication cho người dùng, SQL injection,... ## Maintainability - Khả năng hệ thống thay đổi một vài thành phần mà hệ thống tổng không bị ảnh hưởng ## Integrability - Khả năng hệ thống tương tác và cùng hoạt động với các hệ thống khác. ## Scalability - Khả năng hệ thống dễ dàng mở rộng tải mà không ảnh hưởng performance