paint-brush
IDE אינטרנט זה מריץ את הקוד שלך בענן - מבלי להמיס את המחשב הנייד שלךעל ידי@oleksiijko
היסטוריה חדשה

IDE אינטרנט זה מריץ את הקוד שלך בענן - מבלי להמיס את המחשב הנייד שלך

על ידי Oleksii Bondar12m2025/02/21
Read on Terminal Reader

יותר מדי זמן; לקרוא

הפרויקט בנוי על העיקרון של ארכיטקטורת מיקרו-שירותים, המאפשר לחלק את הפונקציונליות לשירותים עצמאיים. כל רכיב אחראי על משימה מיוחדת מאוד, המבטיחה גמישות, מדרגיות וסובלנות תקלות של המערכת. הפרויקט מבוסס על שפת התכנות Go.
featured image - IDE אינטרנט זה מריץ את הקוד שלך בענן - מבלי להמיס את המחשב הנייד שלך
Oleksii Bondar HackerNoon profile picture
0-item
1-item

בהקשר להתפתחות המהירה של ארכיטקטורת מחשוב ענן וארכיטקטורת מיקרו-שירותים, יש צורך הולך וגובר לספק יכולת ביצוע דינמי של קוד לשפות תכנות שונות עם הבטחה לאבטחה, מדרגיות וביצועים גבוהים. מאמר זה מתאר פרויקט המיישם ביצוע קוד בסביבה מבודדת, ודן ביתרונות של הפתרון הארכיטקטוני הנבחר עבור WEB IDE מודרני. המערכת בנויה על לָלֶכֶת , שימושים gRPC לאינטראקציה בין שירותים יעילה, Redis כמתווך הודעות ו דוקר לבודד את סביבת הביצוע. א WebSocket השרת משמש להצגת תוצאות בזמן אמת.

נתאר בהרחבה כיצד בנויים המרכיבים העיקריים של המערכת, במה הם שונים מפתרונות חלופיים ומדוע הבחירה בטכנולוגיות אלו מאפשרת השגת ביצועים ואבטחה גבוהים.


1. סקירה אדריכלית ומרכיבים עיקריים

הפרויקט בנוי על העיקרון של ארכיטקטורת מיקרו-שירותים, המאפשר לחלק את הפונקציונליות לשירותים עצמאיים. כל רכיב אחראי על משימה מיוחדת מאוד, המבטיחה גמישות, מדרגיות וסובלנות תקלות של המערכת.


מרכיבים עיקריים:


  • gRPC משמש לתקשורת בין שירותים. זה אידיאלי להעברת נתונים בין שירותי מיקרו בשל:
    • פרוטוקול בינארי (Protocol Buffers): מבטיח העברת נתונים מהירה וקומפקטית.
    • הקלדה קפדנית: עוזרת למנוע שגיאות בהעברת ועיבוד נתונים.
    • זמן אחזור נמוך: שהוא קריטי לשיחות פנימיות בין שירותים (לדוגמה, בין שרת gRPC לתור Redis).
  • שרת WebSocket: מספק תקשורת דו-כיוונית עם הלקוח להעברת תוצאות ביצוע בזמן אמת. הוא נרשם לתור עם תוצאות ומעביר את הנתונים ללקוח, ומספק תצוגה מיידית של יומני הידור וביצוע.
  • Worker: שירות עצמאי המושך משימות מתור, יוצר סביבת עבודה זמנית, מאמת ומפעיל קוד בקונטיינר מבודד של Docker, ולאחר מכן מפרסם את תוצאות הביצוע בחזרה לתור.
  • Redis: משמש כמתווך הודעות להעברת משימות משרת gRPC ל-Worker ותוצאות מה-Worker לשרת WebSocket. היתרונות של Redis הם מהירות גבוהה, תמיכה ב-Pub/Sub ושינוי קנה מידה קל.
  • מודולים פנימיים:
    • מהדר ו-Docker Runner: מודול האחראי על הפעלת פקודות Docker עם רישום זרם, המאפשר ניטור בזמן אמת של תהליך ההידור והביצוע.
    • רצי שפה: שלב לוגיקה לאימות, קומפילציה וביצוע של קוד עבור שפות שונות (C, C++, C#, Python, JavaScript, TypeScript). כל רץ מיישם ממשק יחיד, המפשט את הרחבת הפונקציונליות עבור שפות חדשות.


התרשים שלהלן מציג את זרימת הנתונים מהלקוח לתהליך העבודה ובחזרה באמצעות gRPC, Redis ו-WebSocket.


2. טכנולוגיות ורציונל לבחירה

לָלֶכֶת

היתרונות של Go:

  • ביצועים ומדרגיות: ל-Go יש מהירות ביצוע גבוהה, שחשובה במיוחד לטיפול במספר רב של בקשות מקבילות.

  • תמיכת מקבילות מובנית: המנגנונים של גורוטיינים וערוצים מאפשרים יישום אינטראקציה אסינכרונית בין רכיבים ללא דפוסי ריבוי השחלות מורכבים.


gRPC

היתרונות של gRPC:

  • העברת נתונים יעילה: הודות לפרוטוקול ההעברה הבינארי (Protocol Buffers), gRPC מספק חביון נמוך ועומס רשת נמוך.
  • הקלדה חזקה: זה מפחית את מספר השגיאות הקשורות לפרשנות שגויה של נתונים בין שירותי מיקרו.
  • תמיכה בסטרימינג דו-כיווני: זה שימושי במיוחד להחלפת יומנים ותוצאות ביצוע בזמן אמת.

השוואה: בניגוד ל-REST API, gRPC מספק תקשורת יעילה ואמינה יותר בין שירותים, שהיא קריטית עבור מערכות במקביל.


Redis

למה Redis?

  • ביצועים גבוהים: Redis יכול להתמודד עם מספר רב של פעולות בשנייה, מה שהופך אותו לאידיאלי לתורי משימות ותוצאות.

  • תמיכה ב-Pub/Sub ו-List: הפשטות של הטמעת תורים ומנגנוני מנוי מקלה על ארגון אינטראקציות אסינכרוניות בין שירותים.

  • השוואה למתווכים אחרים של הודעות: בניגוד ל-RabbitMQ או Kafka, Redis דורש פחות תצורה ומספק ביצועים מספיקים למערכות בזמן אמת.


דוקר

תפקידו של דוקר:

  • בידוד סביבה: קונטיינרים של Docker מאפשרים להריץ קוד בסביבה מבודדת לחלוטין, מה שמגביר את בטיחות הביצוע ומפחית את הסיכון להתנגשויות עם המערכת הראשית.

  • יכולת ניהול ועקביות: שימוש ב-Docker מספק את אותה סביבה להידור וביצוע קוד, ללא קשר למערכת המארחת.

  • השוואה: הפעלת קוד ישירות על המארח יכולה להוות סיכון אבטחה ולהוביל להתנגשויות תלות, בעוד Docker מאפשר לך לפתור את הבעיות הללו.


WebSocket

  • בזמן אמת: חיבור מתמשך עם הלקוח מאפשר העברת נתונים (יומנים, תוצאות ביצוע) באופן מיידי.
  • חווית משתמש משופרת: עם WebSocket, ה-IDE יכול להציג באופן דינמי את תוצאות הקוד.


3. היתרונות של ארכיטקטורת Microservice

פרויקט זה משתמש בגישת מיקרו-שירות, שיש לה מספר יתרונות משמעותיים:


  • קנה מידה עצמאי: ניתן לשנות את קנה המידה של כל שירות (שרת gRPC, Worker, WebSocket, Redis) בנפרד בהתאם לעומס. הדבר מאפשר שימוש יעיל במשאבים והתאמה מהירה לגידול במספר הבקשות.
  • סובלנות תקלות: חלוקת המערכת למודולים עצמאיים פירושה שכשל בשירות מיקרו אחד אינו מוביל לכשל של המערכת כולה. זה מגביר את היציבות הכללית ומקל על התאוששות משגיאות.
  • גמישות של פיתוח ופריסה: Microservices מפותחים ונפרסים באופן עצמאי, מה שמקל על הכנסת תכונות ועדכונים חדשים. זה גם מאפשר לך להשתמש בטכנולוגיות המתאימות ביותר עבור כל שירות ספציפי.
  • קלות אינטגרציה: ממשקים מוגדרים בבירור (למשל באמצעות gRPC) מקלים על חיבור שירותים חדשים ללא שינויים גדולים בארכיטקטורה הקיימת.
  • בידוד ואבטחה: כל מיקרו-שירות יכול לפעול במיכל משלו, מה שממזער את הסיכונים הכרוכים בביצוע קוד לא בטוח ומספק שכבת הגנה נוספת.


4. ניתוח השוואתי של גישות אדריכליות

בעת בניית WEB IDEs מודרניים לביצוע קוד מרחוק, לעתים קרובות משווים פתרונות ארכיטקטוניים שונים. הבה נבחן שתי גישות:


גישה א': ארכיטקטורת Microservice (gRPC + Redis + Docker)


  • חביון: 40 אלפיות השנייה
  • תפוקה: 90 יחידות
  • אבטחה: 85 יחידות
  • מדרגיות: 90 יחידות


תכונות:
גישה זו מספקת תקשורת בין-שירותית מהירה ואמינה, בידוד גבוה של ביצוע קוד ושינוי קנה מידה גמיש עקב קונטיינריזציה. זה מושלם עבור WEB IDEs מודרניים, שבהם היענות ואבטחה חשובים.


גישה ב': ארכיטקטורה מונוליתית מסורתית (HTTP REST + ביצוע מרכזי)


  • חביון: 70 אלפיות השנייה
  • תפוקה: 65 יחידות
  • אבטחה: 60 יחידות
  • מדרגיות: 70 יחידות


תכונות:
פתרונות מונוליטיים, המשמשים לעתים קרובות בגרסאות מוקדמות של IDEs אינטרנט, מבוססים על HTTP REST וביצוע קוד מרכזי. מערכות כאלה מתמודדות עם בעיות קנה מידה, חביון מוגבר וקשיים בהבטחת אבטחה בעת ביצוע קוד של מישהו אחר.


הערה: בהקשר המודרני של פיתוח WEB IDE, גישת ה-HTTP REST וביצוע ריכוזי נחותים מהיתרונות של ארכיטקטורת מיקרו-שירותים, מכיוון שהיא אינה מספקת את הגמישות וההרחבה הדרושים.


הדמיה של מדדים השוואתיים

הגרף מראה בבירור שארכיטקטורת המיקרו-שירותים (גישה A) מספקת חביון נמוך יותר, תפוקה גבוהה יותר, אבטחה ומדרגיות טובה יותר בהשוואה לפתרון המונוליטי (גישה ב').


5. ארכיטקטורת Docker: בידוד ומדרגיות

אחד המרכיבים המרכזיים של אבטחת ויציבות המערכת הוא השימוש ב-Docker. בפתרון שלנו, כל השירותים פרוסים בקונטיינרים נפרדים, מה שמבטיח:


  • בידוד של סביבת הביצוע: כל שירות (שרת gRPC, Worker, WebSocket) ומתווך הודעות (Redis) פועלים בקונטיינר משלו, מה שממזער את הסיכון של קוד לא בטוח שישפיע על המערכת הראשית. במקביל, הקוד שהמשתמש מריץ בדפדפן (למשל דרך ה-WEB IDE) נוצר ומבוצע בקונטיינר נפרד של Docker עבור כל משימה. גישה זו מבטיחה שקוד שעלול להיות לא בטוח או שגוי לא יכול להשפיע על פעולת התשתית הראשית.
  • עקביות סביבתית: שימוש ב-Docker מבטיח שההגדרות נשארות זהות בסביבות הפיתוח, הבדיקות והייצור, מה שמפשט מאוד את איתור הבאגים ומבטיח חיזוי של ביצוע קוד.
  • גמישות מדרגיות: ניתן לשנות את קנה המידה של כל רכיב באופן עצמאי, מה שמאפשר התאמה יעילה לעומסים משתנים. לדוגמה, ככל שמספר הבקשות גדל, ניתן להפעיל קונטיינרים נוספים של Worker, שכל אחד מהם ייצור קונטיינרים נפרדים לביצוע קוד משתמש.

בסכימה זו, Worker לא רק מקבל משימות מ-Redis, אלא גם יוצר קונטיינר נפרד (Container: Code Execution) עבור כל בקשה לביצוע קוד משתמש בנפרד.


6. קטעים קטנים של קוד

להלן גרסה ממוזערת של חלקי הקוד העיקריים המדגימה כיצד המערכת:

  1. קובע איזו שפה להפעיל באמצעות רישום הרצים הגלובלי.
  2. מפעיל קונטיינר 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 IDEs מודרניים.

7. מסקנה

מאמר זה דן בפלטפורמה לביצוע קוד מרחוק הבנויה על ארכיטקטורת מיקרו-שירות באמצעות מחסנית gRPC + Redis + Docker. גישה זו מאפשרת לך:


  • צמצם את זמן האחזור והבטח תפוקה גבוהה הודות לתקשורת בין שירותים יעילה.
  • הבטח אבטחה על ידי בידוד ביצוע קוד בקונטיינרים נפרדים של Docker, שבהם נוצר קונטיינר נפרד עבור כל בקשת משתמש.
  • קנה מידה גמיש של המערכת עקב קנה מידה עצמאי של שירותי מיקרו.
  • לספק תוצאות בזמן אמת באמצעות WebSocket, שחשוב במיוחד עבור IDEs WEB מודרניים.


ניתוח השוואתי מראה כי ארכיטקטורת המיקרו-שירות עולה בהרבה על פתרונות מונוליטיים מסורתיים בכל מדדי המפתח. היתרונות של גישה זו מאושרים על ידי נתונים אמיתיים, מה שהופך אותה לפתרון אטרקטיבי ליצירת מערכות בעלות ביצועים גבוהים וסובלנות תקלות.



מחבר: אולקסי בונדר
תאריך: 2025–02–07