Sparčiai vystantis debesų kompiuterijos ir mikro paslaugų architektūrai, didėja poreikis suteikti galimybę dinamiškai vykdyti įvairių programavimo kalbų kodą, užtikrinant saugumą, mastelį ir aukštą našumą. Šiame straipsnyje aprašomas projektas, įgyvendinantis kodo vykdymą izoliuotoje aplinkoje, ir aptariami pasirinkto architektūrinio sprendimo privalumai šiuolaikinei WEB IDE. Sistema pastatyta ant
Išsamiai aprašysime, kaip yra struktūrizuoti pagrindiniai sistemos komponentai, kuo jie skiriasi nuo alternatyvių sprendimų ir kodėl šių technologijų pasirinkimas leidžia pasiekti aukštą našumą ir saugumą.
Projektas sukurtas mikroservisų architektūros principu, leidžiančiu funkcionalumą skaidyti į savarankiškas paslaugas. Kiekvienas komponentas yra atsakingas už labai specializuotą užduotį, kuri užtikrina sistemos lankstumą, mastelį ir atsparumą gedimams.
Pagrindiniai komponentai:
Go privalumai:
Našumas ir mastelio keitimas: „Go“ vykdymo greitis yra didelis, o tai ypač svarbu tvarkant daug lygiagrečių užklausų.
Integruotas lygiagretumo palaikymas: Gorutinų ir kanalų mechanizmai leidžia įgyvendinti asinchroninę sąveiką tarp komponentų be sudėtingų kelių gijų modelių.
GRPC pranašumai:
Palyginimas: Skirtingai nuo REST API, gRPC užtikrina efektyvesnį ir patikimesnį ryšį tarp paslaugų, o tai labai svarbu labai vienu metu veikiančioms sistemoms.
Kodėl Redis?
Didelis našumas: „Redis“ gali atlikti daugybę operacijų per sekundę, todėl idealiai tinka užduočių ir rezultatų eilėms.
Pub/Sub ir List palaikymas: eilių ir prenumeratos mechanizmų diegimo paprastumas leidžia lengvai organizuoti asinchroninę paslaugų sąveiką.
Palyginimas su kitais pranešimų tarpininkais: skirtingai nei RabbitMQ ar Kafka, Redis reikalauja mažiau konfigūracijos ir užtikrina pakankamą našumą realiojo laiko sistemoms.
Dockerio vaidmuo:
Aplinkos izoliavimas: „Docker“ konteineriai leidžia paleisti kodą visiškai izoliuotoje aplinkoje, o tai padidina vykdymo saugumą ir sumažina konfliktų su pagrindine sistema riziką.
Valdymas ir nuoseklumas: naudojant „Docker“ sukuriama ta pati aplinka kodui kompiliuoti ir vykdyti, neatsižvelgiant į pagrindinę sistemą.
Palyginimas: Kodo paleidimas tiesiogiai pagrindiniame kompiuteryje gali kelti pavojų saugumui ir sukelti priklausomybės konfliktus, o „Docker“ leidžia išspręsti šias problemas.
Šiame projekte naudojamas mikropaslaugos metodas, kuris turi keletą reikšmingų pranašumų:
Kuriant modernius WEB IDE nuotoliniam kodo vykdymui, dažnai lyginami įvairūs architektūriniai sprendimai. Panagrinėkime du būdus:
A metodas: mikro paslaugų architektūra (gRPC + Redis + Docker)
Savybės:
Šis metodas užtikrina greitą ir patikimą ryšį tarp tarnybų, aukštą kodo vykdymo izoliaciją ir lankstų mastelio keitimą dėl konteinerizacijos. Jis puikiai tinka šiuolaikiniams WEB IDE, kur svarbus reagavimas ir saugumas.
B metodas: tradicinė monolitinė architektūra (HTTP REST + centralizuotas vykdymas)
Savybės:
Monolitiniai sprendimai, dažnai naudojami ankstyvosiose žiniatinklio IDE versijose, yra pagrįsti HTTP REST ir centralizuotu kodo vykdymu. Tokios sistemos susiduria su mastelio keitimo problemomis, padidėjusia delsa ir sunkumais užtikrinant saugumą vykdant kažkieno kodą.
Pastaba: Šiuolaikiniame WEB IDE kūrimo kontekste HTTP REST ir centralizuoto vykdymo metodas yra prastesnis už mikropaslaugų architektūros pranašumus, nes nesuteikia reikiamo lankstumo ir mastelio.
Diagrama aiškiai parodo, kad mikropaslaugų architektūra (A metodas) užtikrina mažesnę delsą, didesnį pralaidumą, geresnį saugumą ir mastelį, palyginti su monolitiniu sprendimu (B metodas).
Vienas iš pagrindinių sistemos saugumo ir stabilumo elementų yra Docker naudojimas. Mūsų sprendime visos paslaugos yra išdėstytos atskiruose konteineriuose, o tai užtikrina:
Žemiau pateikiama sutrumpinta pagrindinių kodo dalių versija, kuri parodo, kaip sistema:
Sistema naudoja pasaulinį registrą, kuriame kiekviena kalba turi savo paleidiklį. Tai leidžia lengvai pridėti naujų kalbų palaikymą, pakanka įdiegti bėgiko sąsają ir ją užregistruoti:
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") }
Naujos kalbos registravimo pavyzdys:
func init() { languages.Register("python", NewGenericRunner("python")) languages.Register("javascript", NewGenericRunner("javascript")) }
Taigi, gavusi užklausą, sistema skambina:
runner, err := languages.GetRunner(req.Language)
ir gauna atitinkamą bėgiką kodui vykdyti.
Kiekvienai vartotojo kodo užklausai sukuriamas atskiras Docker konteineris. Tai atliekama bėgiko metoduose (pavyzdžiui, Run). Pagrindinė konteinerio paleidimo logika yra RunInDockerStreaming funkcijoje:
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") } }
Ši funkcija sugeneruoja docker run komandą, kur:
Taigi, iškviečiant bėgiko bėgimo metodą, nutinka taip:
Sumažintas pagrindinės kodo vykdymo logikos fragmentas (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 } }
Šiame minimaliame pavyzdyje:
Šie pagrindiniai fragmentai parodo, kaip sistema palaiko išplėtimą (lengvą naujų kalbų pridėjimą) ir užtikrina izoliaciją, kiekvienai užklausai sukuriant atskirą Docker konteinerį. Šis metodas pagerina platformos saugumą, stabilumą ir mastelį, o tai ypač svarbu šiuolaikiniams WEB IDE.
Šiame straipsnyje aptariama nuotolinio kodo vykdymo platforma, sukurta mikropaslaugų architektūroje, naudojant gRPC + Redis + Docker krūvą. Šis metodas leidžia:
Lyginamoji analizė rodo, kad mikro paslaugų architektūra visais pagrindiniais rodikliais gerokai lenkia tradicinius monolitinius sprendimus. Šio metodo privalumus patvirtina tikri duomenys, todėl jis yra patrauklus sprendimas kuriant didelio našumo ir gedimams atsparias sistemas.
Autorius: Oleksii Bondar
Data: 2025–02–07