ในบริบทของการพัฒนาอย่างรวดเร็วของการประมวลผลบนคลาวด์และสถาปัตยกรรมไมโครเซอร์วิส มีความต้องการเพิ่มขึ้นในการจัดเตรียมความสามารถในการเรียกใช้โค้ดแบบไดนามิกสำหรับภาษาการเขียนโปรแกรมต่างๆ พร้อมการรับประกันความปลอดภัย ความสามารถในการปรับขนาด และประสิทธิภาพสูง บทความนี้จะอธิบายโครงการที่นำการเรียกใช้โค้ดไปใช้ในสภาพแวดล้อมที่แยกจากกัน และหารือถึงข้อดีของโซลูชันสถาปัตยกรรมที่เลือกสำหรับ WEB IDE สมัยใหม่ ระบบนี้สร้างขึ้นบน , ใช้ เพื่อการโต้ตอบระหว่างบริการอย่างมีประสิทธิภาพ ในฐานะนายหน้าข้อความและ เพื่อแยกสภาพแวดล้อมการดำเนินการ เซิร์ฟเวอร์ใช้เพื่อแสดงผลแบบเรียลไทม์ ไป จีอาร์พีซี เรดิส ด็อกเกอร์ เว็บซ็อกเก็ต เราจะอธิบายรายละเอียดว่าส่วนประกอบหลักของระบบมีโครงสร้างอย่างไร แตกต่างจากโซลูชันทางเลือกอย่างไร และเหตุใดการเลือกเทคโนโลยีเหล่านี้จึงช่วยให้มีประสิทธิภาพและความปลอดภัยสูงได้ 1. ภาพรวมสถาปัตยกรรมและส่วนประกอบหลัก โครงการนี้สร้างขึ้นบนหลักการของสถาปัตยกรรมไมโครเซอร์วิส ซึ่งช่วยให้คุณแบ่งฟังก์ชันการทำงานออกเป็นบริการอิสระได้ แต่ละส่วนประกอบมีหน้าที่รับผิดชอบงานเฉพาะทางสูง ซึ่งรับประกันความยืดหยุ่น ความสามารถในการปรับขนาด และความทนทานต่อข้อผิดพลาดของระบบ ส่วนประกอบหลัก: gRPC ใช้สำหรับการสื่อสารระหว่างบริการ เหมาะอย่างยิ่งสำหรับการถ่ายโอนข้อมูลระหว่างไมโครเซอร์วิส เนื่องจาก: โปรโตคอลไบนารี (โปรโตคอลบัฟเฟอร์): รับประกันการถ่ายโอนข้อมูลที่รวดเร็วและกะทัดรัด การพิมพ์ที่เข้มงวด: ช่วยหลีกเลี่ยงข้อผิดพลาดในการถ่ายโอนและประมวลผลข้อมูล ความหน่วงต่ำ: ซึ่งเป็นสิ่งสำคัญสำหรับการโทรภายในระหว่างบริการ (เช่น ระหว่างเซิร์ฟเวอร์ gRPC และคิว Redis) เซิร์ฟเวอร์ WebSocket: ให้การสื่อสารสองทางกับไคลเอนต์เพื่อส่งผลลัพธ์การดำเนินการแบบเรียลไทม์ เซิร์ฟเวอร์จะสมัครรับข้อมูลผลลัพธ์และส่งต่อข้อมูลไปยังไคลเอนต์ ทำให้แสดงบันทึกการคอมไพล์และการดำเนินการได้ทันที เวิร์กเกอร์: บริการอิสระที่ดึงงานจากคิว สร้างสภาพแวดล้อมการทำงานชั่วคราว ตรวจสอบและดำเนินการรหัสในคอนเทนเนอร์ Docker ที่แยกจากกัน แล้วเผยแพร่ผลลัพธ์ของการดำเนินการกลับไปยังคิว Redis: ใช้เป็นโบรกเกอร์ข้อความเพื่อโอนงานจากเซิร์ฟเวอร์ gRPC ไปยัง Worker และผลลัพธ์จาก Worker ไปยังเซิร์ฟเวอร์ WebSocket ข้อดีของ Redis คือความเร็วสูง รองรับ Pub/Sub และปรับขนาดได้ง่าย โมดูลภายใน: คอมไพเลอร์และ Docker Runner: โมดูลที่รับผิดชอบในการรันคำสั่ง Docker พร้อมการบันทึกสตรีม ช่วยให้สามารถตรวจสอบกระบวนการคอมไพเลอร์และการดำเนินการได้แบบเรียลไทม์ Language Runners: ผสมผสานตรรกะสำหรับการตรวจสอบ การคอมไพล์ และการดำเนินการของโค้ดสำหรับภาษาต่างๆ (C, C++, C#, Python, JavaScript, TypeScript) แต่ละโปรแกรมรันเนอร์ใช้อินเทอร์เฟซเดียว ซึ่งทำให้การขยายฟังก์ชันสำหรับภาษาใหม่ๆ ง่ายขึ้น 2. เทคโนโลยีและเหตุผลในการเลือก ไป ข้อดีของ Go: ประสิทธิภาพและความสามารถในการปรับขนาด: Go มีความเร็วในการดำเนินการสูง ซึ่งมีความสำคัญอย่างยิ่งสำหรับการจัดการคำขอขนานจำนวนมาก การรองรับการทำงานพร้อมกันในตัว: กลไกของ goroutines และช่องทางช่วยให้สามารถใช้งานการโต้ตอบแบบอะซิงโครนัสระหว่างส่วนประกอบต่างๆ ได้โดยไม่ต้องใช้รูปแบบมัลติเธรดที่ซับซ้อน จีอาร์พีซี ข้อดีของ gRPC: การถ่ายโอนข้อมูลที่มีประสิทธิภาพ: ด้วยโปรโตคอลการถ่ายโอนข้อมูลไบนารี (Protocol Buffers) gRPC จึงให้ความหน่วงเวลาต่ำและโหลดเครือข่ายต่ำ การพิมพ์ที่เข้มงวด: ช่วยลดจำนวนข้อผิดพลาดที่เกี่ยวข้องกับการตีความข้อมูลที่ไม่ถูกต้องระหว่างไมโครเซอร์วิส รองรับการสตรีมแบบทิศทางสองทาง: ซึ่งมีประโยชน์อย่างยิ่งสำหรับการแลกเปลี่ยนบันทึกและผลลัพธ์การดำเนินการแบบเรียลไทม์ การเปรียบเทียบ: แตกต่างจาก REST API, gRPC มอบการสื่อสารระหว่างบริการที่มีประสิทธิภาพและเชื่อถือได้มากกว่า ซึ่งเป็นสิ่งสำคัญสำหรับระบบที่มีการทำงานพร้อมกันสูง เรดิส เหตุใดจึงเลือก Redis? ประสิทธิภาพสูง: Redis สามารถจัดการการทำงานจำนวนมากต่อวินาที ซึ่งทำให้เหมาะอย่างยิ่งสำหรับคิวงานและผลลัพธ์ การรองรับ Pub/Sub และรายการ: ความเรียบง่ายของการนำคิวและกลไกการสมัครรับข้อมูลมาใช้ทำให้สามารถจัดระเบียบการโต้ตอบแบบอะซิงโครนัสระหว่างบริการต่างๆ ได้อย่างง่ายดาย การเปรียบเทียบกับโบรกเกอร์ข้อความอื่น ๆ: ไม่เหมือน RabbitMQ หรือ Kafka, Redis ต้องมีการกำหนดค่าน้อยกว่าและมอบประสิทธิภาพเพียงพอสำหรับระบบเรียลไทม์ ด็อกเกอร์ บทบาทของ Docker: การแยกสภาพแวดล้อม: คอนเทนเนอร์ Docker ช่วยให้คุณสามารถเรียกใช้โค้ดในสภาพแวดล้อมที่แยกออกจากกันโดยสมบูรณ์ ซึ่งจะเพิ่มความปลอดภัยในการดำเนินการและลดความเสี่ยงของการขัดแย้งกับระบบหลัก การจัดการและความสอดคล้องกัน: การใช้ Docker มอบสภาพแวดล้อมแบบเดียวกันในการคอมไพล์และดำเนินการโค้ด โดยไม่คำนึงถึงระบบโฮสต์ การเปรียบเทียบ: การรันโค้ดโดยตรงบนโฮสต์อาจก่อให้เกิดความเสี่ยงด้านความปลอดภัยและนำไปสู่ความขัดแย้งในการอ้างอิง ในขณะที่ Docker ช่วยให้คุณแก้ไขปัญหาเหล่านี้ได้ เว็บซ็อกเก็ต แบบเรียลไทม์: การเชื่อมต่ออย่างต่อเนื่องกับไคลเอนต์ช่วยให้สามารถถ่ายโอนข้อมูล (บันทึก ผลการดำเนินการ) ได้ทันที ประสบการณ์ผู้ใช้ที่ได้รับการปรับปรุง: ด้วย WebSocket IDE สามารถแสดงผลลัพธ์ของโค้ดแบบไดนามิกได้ 3. ประโยชน์ของสถาปัตยกรรมไมโครเซอร์วิส โครงการนี้ใช้แนวทางไมโครเซอร์วิสซึ่งมีข้อดีสำคัญหลายประการ: การปรับขนาดอิสระ: สามารถปรับขนาดบริการแต่ละอย่าง (เซิร์ฟเวอร์ gRPC, Worker, เซิร์ฟเวอร์ WebSocket, Redis) ได้แยกกันตามโหลด ซึ่งช่วยให้ใช้ทรัพยากรได้อย่างมีประสิทธิภาพและปรับตัวได้รวดเร็วตามจำนวนคำขอที่เพิ่มขึ้น การทนทานต่อข้อผิดพลาด: การแบ่งระบบออกเป็นโมดูลอิสระหมายความว่าความล้มเหลวของไมโครเซอร์วิสหนึ่งรายการจะไม่ส่งผลให้ระบบทั้งหมดล้มเหลว ซึ่งจะช่วยเพิ่มเสถียรภาพโดยรวมและทำให้การกู้คืนจากข้อผิดพลาดง่ายขึ้น ความยืดหยุ่นในการพัฒนาและปรับใช้: ไมโครเซอร์วิสได้รับการพัฒนาและปรับใช้โดยอิสระ ซึ่งทำให้การแนะนำฟีเจอร์และการอัปเดตใหม่ทำได้ง่ายขึ้น นอกจากนี้ยังช่วยให้คุณสามารถใช้เทคโนโลยีที่เหมาะสมที่สุดสำหรับแต่ละบริการเฉพาะได้อีกด้วย ความสะดวกในการบูรณาการ: อินเทอร์เฟซที่กำหนดไว้อย่างชัดเจน (เช่น ผ่าน gRPC) ทำให้สามารถเชื่อมต่อบริการใหม่ได้อย่างง่ายดายโดยไม่ต้องเปลี่ยนแปลงสถาปัตยกรรมที่มีอยู่มากนัก การแยกและการรักษาความปลอดภัย: ไมโครเซอร์วิสแต่ละรายการสามารถทำงานในคอนเทนเนอร์ของตัวเอง ซึ่งจะช่วยลดความเสี่ยงที่เกี่ยวข้องกับการรันโค้ดที่ไม่ปลอดภัย และให้การป้องกันอีกชั้นหนึ่ง 4. การวิเคราะห์เชิงเปรียบเทียบแนวทางสถาปัตยกรรม เมื่อสร้าง WEB IDE ที่ทันสมัยสำหรับการรันโค้ดจากระยะไกล มักมีการเปรียบเทียบโซลูชันทางสถาปัตยกรรมต่างๆ ลองพิจารณาสองแนวทาง: แนวทาง A: สถาปัตยกรรมไมโครเซอร์วิส (gRPC + Redis + Docker) ความหน่วง: 40 มิลลิวินาที ปริมาณงาน: 90 หน่วย ระบบรักษาความปลอดภัย : 85 ยูนิต ความสามารถในการขยาย: 90 หน่วย คุณสมบัติ: แนวทางนี้ช่วยให้การสื่อสารระหว่างบริการรวดเร็วและเชื่อถือได้ การแยกการทำงานของโค้ดออกจากกันสูง และการปรับขนาดที่ยืดหยุ่นเนื่องจากการทำคอนเทนเนอร์ เหมาะอย่างยิ่งสำหรับ WEB IDE ยุคใหม่ ซึ่งการตอบสนองและความปลอดภัยเป็นสิ่งสำคัญ แนวทาง B: สถาปัตยกรรมโมโนลิธิกแบบดั้งเดิม (HTTP REST + การดำเนินการแบบรวมศูนย์) ความหน่วง: 70 มิลลิวินาที ปริมาณงาน: 65 หน่วย ระบบรักษาความปลอดภัย : 60 ยูนิต ความสามารถในการขยาย: 70 หน่วย คุณสมบัติ: โซลูชันแบบโมโนลิธิก ซึ่งมักใช้ในเวอร์ชันแรกของ IDE เว็บนั้นใช้ HTTP REST และการรันโค้ดแบบรวมศูนย์ ระบบดังกล่าวจะประสบปัญหาด้านการปรับขนาด ความล่าช้าที่เพิ่มขึ้น และความยากลำบากในการรับรองความปลอดภัยเมื่อรันโค้ดของผู้อื่น หมายเหตุ: ในบริบทสมัยใหม่ของการพัฒนา WEB IDE แนวทาง HTTP REST และการดำเนินการแบบรวมศูนย์นั้นด้อยกว่าข้อดีของสถาปัตยกรรมไมโครเซอร์วิส เนื่องจากไม่ได้ให้ความยืดหยุ่นและความสามารถในการปรับขนาดตามที่จำเป็น การแสดงภาพของเมตริกเชิงเปรียบเทียบ กราฟแสดงให้เห็นอย่างชัดเจนว่าสถาปัตยกรรมไมโครเซอร์วิส (แนวทาง A) ให้ความหน่วงที่ต่ำกว่า ปริมาณงานที่สูงขึ้น ปลอดภัยกว่า และมีความสามารถในการปรับขนาดได้ดีกว่าเมื่อเปรียบเทียบกับโซลูชันแบบโมโนลิธิก (แนวทาง B) 5. สถาปัตยกรรม Docker: การแยกและความสามารถในการปรับขนาด องค์ประกอบสำคัญประการหนึ่งของการรักษาความปลอดภัยและเสถียรภาพของระบบคือการใช้ Docker ในโซลูชันของเรา บริการทั้งหมดจะถูกปรับใช้ในคอนเทนเนอร์ที่แยกจากกัน ซึ่งช่วยให้แน่ใจได้ว่า: การแยกสภาพแวดล้อมการทำงาน: บริการแต่ละอย่าง (เซิร์ฟเวอร์ gRPC, Worker, เซิร์ฟเวอร์ WebSocket) และโบรกเกอร์ข้อความ (Redis) ทำงานในคอนเทนเนอร์ของตัวเอง ซึ่งช่วยลดความเสี่ยงของโค้ดที่ไม่ปลอดภัยที่จะส่งผลกระทบต่อระบบหลัก ในขณะเดียวกัน โค้ดที่ผู้ใช้เรียกใช้ในเบราว์เซอร์ (เช่น ผ่าน WEB IDE) จะถูกสร้างและดำเนินการในคอนเทนเนอร์ Docker แยกต่างหากสำหรับแต่ละงาน แนวทางนี้ช่วยให้มั่นใจได้ว่าโค้ดที่อาจไม่ปลอดภัยหรือผิดพลาดจะไม่ส่งผลกระทบต่อการทำงานของโครงสร้างพื้นฐานหลัก ความสอดคล้องของสภาพแวดล้อม: การใช้ Docker ช่วยให้แน่ใจว่าการตั้งค่ายังคงเหมือนเดิมในสภาพแวดล้อมการพัฒนา การทดสอบ และการผลิต ซึ่งช่วยลดความซับซ้อนของการดีบักและทำให้แน่ใจถึงความสามารถในการคาดเดาการทำงานของโค้ด ความยืดหยุ่นในการปรับขนาด: สามารถปรับขนาดแต่ละส่วนประกอบได้อย่างอิสระ ซึ่งช่วยให้คุณปรับตัวให้เข้ากับโหลดที่เปลี่ยนแปลงได้อย่างมีประสิทธิภาพ ตัวอย่างเช่น เมื่อจำนวนคำขอเพิ่มขึ้น คุณสามารถเปิดใช้คอนเทนเนอร์ Worker เพิ่มเติมได้ โดยแต่ละคอนเทนเนอร์จะสร้างคอนเทนเนอร์แยกกันสำหรับการดำเนินการโค้ดของผู้ใช้ 6. ส่วนเล็ก ๆ ของโค้ด ด้านล่างนี้เป็นเวอร์ชันย่อของส่วนหลักของโค้ดที่แสดงให้เห็นถึงการทำงานของระบบ: กำหนดภาษาที่จะรันโดยใช้รีจิสทรีตัวรันระดับโลก เริ่มคอนเทนเนอร์ Docker เพื่อรันโค้ดผู้ใช้โดยใช้ฟังก์ชัน RunInDockerStreaming 1. การตรวจจับภาษาผ่านการลงทะเบียนนักวิ่ง ระบบใช้รีจิสทรีทั่วโลก โดยที่แต่ละภาษาจะมีตัวรันเนอร์ของตัวเอง วิธีนี้ทำให้คุณสามารถเพิ่มการรองรับภาษาใหม่ได้อย่างง่ายดาย เพียงแค่คุณนำอินเทอร์เฟซตัวรันเนอร์ไปใช้งานและลงทะเบียน: package languages import ( "errors" "sync" ) var ( registry = make(map[string]Runner) registryMu sync.RWMutex ) type Runner interface { Validate(projectDir string) error Compile(ctx context.Context, projectDir string) (<-chan string, error) Run(ctx context.Context, projectDir string) (<-chan string, error) } func Register(language string, runner Runner) { registryMu.Lock() defer registryMu.Unlock() registry[language] = runner } func GetRunner(language string) (Runner, error) { registryMu.RLock() defer registryMu.RUnlock() if runner, exists := registry[language]; exists { return runner, nil } return nil, errors.New("unsupported language") } ตัวอย่างการลงทะเบียนภาษาใหม่: func init() { languages.Register("python", NewGenericRunner("python")) languages.Register("javascript", NewGenericRunner("javascript")) } ดังนั้นเมื่อได้รับคำขอระบบจะเรียกใช้งานดังนี้ runner, err := languages.GetRunner(req.Language) และรับตัวรันเนอร์ที่สอดคล้องเพื่อดำเนินการโค้ด 2. การเปิดตัวคอนเทนเนอร์ Docker เพื่อดำเนินการโค้ด สำหรับคำขอรหัสผู้ใช้แต่ละราย จะมีการสร้างคอนเทนเนอร์ Docker แยกต่างหาก ซึ่งจะดำเนินการภายในเมธอดรันเนอร์ (ตัวอย่างเช่น ใน Run) ตรรกะหลักในการรันคอนเทนเนอร์อยู่ในฟังก์ชัน RunInDockerStreaming: package compiler import ( "bufio" "fmt" "io" "log" "os/exec" "time" ) func RunInDockerStreaming(image, dir, cmdStr string, logCh chan < -string) error { timeout: = 50 * time.Second cmd: = exec.Command("docker", "run", "--memory=256m", "--cpus=0.5", "--network=none", "-v", fmt.Sprintf("%s:/app", dir), "-w", "/app", image, "sh", "-c", cmdStr) cmd.Stdin = nil stdoutPipe, err: = cmd.StdoutPipe() if err != nil { return fmt.Errorf("error getting stdout: %v", err) } stderrPipe, err: = cmd.StderrPipe() if err != nil { return fmt.Errorf("error getting stderr: %v", err) } if err: = cmd.Start();err != nil { return fmt.Errorf("Error starting command: %v", err) } // Reading logs from the container go func() { reader: = bufio.NewReader(io.MultiReader(stdoutPipe, stderrPipe)) for { line, isPrefix, err: = reader.ReadLine() if err != nil { if err != io.EOF { logCh < -fmt.Sprintf("[Error reading logs: %v]", err) } break } msg: = string(line) for isPrefix { more, morePrefix, err: = reader.ReadLine() if err != nil { break } msg += string(more) isPrefix = morePrefix } logCh < -msg } close(logCh) }() doneCh: = make(chan error, 1) go func() { doneCh < -cmd.Wait() }() select { case err: = < -doneCh: return err case <-time.After(timeout): if cmd.Process != nil { cmd.Process.Kill() } return fmt.Errorf("Execution timed out") } } ฟังก์ชันนี้จะสร้างคำสั่ง docker run โดยที่: image เป็นภาพ Docker ที่เลือกสำหรับภาษาเฉพาะ (กำหนดโดยการกำหนดค่าตัวรันเนอร์) dir คือไดเร็กทอรีที่มีรหัสที่สร้างขึ้นสำหรับคำขอนี้ cmdStr คือคำสั่งสำหรับการคอมไพล์หรือดำเนินการโค้ด ดังนั้นเมื่อเรียกใช้เมธอด Run ของตัวเรียกใช้งาน จะเกิดขึ้นต่อไปนี้: ฟังก์ชัน RunInDockerStreaming จะเริ่มคอนเทนเนอร์ Docker ที่มีการเรียกใช้โค้ด บันทึกการดำเนินการจะถูกสตรีมไปยังช่อง logCh ซึ่งช่วยให้คุณสามารถส่งข้อมูลเกี่ยวกับกระบวนการดำเนินการได้แบบเรียลไทม์ 3. กระบวนการดำเนินการแบบบูรณาการ ย่อส่วนของตรรกะหลักของการทำงานของโค้ด (executor.ExecuteCode): func ExecuteCode(ctx context.Context, req CodeRequest, logCh chan string) CodeResponse { // Create a temporary directory and write files projectDir, err: = util.CreateTempProjectDir() if err != nil { return CodeResponse { "", fmt.Sprintf("Error: %v", err) } } defer os.RemoveAll(projectDir) for fileName, content: = range req.Files { util.WriteFileRecursive(filepath.Join(projectDir, fileName), [] byte(content)) } // Get a runner for the selected language runner, err: = languages.GetRunner(req.Language) if err != nil { return CodeResponse { "", err.Error() } } if err: = runner.Validate(projectDir); err != nil { return CodeResponse { "", fmt.Sprintf("Validation error: %v", err) } } // Compile (if needed) and run code in Docker container compileCh, _: = runner.Compile(ctx, projectDir) for msg: = range compileCh { logCh < -"[Compilation]: " + msg } runCh, _: = runner.Run(ctx, projectDir) var output string for msg: = range runCh { logCh < -"[Run]: " + msg output += msg + "\n" } return CodeResponse { Output: output } } ในตัวอย่างขั้นต่ำนี้: การตรวจจับภาษาจะดำเนินการโดยเรียก languages.GetRunner(req.Language) ซึ่งช่วยให้สามารถเพิ่มการรองรับสำหรับภาษาใหม่ได้อย่างง่ายดาย การเปิดตัวคอนเทนเนอร์ Docker ถูกนำมาใช้ภายในวิธีการ Compile/Run ซึ่งใช้ RunInDockerStreaming เพื่อรันโค้ดแบบแยกส่วน ส่วนสำคัญเหล่านี้แสดงให้เห็นว่าระบบรองรับการขยายได้ (การเพิ่มภาษาใหม่ได้ง่าย) อย่างไร และให้การแยกส่วนโดยการสร้างคอนเทนเนอร์ Docker แยกต่างหากสำหรับแต่ละคำขอ แนวทางนี้ช่วยปรับปรุงความปลอดภัย ความเสถียร และความสามารถในการปรับขนาดของแพลตฟอร์ม ซึ่งมีความสำคัญอย่างยิ่งสำหรับ WEB IDE สมัยใหม่ 7. บทสรุป บทความนี้จะกล่าวถึงแพลตฟอร์มสำหรับการเรียกใช้โค้ดจากระยะไกลที่สร้างขึ้นบนสถาปัตยกรรมไมโครเซอร์วิสโดยใช้สแต็ก gRPC + Redis + Docker แนวทางนี้ช่วยให้คุณสามารถ: ลดเวลาแฝงและรับรองปริมาณงานสูงเนื่องจากการสื่อสารระหว่างบริการที่มีประสิทธิภาพ รับประกันความปลอดภัยโดยแยกการทำงานของโค้ดในคอนเทนเนอร์ Docker แยกต่างหาก โดยจะสร้างคอนเทนเนอร์แยกกันสำหรับคำขอของผู้ใช้แต่ละราย การปรับขนาดระบบอย่างยืดหยุ่นเนื่องจากการปรับขนาดไมโครเซอร์วิสแบบอิสระ ส่งมอบผลลัพธ์แบบเรียลไทม์ผ่าน WebSocket ซึ่งมีความสำคัญอย่างยิ่งสำหรับ WEB IDE สมัยใหม่ การวิเคราะห์เชิงเปรียบเทียบแสดงให้เห็นว่าสถาปัตยกรรมไมโครเซอร์วิสมีประสิทธิภาพเหนือกว่าโซลูชันโมโนลิธิกแบบเดิมอย่างเห็นได้ชัดในเมตริกที่สำคัญทั้งหมด ข้อดีของแนวทางนี้ได้รับการยืนยันจากข้อมูลจริง ซึ่งทำให้เป็นโซลูชันที่น่าสนใจสำหรับการสร้างระบบที่มีประสิทธิภาพสูงและทนต่อความผิดพลาด ผู้แต่ง : โอเล็กซี บอนดาร์ วันที่ : 2025-02-07