Coding Gun

จัดการกับข้อผิดพลาดด้วย Circuit Breaker

Circuit breaker คือ Microservices patterns

Circuit Breaker Microservices Design Pattern
Circuit breaker คือ microservices design patterns ที่ทำหน้าที่จัดการกับข้อผิดพลาดที่อาจเกิดขึ้น เช่น ในกรณีที่ service บาง service ไม่ทำงาน ซึ่งอาจเป็นเพราะ server down หรือ network disconnected ก็ได้ (เหมือนกับ try catch ใน source code)

Circuit breaker ทำงานยังไง?

Circuit breaker นั้นมีแนวคิดมาจากวงจรไฟฟ้าตามชื่อของ design pattern นี้ ซึ่งโดยปกติการทำงานของวงจรไฟฟ้าจะเป็น

ดังนั้นในตอนที่ service เริ่มทำงานจะมีสถานะเริ่มต้นเป็น closed (วงจรปิด) service ทำงานได้ปกติ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
server.on('listening', () => {
  const registerService = () => axios.put(`http://localhost:3000/register/${config.service-name}/${config.version}/${server.address().port}`)
    .catch(err => log.fatal(err));
  const unregisterService = () => axios.delete(`http://localhost:3000/register/${config.service-name}/${config.version}/${server.address().port}`)
    .catch(err => log.fatal(err));

  registerService();

  // Code สำหรับการทำ health check เพื่อตรวจสอบสถานะของ service (liveness)
  const interval = setInterval(registerService, 15000);
  const cleanup = async () => {
    let clean = false;
    if (!clean) {
      clean = true;
      clearInterval(interval);
      await unregisterService();
    }
  };

แต่ถ้าเกิดความผิดปกติเกิดขึ้น จะทำการเปลี่ยนสถานะเป็น Open(วงจรเปิด) service หยุดทำงาน โดยที่การจัดการกับสถานะนี้มีได้ 2 วิธีคือการ handle event ที่เป็นการ exit ออกจากโปรแกรม ดังตัวอย่างนี้

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
process.on('uncaughtException', async () => {
    await cleanup();
    process.exit(0);
  });

  process.on('SIGINT', async () => {
    await cleanup();
    process.exit(0);
  });

  process.on('SIGTERM', async () => {
    await cleanup();
    process.exit(0);
  });

ทำการ de-register หรือ unregister ก่อนจะ exit

1
2
3
4
5
6
7
8
 const cleanup = async () => {
    let clean = false;
    if (!clean) {
      clean = true;
      clearInterval(interval);
      await unregisterService();
    }
  };

หลังจากนั้นจะมีการส่ง request ไปหา service ปลายทางนั้นเรื่อยๆ เพื่อดูว่า service นั้นกลับมาทำงานแล้วรึยัง

ส่วนวิธีที่ 2 เป็นการใช้ service registry ซึ่งเป็นฐานข้อมูลที่ใช้ในการเก็บสถานะของ service ว่าทำงานอยู่ที่ instances ไหนบ้าง(ส่วนใหญ่จะมีมากกว่า 1 instance) และเปิดการทำงานไว้ที่ port ไหน ตัวอย่างเครื่องมือที่ใช้เป็น service registry ก็จะมีดังนี้

หรือเราอาจเลือกที่จะ implement เองก็ได้ โดยจะมี 3 method ดังนี้

1
2
3
4
5
6
7
8
9
service.put('/register/:servicename/:serviceversion/:serviceport', (req, res) => {
    const { servicename, serviceversion, serviceport } = req.params;

    const serviceip = req.connection.remoteAddress.includes('::') ? `[${req.connection.remoteAddress}]` : req.connection.remoteAddress;

    const serviceKey = serviceRegistry
      .register(servicename, serviceversion, serviceip, serviceport);
    return res.json({ result: serviceKey });
  });
1
2
3
4
5
6
7
8
9
service.delete('/register/:servicename/:serviceversion/:serviceport', (req, res) => {
    const { servicename, serviceversion, serviceport } = req.params;

    const serviceip = req.connection.remoteAddress.includes('::') ? `[${req.connection.remoteAddress}]` : req.connection.remoteAddress;

    const serviceKey = serviceRegistry
      .unregister(servicename, serviceversion, serviceip, serviceport);
    return res.json({ result: serviceKey });
  });
1
2
3
4
5
6
  service.get('/find/:servicename/:serviceversion', (req, res) => {
    const { servicename, serviceversion } = req.params;
    const svc = serviceRegistry.get(servicename, serviceversion);
    if (!svc) return res.status(404).json({ result: 'Service not found' });
    return res.json(svc);
  });

ในตัวอย่างนี้คุณต้องไป implement serviceRegistry เอง ขึ้นอยู่กับว่าเราเก็บข้อมูลไว้ที่ไหน อาจเป็น im-memory database หรือ persistance database(database ทั่วๆไป) ก็ได้

Phanupong Permpimol
Follow me