ในบริบทของการพัฒนาอย่างรวดเร็วของการประมวลผลบนคลาวด์และสถาปัตยกรรมไมโครเซอร์วิส มีความต้องการเพิ่มขึ้นในการจัดเตรียมความสามารถในการเรียกใช้โค้ดแบบไดนามิกสำหรับภาษาการเขียนโปรแกรมต่างๆ พร้อมการรับประกันความปลอดภัย ความสามารถในการปรับขนาด และประสิทธิภาพสูง บทความนี้จะอธิบายโครงการที่นำการเรียกใช้โค้ดไปใช้ในสภาพแวดล้อมที่แยกจากกัน และหารือถึงข้อดีของโซลูชันสถาปัตยกรรมที่เลือกสำหรับ WEB IDE สมัยใหม่ ระบบนี้สร้างขึ้นบน
เราจะอธิบายรายละเอียดว่าส่วนประกอบหลักของระบบมีโครงสร้างอย่างไร แตกต่างจากโซลูชันทางเลือกอย่างไร และเหตุใดการเลือกเทคโนโลยีเหล่านี้จึงช่วยให้มีประสิทธิภาพและความปลอดภัยสูงได้
โครงการนี้สร้างขึ้นบนหลักการของสถาปัตยกรรมไมโครเซอร์วิส ซึ่งช่วยให้คุณแบ่งฟังก์ชันการทำงานออกเป็นบริการอิสระได้ แต่ละส่วนประกอบมีหน้าที่รับผิดชอบงานเฉพาะทางสูง ซึ่งรับประกันความยืดหยุ่น ความสามารถในการปรับขนาด และความทนทานต่อข้อผิดพลาดของระบบ
ส่วนประกอบหลัก:
ข้อดีของ Go:
ประสิทธิภาพและความสามารถในการปรับขนาด: Go มีความเร็วในการดำเนินการสูง ซึ่งมีความสำคัญอย่างยิ่งสำหรับการจัดการคำขอขนานจำนวนมาก
การรองรับการทำงานพร้อมกันในตัว: กลไกของ goroutines และช่องทางช่วยให้สามารถใช้งานการโต้ตอบแบบอะซิงโครนัสระหว่างส่วนประกอบต่างๆ ได้โดยไม่ต้องใช้รูปแบบมัลติเธรดที่ซับซ้อน
ข้อดีของ gRPC:
การเปรียบเทียบ: แตกต่างจาก REST API, gRPC มอบการสื่อสารระหว่างบริการที่มีประสิทธิภาพและเชื่อถือได้มากกว่า ซึ่งเป็นสิ่งสำคัญสำหรับระบบที่มีการทำงานพร้อมกันสูง
เหตุใดจึงเลือก Redis?
ประสิทธิภาพสูง: Redis สามารถจัดการการทำงานจำนวนมากต่อวินาที ซึ่งทำให้เหมาะอย่างยิ่งสำหรับคิวงานและผลลัพธ์
การรองรับ Pub/Sub และรายการ: ความเรียบง่ายของการนำคิวและกลไกการสมัครรับข้อมูลมาใช้ทำให้สามารถจัดระเบียบการโต้ตอบแบบอะซิงโครนัสระหว่างบริการต่างๆ ได้อย่างง่ายดาย
การเปรียบเทียบกับโบรกเกอร์ข้อความอื่น ๆ: ไม่เหมือน RabbitMQ หรือ Kafka, Redis ต้องมีการกำหนดค่าน้อยกว่าและมอบประสิทธิภาพเพียงพอสำหรับระบบเรียลไทม์
บทบาทของ Docker:
การแยกสภาพแวดล้อม: คอนเทนเนอร์ Docker ช่วยให้คุณสามารถเรียกใช้โค้ดในสภาพแวดล้อมที่แยกออกจากกันโดยสมบูรณ์ ซึ่งจะเพิ่มความปลอดภัยในการดำเนินการและลดความเสี่ยงของการขัดแย้งกับระบบหลัก
การจัดการและความสอดคล้องกัน: การใช้ Docker มอบสภาพแวดล้อมแบบเดียวกันในการคอมไพล์และดำเนินการโค้ด โดยไม่คำนึงถึงระบบโฮสต์
การเปรียบเทียบ: การรันโค้ดโดยตรงบนโฮสต์อาจก่อให้เกิดความเสี่ยงด้านความปลอดภัยและนำไปสู่ความขัดแย้งในการอ้างอิง ในขณะที่ Docker ช่วยให้คุณแก้ไขปัญหาเหล่านี้ได้
โครงการนี้ใช้แนวทางไมโครเซอร์วิสซึ่งมีข้อดีสำคัญหลายประการ:
เมื่อสร้าง WEB IDE ที่ทันสมัยสำหรับการรันโค้ดจากระยะไกล มักมีการเปรียบเทียบโซลูชันทางสถาปัตยกรรมต่างๆ ลองพิจารณาสองแนวทาง:
แนวทาง A: สถาปัตยกรรมไมโครเซอร์วิส (gRPC + Redis + Docker)
คุณสมบัติ:
แนวทางนี้ช่วยให้การสื่อสารระหว่างบริการรวดเร็วและเชื่อถือได้ การแยกการทำงานของโค้ดออกจากกันสูง และการปรับขนาดที่ยืดหยุ่นเนื่องจากการทำคอนเทนเนอร์ เหมาะอย่างยิ่งสำหรับ WEB IDE ยุคใหม่ ซึ่งการตอบสนองและความปลอดภัยเป็นสิ่งสำคัญ
แนวทาง B: สถาปัตยกรรมโมโนลิธิกแบบดั้งเดิม (HTTP REST + การดำเนินการแบบรวมศูนย์)
คุณสมบัติ:
โซลูชันแบบโมโนลิธิก ซึ่งมักใช้ในเวอร์ชันแรกของ IDE เว็บนั้นใช้ HTTP REST และการรันโค้ดแบบรวมศูนย์ ระบบดังกล่าวจะประสบปัญหาด้านการปรับขนาด ความล่าช้าที่เพิ่มขึ้น และความยากลำบากในการรับรองความปลอดภัยเมื่อรันโค้ดของผู้อื่น
หมายเหตุ: ในบริบทสมัยใหม่ของการพัฒนา WEB IDE แนวทาง HTTP REST และการดำเนินการแบบรวมศูนย์นั้นด้อยกว่าข้อดีของสถาปัตยกรรมไมโครเซอร์วิส เนื่องจากไม่ได้ให้ความยืดหยุ่นและความสามารถในการปรับขนาดตามที่จำเป็น
กราฟแสดงให้เห็นอย่างชัดเจนว่าสถาปัตยกรรมไมโครเซอร์วิส (แนวทาง A) ให้ความหน่วงที่ต่ำกว่า ปริมาณงานที่สูงขึ้น ปลอดภัยกว่า และมีความสามารถในการปรับขนาดได้ดีกว่าเมื่อเปรียบเทียบกับโซลูชันแบบโมโนลิธิก (แนวทาง B)
องค์ประกอบสำคัญประการหนึ่งของการรักษาความปลอดภัยและเสถียรภาพของระบบคือการใช้ Docker ในโซลูชันของเรา บริการทั้งหมดจะถูกปรับใช้ในคอนเทนเนอร์ที่แยกจากกัน ซึ่งช่วยให้แน่ใจได้ว่า:
ด้านล่างนี้เป็นเวอร์ชันย่อของส่วนหลักของโค้ดที่แสดงให้เห็นถึงการทำงานของระบบ:
ระบบใช้รีจิสทรีทั่วโลก โดยที่แต่ละภาษาจะมีตัวรันเนอร์ของตัวเอง วิธีนี้ทำให้คุณสามารถเพิ่มการรองรับภาษาใหม่ได้อย่างง่ายดาย เพียงแค่คุณนำอินเทอร์เฟซตัวรันเนอร์ไปใช้งานและลงทะเบียน:
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)
และรับตัวรันเนอร์ที่สอดคล้องเพื่อดำเนินการโค้ด
สำหรับคำขอรหัสผู้ใช้แต่ละราย จะมีการสร้างคอนเทนเนอร์ 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 โดยที่:
ดังนั้นเมื่อเรียกใช้เมธอด Run ของตัวเรียกใช้งาน จะเกิดขึ้นต่อไปนี้:
ย่อส่วนของตรรกะหลักของการทำงานของโค้ด (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 } }
ในตัวอย่างขั้นต่ำนี้:
ส่วนสำคัญเหล่านี้แสดงให้เห็นว่าระบบรองรับการขยายได้ (การเพิ่มภาษาใหม่ได้ง่าย) อย่างไร และให้การแยกส่วนโดยการสร้างคอนเทนเนอร์ Docker แยกต่างหากสำหรับแต่ละคำขอ แนวทางนี้ช่วยปรับปรุงความปลอดภัย ความเสถียร และความสามารถในการปรับขนาดของแพลตฟอร์ม ซึ่งมีความสำคัญอย่างยิ่งสำหรับ WEB IDE สมัยใหม่
บทความนี้จะกล่าวถึงแพลตฟอร์มสำหรับการเรียกใช้โค้ดจากระยะไกลที่สร้างขึ้นบนสถาปัตยกรรมไมโครเซอร์วิสโดยใช้สแต็ก gRPC + Redis + Docker แนวทางนี้ช่วยให้คุณสามารถ:
การวิเคราะห์เชิงเปรียบเทียบแสดงให้เห็นว่าสถาปัตยกรรมไมโครเซอร์วิสมีประสิทธิภาพเหนือกว่าโซลูชันโมโนลิธิกแบบเดิมอย่างเห็นได้ชัดในเมตริกที่สำคัญทั้งหมด ข้อดีของแนวทางนี้ได้รับการยืนยันจากข้อมูลจริง ซึ่งทำให้เป็นโซลูชันที่น่าสนใจสำหรับการสร้างระบบที่มีประสิทธิภาพสูงและทนต่อความผิดพลาด
ผู้แต่ง : โอเล็กซี บอนดาร์
วันที่ : 2025-02-07