← Về danh sách
Hạ tầng & Đám mây
#서비스 디스커버리#서비스 레지스트리#마이크로서비스#헬스체크#클라이언트/서버 사이드
Cập nhật lần cuối · 2026-10-12

Service Discovery và việc dò tìm vị trí dịch vụ động

1. Tổng quan

A. Định nghĩa

Service Discovery là cơ chế tự động đăng ký, dò tìm và cập nhật vị trí mạng (IP·cổng) của các thực thể dịch vụ thường xuyên thay đổi trong môi trường microservice·phân tán, thông qua một registry trung tâm hoặc cơ chế tra cứu, để bên gọi có thể gửi yêu cầu tới một thực thể khỏe mạnh mà không cần địa chỉ được mã hóa cứng.

Service discovery là bộ máy giải quyết tự động câu hỏi "ai đang ở đâu" tại thời điểm chạy. Trước đây, khi hệ thống chạy trên một máy chủ duy nhất, ứng dụng có thể cố định địa chỉ của cơ sở dữ liệu hay dịch vụ khác trong tệp cấu hình mà không gặp vấn đề gì. Nhưng trong môi trường hiện đại nơi hàng chục·hàng trăm dịch vụ liên tục được tạo ra và hủy đi trên container, địa chỉ thay đổi theo từng phút, nên cách quản lý vị trí bằng con người không còn hiệu lực. Service discovery để hệ thống tự theo dõi thay đổi này, giảm độ kết dính giữa bên gọi và bên bị gọi.

Một phân biệt quan trọng ở đây là discovery không phải một "sổ địa chỉ" đơn thuần mà là một sổ địa chỉ động phản ánh trạng thái. Registry quản lý không chỉ sự tồn tại của thực thể mà còn cả việc thực thể đó hiện có thể nhận yêu cầu hay không (tình trạng sức khỏe). Khi giải thích discovery, Kỹ sư chuyên nghiệp nên xử lý đồng thời bốn trục là đăng ký·tra cứu·kiểm tra sức khỏe·lan truyền, và trình bày các đánh đổi giữa tính nhất quán và tính khả dụng phát sinh ở mỗi trục.

B. Bối cảnh ra đời và sự cần thiết

Trong kiến trúc monolithic truyền thống, các thành phần nằm trong một tiến trình duy nhất nên tìm thấy nhau qua lời gọi hàm. Ngay cả khi tách thành cấu trúc 3 lớp, địa chỉ của các lớp web·ứng dụng·DB tương đối cố định, nên việc quản lý thủ công bộ cân bằng tải (VIP) và bản ghi DNS là đủ. Tuy nhiên khi chuyển sang microservice, số lượng dịch vụ tăng mạnh, mỗi dịch vụ được triển khai·mở rộng độc lập, và việc autoscaling cùng điều phối container thay thế thực thể liên tục. Ví dụ trong Kubernetes, Pod gần như luôn nhận IP mới khi khởi động lại, nên địa chỉ của hôm qua có thể trỏ tới một dịch vụ hoàn toàn khác vào hôm nay.

Khăng khăng quản lý thủ công sẽ tạo ra hai vấn đề. Thứ nhất, người vận hành không theo kịp thay đổi địa chỉ nên trôi dạt cấu hình (configuration drift) tích tụ, và lưu lượng chảy tới thực thể đã chết, lan truyền sự cố. Thứ hai, vì phải thay đổi cấu hình bên gọi mỗi lần triển khai, nên nảy sinh kết dính triển khai giữa các dịch vụ, xóa đi lợi ích bản chất của microservice là triển khai độc lập. Service discovery tập trung hóa thông tin vị trí thành nguồn chân lý tại thời điểm chạy (Source of Truth) và lan truyền thay đổi tự động, giải quyết đồng thời cả hai vấn đề.

Như một trường hợp thực tế, Netflix đã giới thiệu registry riêng là Eureka trong môi trường nơi hàng nghìn thực thể liên tục bị thay thế, thiết kế để client định kỳ nhận danh sách các thực thể khả dụng và chọn cục bộ trong số đó. Đây là lựa chọn ưu tiên tính khả dụng để sự cố của điểm tra cứu trung tâm không chặn toàn bộ lời gọi, cho thấy thiết kế discovery gắn trực tiếp với chiến lược cô lập sự cố.

C. Đặc tính cốt lõi

Thứ nhất, tính động. Việc tạo·hủy·đổi địa chỉ của thực thể phải được phản ánh vào registry gần như theo thời gian thực. Thứ hai, nhận biết sức khỏe. Kết quả tra cứu phải chỉ chứa các thực thể hiện có thể xử lý yêu cầu, chứ không chỉ là các thực thể đã đăng ký. Thứ ba, tính trong suốt về vị trí. Bên gọi tham chiếu đối tác bằng tên dịch vụ logic thay vì địa chỉ vật lý, ủy thác việc quyết định địa chỉ thực tế cho lớp discovery. Thứ tư, khả năng mở rộng và cô lập sự cố. Bản thân registry phải được trang bị sao chép·bộ nhớ đệm·suy giảm duyên dáng (graceful degradation) để không trở thành nút thắt cổ chai hay điểm lỗi đơn.

2. Cấu trúc tổng thể và mô hình vận hành

Service discovery gồm ba vai trò — bên cung cấp dịch vụ (Provider), registry dịch vụ (Registry), và bên tiêu thụ dịch vụ (Consumer) — cùng các luồng đăng ký·tra cứu·kiểm tra sức khỏe nối chúng lại. Bên cung cấp đăng ký vị trí và siêu dữ liệu của mình vào registry khi khởi động, còn bên tiêu thụ truy vấn registry để lấy đích gọi. Registry liên tục xác minh sự sống của các thực thể đã đăng ký và loại bỏ khỏi danh sách các thực thể không phản hồi.

graph TD
  subgraph Providers["Thực thể bên cung cấp dịch vụ"]
    P1["Dịch vụ đặt hàng #1 (10.0.1.5:8080)"]
    P2["Dịch vụ đặt hàng #2 (10.0.1.7:8080)"]
    P3["Dịch vụ thanh toán #1 (10.0.2.3:9090)"]
  end
  REG["Registry dịch vụ<br/>(thông tin đăng ký + trạng thái sức khỏe)"]
  subgraph Consumers["Bên tiêu thụ dịch vụ"]
    C1["API Gateway"]
    C2["Frontend BFF"]
  end
  P1 -->|"① đăng ký/heartbeat"| REG
  P2 -->|"① đăng ký/heartbeat"| REG
  P3 -->|"① đăng ký/heartbeat"| REG
  REG -->|"② kiểm tra sức khỏe"| P1
  REG -->|"② kiểm tra sức khỏe"| P2
  C1 -->|"③ tra cứu(tên dịch vụ)"| REG
  C2 -->|"③ tra cứu(tên dịch vụ)"| REG
  C1 -->|"④ gọi(thực thể khỏe mạnh)"| P1
  C2 -->|"④ gọi(thực thể khỏe mạnh)"| P3

Điểm cốt lõi trong cấu trúc trên là ①~④ được tách lỏng và mỗi cái vận hành theo nhịp riêng. Đăng ký và heartbeat do bên cung cấp chủ trì, kiểm tra sức khỏe do registry chủ trì, còn tra cứu do bên tiêu thụ thực hiện khi cần. Nhờ sự tách lỏng này, độ trễ trên một trục không lập tức chặn trục khác, nhưng đồng thời nảy sinh độ lệch thời gian giữa các trục khiến thông tin cũ (stale) có thể bị tra cứu tạm thời. Do đó phía bên tiêu thụ cần một lưới an toàn (thử lại·circuit breaker) không tin mù quáng vào kết quả tra cứu và thử lại trên một thực thể khác khi lời gọi thất bại.

Thông tin lưu trong registry không chỉ giới hạn ở IP·cổng. Các siêu dữ liệu như tên dịch vụ, phiên bản, vùng khả dụng (AZ)·region, trọng số, giao thức, trạng thái (UP/DOWN/STARTING/OUT_OF_SERVICE) được lưu cùng nhau để bên tiêu thụ có thể chọn thực thể theo mức độ gần về địa lý hay tỷ lệ canary. Ví dụ, ưu tiên chọn thực thể trong cùng AZ có thể giảm chi phí và độ trễ của lưu lượng liên-AZ, và để triển khai chính sách như vậy thì registry phải giữ siêu dữ liệu vị trí.

3. Các phương thức discovery: client-side vs server-side

Discovery được chia thành client-side và server-side tùy theo "nơi nào chọn thực thể". Vì phân biệt này gắn trực tiếp với vị trí của trách nhiệm cân bằng tải, nên việc hiểu sự khác biệt giữa hai phương thức cùng với lý do là quan trọng.

Trong discovery client-side, bên tiêu thụ nhận toàn bộ danh sách thực thể từ registry và áp dụng thuật toán cân bằng tải (round robin·có trọng số·ít kết nối nhất, v.v.) cục bộ để gọi trực tiếp. Nó có lợi thế độ trễ thấp vì không có bước nhảy trung gian, và bên tiêu thụ có thể giữ chính sách lựa chọn chi tiết. Mặt khác, logic discovery phải được nhúng vào client của mọi ngôn ngữ·runtime, tạo ra sự phụ thuộc ngôn ngữ và gánh nặng quản lý phiên bản thư viện client. Tổ hợp Netflix Eureka + Ribbon là tiêu biểu, nơi bên tiêu thụ định kỳ polling registry để duy trì bộ nhớ đệm cục bộ.

Trong discovery server-side, bên tiêu thụ chỉ gửi yêu cầu tới một router cố định (bộ cân bằng tải·API Gateway·Service của Kubernetes), và router truy vấn registry rồi chuyển tiếp tới một thực thể khỏe mạnh. Vì bên tiêu thụ không cần biết gì về discovery nên client trở nên đơn giản và trung lập ngôn ngữ, đó là lợi thế lớn. Đổi lại, một bước nhảy gọi là router được thêm vào, dồn gánh nặng quản lý độ trễ và tính khả dụng vào router. Service (IP ảo) + kube-proxy của Kubernetes và AWS ELB là điển hình của phương thức này.

sequenceDiagram
  participant C as Bên tiêu thụ(client)
  participant R as Registry
  participant LB as Router/bộ cân bằng tải
  participant S as Thực thể dịch vụ
  Note over C,S: Client-side
  C->>R: Tra cứu danh sách thực thể theo tên dịch vụ
  R-->>C: Trả về danh sách thực thể khỏe mạnh
  C->>C: Chọn đích bằng cân bằng tải cục bộ
  C->>S: Gọi trực tiếp thực thể đã chọn
  Note over C,S: Server-side
  C->>LB: Yêu cầu theo tên dịch vụ/IP ảo
  LB->>R: Truy vấn thực thể (hoặc bộ nhớ đệm)
  R-->>LB: Danh sách thực thể khỏe mạnh
  LB->>S: Chuyển tiếp sau khi cân bằng tải
  S-->>LB: Phản hồi
  LB-->>C: Trả về phản hồi

Lựa chọn giữa hai phương thức phụ thuộc vào bối cảnh tổ chức. Với bối cảnh một ngôn ngữ·ít nhóm thì client-side có lợi về mặt giảm bước nhảy và kiểm soát chi tiết, nhưng với một nền tảng nhiều ngôn ngữ và nhiều tích hợp bên ngoài thì server-side (hoặc dựa trên sidecar) tốt hơn về tính nhất quán vận hành. Gần đây, như một dung hòa giữa hai phương thức, mẫu hình chuyển discovery và cân bằng tải sang một proxy sidecar bên ngoài ứng dụng (data plane của service mesh) — để client chỉ gọi localhost và proxy thực hiện discovery — đang lan rộng. Đây là nỗ lực lấy đồng thời hiệu năng của client-side và tính trung lập ngôn ngữ của server-side.

4. Các mẫu đăng ký dịch vụ và kiểm tra sức khỏe

A. Các mẫu đăng ký: tự đăng ký vs đăng ký bên thứ ba

Phương thức đăng ký được chia theo ai ghi thực thể vào registry. Tự đăng ký (Self-registration) là việc thực thể dịch vụ tự đăng ký vào registry khi khởi động và hủy đăng ký khi kết thúc, báo sự sống qua heartbeat định kỳ. Nó đơn giản để triển khai và thực thể biết rõ nhất trạng thái của chính nó, nhưng logic discovery xâm nhập vào mã ứng dụng và đăng ký có thể còn sót lại khi kết thúc bất thường.

Đăng ký bên thứ ba (Third-party registration) là việc một trình quản lý đăng ký (Registrar) riêng quan sát các sự kiện triển khai của bộ điều phối và đăng ký·hủy đăng ký thực thể thay cho chúng. Nó có lợi thế lớn là tách mối quan tâm discovery khỏi mã ứng dụng, với Kubernetes là đại diện của phương thức này. Trong Kubernetes, ngay cả khi lập trình viên không viết mã discovery nào, control plane vẫn quan sát vòng đời Pod và tự động cập nhật Endpoints/EndpointSlice.

B. Thiết kế kiểm tra sức khỏe

Kiểm tra sức khỏe là cốt lõi quyết định độ tin cậy của discovery. Phương thức đơn giản nhất là heartbeat, nơi thực thể gửi tín hiệu tới registry theo một chu kỳ cố định, và nếu không có tín hiệu trong một thời gian nhất định thì registry cho hết hạn thực thể đó. Phương thức này có rủi ro cho hết hạn nhầm các thực thể khỏe mạnh khi phân mảnh mạng, nên Netflix Eureka đặt một chế độ tự bảo tồn (self-preservation) tạm hoãn việc hủy đăng ký khi mất hơn một tỷ lệ heartbeat nhất định, để không nhầm vấn đề mạng thành sự cố thực thể.

Phương thức chủ động hơn là việc registry hoặc router định kỳ gọi điểm cuối sức khỏe của thực thể (ví dụ: /health) và xác minh phản hồi. Ở đây việc phân biệt liveness với readiness là quan trọng. Liveness nghĩa là tiến trình có đang sống hay không, còn readiness nghĩa là nó đã sẵn sàng nhận lưu lượng ngay bây giờ hay chưa. Một thực thể vừa khởi động mà chưa hoàn tất làm nóng bộ nhớ đệm hay khởi tạo connection pool thì readiness phải là sai dù nó đang sống, và khi đó discovery loại thực thể này khỏi kết quả tra cứu để ngăn lỗi ban đầu. Kubernetes tách livenessProbe·readinessProbe·startupProbe để xử lý tường minh sự khác biệt về ý nghĩa này.

5. So sánh các công nghệ triển khai chính

Các giải pháp service discovery dùng nội bộ cơ chế đồng thuận phân tán hoặc sao chép, và lựa chọn đó quyết định tính chất CAP của chúng (ưu tiên nhất quán CP vs ưu tiên khả dụng AP). Bảng dưới so sánh các công nghệ tiêu biểu, nhưng nên tập trung vào sự khác biệt về triết lý thiết kế hơn là con số.

Công nghệ Đồng thuận/sao chép Tính chất CAP Giao diện tra cứu Đặc điểm
Netflix Eureka Sao chép bất đồng bộ giữa peer AP (ưu tiên khả dụng) REST (bộ đệm client) Chế độ tự bảo tồn, giả định client-side
HashiCorp Consul Raft CP (mặc định) DNS + HTTP Đa DC, kiểm tra sức khỏe phong phú, kho KV
etcd Raft CP gRPC/HTTP (watch) Backend của Kubernetes, nhất quán mạnh
Apache ZooKeeper ZAB CP Client gốc (watch) Trưởng thành nhưng vận hành phức tạp, dựa trên phiên
Kubernetes (CoreDNS+Service) Dựa trên etcd CP (lưu trữ) + bộ đệm DNS + IP ảo Tích hợp sẵn nền tảng, tự động hóa đăng ký bên thứ ba

Nhóm CP (Consul·etcd·ZooKeeper) có thể từ chối ghi hoặc tra cứu ở phân vùng thiểu số khi phân mảnh mạng để giữ tính nhất quán. Ngược lại nhóm AP (Eureka) vẫn phản hồi tra cứu ngay cả khi phân mảnh bằng danh sách (có thể cũ) mà mỗi nút nắm giữ, duy trì tính khả dụng. Bản chất của discovery là "có thể tiếp tục gọi dù với danh sách hơi cũ" thường tốt hơn "chính xác nhưng tra cứu dừng lại", đó là lý do quan điểm cho rằng khuynh hướng AP phù hợp với registry discovery được chấp nhận rộng rãi. Dẫu vậy, ngay cả khi dùng etcd nhất quán mạnh làm kho lưu trữ như trong Kubernetes, đường tra cứu thực tế (bộ đệm CoreDNS·kube-proxy) vẫn bảo đảm tính khả dụng bằng bộ nhớ đệm và phản ánh bất đồng bộ, nên phải phân biệt tính chất CAP của kho lưu trữ với tính chất cảm nhận của data plane.

Ví dụ, nếu một dịch vụ tài chính vận hành nhiều trung tâm dữ liệu chọn Consul, thì tập máy chủ theo từng DC giữ nhất quán bằng Raft trong khi một WAN gateway chuyển tiếp truy vấn liên-DC, thu được đồng thời cô lập sự cố theo vùng và tra cứu liên-DC. Ngược lại, nếu vận hành một pool dịch vụ toàn cầu khổng lồ duy nhất, thì thiết kế ưu tiên khả dụng của dòng Eureka có thể có lợi hơn về mặt ổn định vận hành.

6. Các trường hợp áp dụng và ví dụ thiết kế

Thứ nhất, discovery gốc Kubernetes. Khi một Pod được tạo, kubelet phản ánh IP của các Pod vượt qua readiness probe vào Endpoints/EndpointSlice, và Service cung cấp một ClusterIP ổn định (IP ảo) cùng tên DNS svc.namespace.svc.cluster.local. Bên tiêu thụ chỉ cần gọi một tên dịch vụ như http://order-service, và CoreDNS phân giải tên trong khi kube-proxy phân phối tới các Pod thực tế. Đây là nguyên mẫu của đăng ký bên thứ ba, nơi lập trình viên không cần viết mã discovery nào cả.

Thứ hai, discovery client-side dựa trên Spring Cloud. Một dịch vụ tự đăng ký bằng Eureka Client, và bên tiêu thụ chọn từ danh sách nhận được từ Eureka bằng Spring Cloud LoadBalancer để gọi trực tiếp. Nó phù hợp để giảm bước nhảy và triển khai chính sách lựa chọn chi tiết (ưu tiên vùng, trọng số) khi nhiều dịch vụ nội bộ cùng dùng một ngăn xếp JVM.

Thứ ba, thiết kế tích hợp service mesh. Một mesh như Istio·Linkerd đọc discovery của nền tảng (Endpoints của Kubernetes) để control plane phân phối các endpoint tới từng sidecar (Envoy). Ứng dụng chỉ gọi proxy localhost, và proxy đảm nhận discovery·cân bằng tải·mTLS·thử lại. Theo cách này, hàng trăm dịch vụ đa ngôn ngữ nhận được một chính sách discovery·bảo mật chung mà không cần thay đổi mã.

7. Chuyên sâu: xu hướng mới nhất và thiết kế mở rộng

Discovery gần đây đang tiến hóa theo ba hướng. Thứ nhất, chuyển discovery sang sidecar. Với sự lan rộng của service mesh, discovery·cân bằng tải·xử lý sự cố chuyển sang data plane bên ngoài ứng dụng, và xu hướng đạt đồng thời tính trung lập ngôn ngữ và nhất quán chính sách là rõ rệt. Thứ hai, discovery đa cụm và đa đám mây. Các nỗ lực chuẩn hóa gộp dịch vụ qua nhiều cụm Kubernetes vượt ra ngoài một cụm đơn (ví dụ: Multi-Cluster Service API) kết hợp với cân bằng tải toàn cầu, xử lý failover sang một cụm khác tại lớp discovery khi có sự cố theo vùng. Thứ ba, tinh chỉnh tín hiệu sức khỏe. Vượt ra ngoài phán đoán sống/chết đơn giản, nó phản ánh các tín hiệu chất lượng như độ trễ phản hồi·tỷ lệ lỗi để phân biệt trạng thái degraded (suy giảm hiệu năng) và kết hợp với phát hiện ngoại lệ (outlier detection) hạ trọng số một cách động.

Về mặt thiết kế mở rộng, vì QPS tra cứu của registry tăng theo tích của số lượng dịch vụ·bên tiêu thụ nên bộ nhớ đệm là bắt buộc. Kết hợp bộ đệm cục bộ phía bên tiêu thụ (với một TTL nhất định) và bộ đệm phản hồi phía máy chủ, cùng việc lan truyền thay đổi bằng watch/streaming thay vì polling, có thể hạ đồng thời tải tra cứu và độ trễ phản ánh. Ngoài ra, trong môi trường quy mô lớn, phân phối toàn bộ endpoint tới mọi proxy làm bùng nổ bộ nhớ·tính toán, nên việc giới hạn phạm vi (scoping) — chỉ đẩy xuống một cách chọn lọc các endpoint của những dịch vụ mà bên tiêu thụ thực sự gọi — trở thành chìa khóa của khả năng mở rộng.

8. Các điểm cần cân nhắc và hàm ý

Thứ nhất, lựa chọn tường minh giữa nhất quán và khả dụng. Dữ liệu discovery thường có lợi hơn khi giữ tính khả dụng dù hơi cũ, nên tính chất CAP của registry và TTL bộ đệm phải được thiết kế khớp với mức chịu lỗi của dịch vụ. TTL quá dài mời gọi việc gọi tới thực thể đã chết, còn TTL quá ngắn mời gọi quá tải registry.

Thứ hai, tách ý nghĩa của kiểm tra sức khỏe và thêm cơ chế bảo vệ. Phân biệt liveness·readiness·startup, và đặt vùng đệm tự bảo tồn·chống báo động giả để không nhầm phân mảnh mạng thành sự cố thực thể. Đừng chỉ tin discovery; hãy thiết kế đồng thời thử lại·timeout·circuit breaker phía bên tiêu thụ để hấp thụ các thất bại do danh sách cũ gây ra.

Thứ ba, bảo mật và ranh giới tin cậy. Vì registry là tài sản nhạy cảm chứa toàn bộ topology dịch vụ, nên cần xác thực·phân quyền (ACL) cho đăng ký·tra cứu và mã hóa đường truyền (mTLS). Xác minh danh tính của bên đăng ký là cốt lõi để chặn mối đe dọa một thực thể độc hại tùy tiện đăng ký và cướp lưu lượng (discovery spoofing), và theo quan điểm zero-trust có xu hướng mở rộng sang phân quyền dựa trên danh tính dịch vụ.

Thứ tư, khả năng quan sát vận hành và khóa nhà cung cấp. Độ trễ đăng ký·thất bại tra cứu·tỷ lệ hết hạn·độ tươi của danh sách phải được thu thập thành các chỉ số để theo dõi sức khỏe của chính discovery. Ngoài ra, nếu logic discovery kết dính sâu với một sản phẩm registry cụ thể thì chi phí chuyển đổi tăng, nên về lâu dài ứng dụng có lợi hơn khi hạ sự phụ thuộc triển khai thông qua một lớp trừu tượng discovery (hoặc ủy thác cho nền tảng·mesh).

Thứ năm, chiến lược áp dụng dần dần. Khi di chuyển từ monolithic sang microservice, thay vì giới thiệu một mesh đầy đủ ngay từ đầu, cách tiếp cận bắt đầu bằng discovery tích hợp sẵn của nền tảng (DNS của Kubernetes) rồi mở rộng dần sang sidecar·mesh khi nhu cầu kiểm soát lưu lượng·bảo mật tăng lên là đáng mong muốn về mặt cân bằng giữa độ phức tạp và lợi ích.

Tài liệu tham khảo


Tóm tắt một câu: Service discovery tập trung hóa vị trí thường xuyên thay đổi của các thực thể vào một registry và lan truyền chúng tự động cùng trạng thái sức khỏe, để bên gọi đến được một thực thể khỏe mạnh chỉ bằng một tên logic — một cơ chế dò tìm vị trí động.