ข้ามไปยังเนื้อหา

Service Discovery

Order service ต้องเรียก Customer service ถ้าเป็น deployment แบบตายตัว คุณก็แค่ hard-code http://customer-service:8080 ลงไปแล้วจบ แต่ใน environment สมัยใหม่ service รันเป็น instance หลายตัวภายใต้ autoscaling container ถูกจัดตารางใหม่ข้ามโฮสต์ และแพลตฟอร์มกำหนดตำแหน่งบนเครือข่ายแบบไดนามิก ชุด instance ของ Customer ที่ยังมีชีวิตอยู่ พร้อมทั้ง IP address และพอร์ต จึงเปลี่ยนทุกนาที ที่อยู่ที่ผู้เรียกต้องใช้ไม่ได้อยู่นิ่งเป็นค่าคงที่ที่ไหนเลย

ผู้เรียกต้องรู้ตำแหน่งบนเครือข่ายของ instance ปลายทาง แต่ตำแหน่งนั้นเป็นแบบไดนามิก instance เกิดและดับตามการ scale บ้างก็ crash บ้างก็ redeploy แล้วย้ายที่อยู่ นอกจากนี้ผู้เรียกยังอยากกระจาย request ไปยัง instance ที่แข็งแรงทุกตัว และต้องไม่ส่ง traffic ไปหา instance ที่ตายแล้ว การ hard-code ที่อยู่จึงเป็นไปไม่ได้ ส่วนไฟล์ config แบบ static ก็ล้าสมัยทันทีที่ instance restart บนโฮสต์ใหม่ คำถามคือผู้เรียกจะค้นหา instance ที่ยังมีชีวิตได้อย่างน่าเชื่อถืออย่างไร

หัวใจสำคัญคือ service registry ที่เป็นฐานข้อมูลที่เก็บรายชื่อ service, instance ของแต่ละ service พร้อมตำแหน่งและสุขภาพ โดย instance เข้าสู่ registry ได้สองวิธี:

  • Self-registration — แต่ละ instance ลงทะเบียนตัวเองตอนเริ่มทำงาน ส่ง heartbeat เป็นระยะ และถอนการลงทะเบียนตอนปิด เรียบง่าย แต่ผูกมัดทุกบริการเข้ากับ API ของ registry
  • Third-party registration — มี registrar แยกต่างหาก ซึ่งมักเป็นแพลตฟอร์ม deployment คอยเฝ้าดู instance แล้วอัปเดต registry ให้แทน ตัว code ของ service จึงไม่ต้องรู้เรื่องนี้เลย

เมื่อมี registry แล้ว ผู้เรียกเข้าถึง instance ได้สองวิธี

Client-side discovery ฝั่ง client query registry เพื่อขอรายการ instance ที่แข็งแรง แล้วเลือกมาหนึ่งตัวโดยทำ load balancing เองในฝั่ง client จากนั้นเรียกไปที่ instance นั้นโดยตรง

sequenceDiagram
  participant C as Client (Order Service)
  participant R as Service Registry
  participant I as Customer Instance
  C->>R: where is customer-service?
  R-->>C: [10.0.1.7:8080, 10.0.2.3:8080]
  C->>C: pick one (load-balance)
  C->>I: GET /customers/42
  I-->>C: 200 OK
Client-side discovery — client สอบถาม registry และทำ load-balancing ข้าม instance ด้วยตัวเอง

Server-side discovery client เรียกที่อยู่ที่เสถียร — router หรือ load balancer — ซึ่งสอบถาม registry เลือก instance และส่งต่อคำขอ client ไม่รู้อะไรเกี่ยวกับ registry เลย

sequenceDiagram
  participant C as Client (Order Service)
  participant LB as Load Balancer / Router
  participant R as Service Registry
  participant I as Customer Instance
  C->>LB: GET /customers/42
  LB->>R: where is customer-service?
  R-->>LB: healthy instances
  LB->>I: forward request
  I-->>LB: 200 OK
  LB-->>C: 200 OK
Server-side discovery — load balancer ที่ที่อยู่ที่เสถียร resolve registry และส่งต่อคำขอ

รายการ registry และบันทึกเกี่ยวกับ load-balancer registry เก็บตำแหน่งและสุขภาพของแต่ละ instance; load balancer จัดเส้นทางตามชื่อบริการไปยัง instance ใดก็ตามที่มีสุขภาพดีอยู่ในขณะนั้น

# Service registry entry for customer-service
service: customer-service
instances:
- id: customer-7a3f address: 10.0.1.7:8080 health: passing ttl: 10s
- id: customer-9b2c address: 10.0.2.3:8080 health: passing ttl: 10s
- id: customer-4d1e address: 10.0.3.9:8080 health: critical ttl: 10s # excluded from routing
# Load balancer routing note (server-side discovery)
# Clients call the stable name "customer-service".
# The LB resolves it against the registry and forwards
# only to instances whose health == passing, spreading
# load round-robin. An instance that misses its TTL
# heartbeat is marked critical and dropped automatically.

สิ่งที่คุณได้:

  • การจัดเส้นทางที่ไดนามิกและทนทาน ผู้เรียกเข้าถึง instance ที่มีสุขภาพดีในขณะนั้นได้เสมอ ไม่ว่า instance จะขยายขนาด crash หรือย้ายที่อยู่บ่อยแค่ไหน instance ที่ตายแล้วจะหลุดออกจากการหมุนเวียนโดยอัตโนมัติ
  • load balancing ในตัว คำขอกระจายไปยัง instance ที่มีสุขภาพดีทั้งหมด ไม่ว่า client (client-side) หรือ router (server-side) จะเป็นผู้กระจาย

สิ่งที่คุณต้องจ่าย:

  • registry กลายเป็น infrastructure ที่สำคัญที่สุด ต้อง available สูงและสอดคล้องมากพอจะเชื่อถือได้ ถ้า registry ดับหรือคืนข้อมูลเก่า ผู้เรียกก็จะส่ง traffic ไปหา instance ที่ตายแล้ว ปกติจึงรันเป็น cluster ที่ทำ replication ไว้
  • การแลกได้แลกเสียระหว่างสองรูปแบบ client-side discovery มีประสิทธิภาพ (ไม่มี hop เพิ่ม) แต่ใส่ logic การ discovery และ load-balancing เข้าไปในทุก client และทุกภาษาที่คุณใช้ server-side discovery ทำให้ client เรียบง่าย แต่เพิ่ม network hop และองค์ประกอบอีกตัวให้ต้องทำงาน
  • การปรับจูน heartbeat health check และ TTL ต้องถูกปรับจูน: ช้าเกินไปแล้ว instance ที่ตายแล้วก็ค้างอยู่; ก้าวร้าวเกินไปแล้วการสะดุดชั่วครู่ก็ขับ instance ที่มีสุขภาพดีออกไป

แพลตฟอร์มหลายตัว เช่น container orchestrator ที่มี DNS ในตัว และ service mesh ให้ server-side discovery มาพร้อมใช้งานทันที registry และการจัดเส้นทางจึงกลายเป็นส่วนหนึ่งของ infrastructure มากกว่าจะเป็น code แอปพลิเคชันของคุณ

  • Remote Procedure Invocation — การเรียกแบบ synchronous ที่ต้องการที่อยู่ instance ที่จะส่งไปหา
  • API Gateway — พึ่งพา discovery เพื่อจัดเส้นทางไปยัง instance ที่ยังมีชีวิตอยู่ของบริการภายใน
  • Backends for Frontends — แต่ละ BFF ทำ discovery เพื่อหา service ภายในที่ต้องนำมาประกอบ
ข้อดีข้อแลกเปลี่ยน
service ค้นหากันเองได้โดยไม่ต้อง hardcode IPเพิ่ม infrastructure component (registry) ที่ต้องดูแล
scale service ขึ้นลงได้โดย registry อัปเดตเองregistry ล้มหมายถึงทุก service หาคู่สื่อสารไม่ได้
deploy service ใหม่โดยไม่ต้องแก้ config ของ callerhealth check ที่ไม่ดีทำให้ส่ง request ไป service ที่ตายแล้ว
รองรับ environment หลายแบบ (dev, staging, prod)เพิ่ม latency เล็กน้อยจากการ lookup registry

Static IP Discovery — hardcode IP address ของ service ใน config

อาการ:

  • เพิ่ม replica ต้องแก้ config ของทุก caller
  • restart service ได้ IP ใหม่ทำให้ caller ส่ง request ไม่ถึง
  • ทำงานไม่ได้ใน Kubernetes ที่ Pod IP เปลี่ยนทุก restart

Stale Registry — registry มีข้อมูล service ที่ตายแล้ว

อาการ:

  • service deregister ไม่สำเร็จ registry ยังมีชื่อเก่า
  • caller ส่ง request ไปหา instance ที่ไม่มีแล้ว
  • error rate สูง client side retry เพิ่มขึ้น

💡 ตัวอย่างจากของจริง

Netflix (Eureka):

  • สร้าง Eureka เพื่อ Service Discovery บน AWS ที่ IP เปลี่ยนบ่อย
  • แต่ละ service register ตัวเองเมื่อ start และ deregister เมื่อ stop
  • ปัจจุบัน open source และเป็นส่วนหนึ่งของ Spring Cloud

Kubernetes:

  • DNS-based Service Discovery built-in ผ่าน CoreDNS
  • Service object ทำหน้าที่เป็น stable endpoint แม้ Pod จะ restart
เหตุใดผู้เรียกจึงไม่สามารถ hard-code ที่อยู่ของบริการได้ง่าย ๆ?
บทบาทของ service registry คืออะไร?
client-side discovery แตกต่างจาก server-side discovery อย่างไร?
อะไรแยก self-registration ออกจาก third-party registration?