จัดการกับข้อผิดพลาดด้วย Circuit Breaker
Wed, 18 Jan 2023
Circuit breaker คือ Microservices patterns
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 ก็จะมีดังนี้
kubernetes (เก็บอยู่ใน cluster db)
consul
eureka server
หรือเราอาจเลือกที่จะ implement เองก็ได้ โดยจะมี 3 method ดังนี้
Method PUT สำหรับการ register service(ตอน start ขึ้นมาทำงาน)
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 });
});
Method DELETE สำหรับการ de-register service (ตอน exit)
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 });
});
Method GET สำหรับการเรียกใช้งาน service (service discovery)
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 ทั่วๆไป) ก็ได้