paint-brush
Ši žiniatinklio IDE paleidžia jūsų kodą debesyje – neištirpdama nešiojamojo kompiuteriopateikė@oleksiijko
Nauja istorija

Ši žiniatinklio IDE paleidžia jūsų kodą debesyje – neištirpdama nešiojamojo kompiuterio

pateikė Oleksii Bondar12m2025/02/21
Read on Terminal Reader

Per ilgai; Skaityti

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. Projektas paremtas Go programavimo kalba.
featured image - Ši žiniatinklio IDE paleidžia jūsų kodą debesyje – neištirpdama nešiojamojo kompiuterio
Oleksii Bondar HackerNoon profile picture
0-item
1-item

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 Eik , naudoja gRPC už efektyvią tarnybų sąveiką, Redis kaip žinučių tarpininkas ir Dokeris izoliuoti vykdymo aplinką. A WebSocket serveris naudojamas rezultatams rodyti realiu laiku.

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ą.


1. Architektūrinė apžvalga ir pagrindiniai komponentai

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:


  • gRPC naudojamas tarpžinybiniam ryšiui palaikyti. Idealiai tinka duomenų perkėlimui tarp mikro paslaugų, nes:
    • Dvejetainis protokolas (Protocol Buffers): užtikrina greitą ir kompaktišką duomenų perdavimą.
    • Griežtas spausdinimas: padeda išvengti duomenų perdavimo ir apdorojimo klaidų.
    • Maža delsa: tai labai svarbu vidiniams skambučiams tarp paslaugų (pavyzdžiui, tarp gRPC serverio ir Redis eilės).
  • „WebSocket“ serveris: teikia dvipusį ryšį su klientu, kad realiuoju laiku būtų perduodami vykdymo rezultatai. Jis užsiprenumeruoja eilę su rezultatais ir persiunčia duomenis klientui, akimirksniu parodydamas kompiliavimo ir vykdymo žurnalus.
  • Darbuotojas: nepriklausoma paslauga, kuri paima užduotis iš eilės, sukuria laikiną darbo aplinką, patvirtina ir vykdo kodą izoliuotame Docker konteineryje, o tada paskelbia vykdymo rezultatus atgal į eilę.
  • Redis: naudojamas kaip pranešimų tarpininkas užduotims iš gRPC serverio perkelti į darbuotoją ir rezultatus iš darbuotojo į WebSocket serverį. Redis pranašumai yra didelė sparta, Pub/Sub palaikymas ir lengvas mastelio keitimas.
  • Vidiniai moduliai:
    • Kompiliatorius ir „Docker Runner“: modulis, atsakingas už „Docker“ komandų vykdymą su srauto registravimu, leidžiančiu realiuoju laiku stebėti kompiliavimo ir vykdymo procesą.
    • Kalbų bėgikai: sujunkite įvairių kalbų (C, C++, C#, Python, JavaScript, TypeScript) kodo patvirtinimo, kompiliavimo ir vykdymo logiką. Kiekvienas bėgikas įdiegia vieną sąsają, kuri supaprastina naujų kalbų funkcijų išplėtimą.


Toliau pateiktoje diagramoje parodytas duomenų srautas iš kliento į darbuotojo procesą ir atgal naudojant gRPC, Redis ir WebSocket.


2. Technologijos ir pasirinkimo pagrindimas

Eik

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ų.


grRPC

GRPC pranašumai:

  • Efektyvus duomenų perdavimas: Dvejetainio perdavimo protokolo (Protocol Buffers) dėka gRPC užtikrina mažą delsą ir mažą tinklo apkrovą.
  • Stiprus spausdinimas: tai sumažina klaidų, susijusių su neteisingu duomenų interpretavimu tarp mikroservisų, skaičių.
  • Dviejų krypčių srautinio perdavimo palaikymas: tai ypač naudinga keičiantis žurnalais ir vykdymo rezultatais realiuoju laiku.

Palyginimas: Skirtingai nuo REST API, gRPC užtikrina efektyvesnį ir patikimesnį ryšį tarp paslaugų, o tai labai svarbu labai vienu metu veikiančioms sistemoms.


Redis

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.


Dokeris

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.


WebSocket

  • Realus laikas: nuolatinis ryšys su klientu leidžia duomenis (žurnalus, vykdymo rezultatus) perkelti akimirksniu.
  • Pagerinta vartotojo patirtis: Naudodamas „WebSocket“, IDE gali dinamiškai rodyti kodo rezultatus.


3. Microservice Architecture privalumai

Šiame projekte naudojamas mikropaslaugos metodas, kuris turi keletą reikšmingų pranašumų:


  • Nepriklausomas mastelio keitimas: kiekviena paslauga (gRPC serveris, Worker, WebSocket serveris, Redis) gali būti keičiamas atskirai, atsižvelgiant į apkrovą. Tai leidžia efektyviai panaudoti išteklius ir greitai prisitaikyti prie augančio užklausų skaičiaus.
  • Gedimų tolerancija: sistemos padalijimas į nepriklausomus modulius reiškia, kad vienos mikropaslaugos gedimas nesukelia visos sistemos gedimo. Tai padidina bendrą stabilumą ir supaprastina atsigavimą po klaidų.
  • Kūrimo ir diegimo lankstumas: mikropaslaugos kuriamos ir diegiamos nepriklausomai, o tai supaprastina naujų funkcijų ir atnaujinimų įdiegimą. Tai taip pat leidžia naudoti tinkamiausias technologijas kiekvienai konkrečiai paslaugai.
  • Integravimo paprastumas: aiškiai apibrėžtos sąsajos (pvz., per gRPC) leidžia lengvai prijungti naujas paslaugas be didelių esamos architektūros pakeitimų.
  • Izoliavimas ir saugumas: kiekviena mikro paslauga gali veikti savo konteineryje, o tai sumažina riziką, susijusią su nesaugaus kodo vykdymu, ir suteikia papildomą apsaugos lygį.


4. Lyginamoji architektūrinių požiūrių analizė

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)


  • Vėlavimas: 40 ms
  • Pralaidumas: 90 vnt
  • Apsauga: 85 vnt
  • Mastelio keitimas: 90 vienetų


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)


  • Vėlavimas: 70 ms
  • Pralaidumas: 65 vnt
  • Apsauga: 60 vnt
  • Mastelio keitimas: 70 vnt


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.


Lyginamųjų metrikų vizualizacija

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).


5. Docker architektūra: izoliacija ir mastelio keitimas

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:


  • Vykdymo aplinkos izoliavimas: kiekviena paslauga (gRPC serveris, Worker, WebSocket serveris) ir pranešimų tarpininkas (Redis) veikia savo konteineryje, o tai sumažina riziką, kad nesaugus kodas paveiks pagrindinę sistemą. Tuo pačiu metu kodas, kurį vartotojas paleidžia naršyklėje (pavyzdžiui, per WEB IDE), sukuriamas ir vykdomas atskirame Docker konteineryje kiekvienai užduočiai. Šis metodas užtikrina, kad galimai nesaugus ar klaidingas kodas negalėtų paveikti pagrindinės infrastruktūros veikimo.
  • Aplinkos nuoseklumas: naudojant „Docker“ užtikrinama, kad nustatymai išliks tokie patys kūrimo, testavimo ir gamybos aplinkoje, o tai labai supaprastina derinimą ir užtikrina kodo vykdymo nuspėjamumą.
  • Mastelio lankstumas: kiekvienas komponentas gali būti keičiamas atskirai, o tai leidžia efektyviai prisitaikyti prie kintančių apkrovų. Pavyzdžiui, didėjant užklausų skaičiui, galite paleisti papildomus Worker konteinerius, kurių kiekvienas sukurs atskirus konteinerius vartotojo kodui vykdyti.

Šioje schemoje darbuotojas ne tik gauna užduotis iš Redis, bet ir sukuria atskirą konteinerį (Container: Code Execution) kiekvienai užklausai vykdyti vartotojo kodą atskirai.


6. Mažos kodo dalys

Žemiau pateikiama sutrumpinta pagrindinių kodo dalių versija, kuri parodo, kaip sistema:

  1. Nustato, kurią kalbą paleisti naudojant visuotinį bėgikų registrą.
  2. Paleidžia „Docker“ konteinerį, kad būtų paleistas vartotojo kodas, naudojant funkciją „RunInDockerStreaming“.



1. Kalbos aptikimas per bėgiko registraciją

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.


2. Docker konteinerio paleidimas 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:


  • vaizdas yra Docker vaizdas, pasirinktas konkrečiai kalbai (apibrėžta pagal bėgiko konfigūraciją).
  • dir yra katalogas su kodu, sukurtu šiai užklausai.
  • cmdStr yra kodo kompiliavimo arba vykdymo komanda.


Taigi, iškviečiant bėgiko bėgimo metodą, nutinka taip:


  • Funkcija RunInDockerStreaming paleidžia Docker konteinerį, kuriame vykdomas kodas.
  • Vykdymo žurnalai yra perduodami į logCh kanalą, kuris leidžia perduoti informaciją apie vykdymo procesą realiu laiku.


3. Integruotas vykdymo procesas

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:


  • Kalbos aptikimas atliekamas iškvietus languages.GetRunner(req.Language), kuris leidžia lengvai pridėti naujos kalbos palaikymą.
  • „Docker“ konteinerio paleidimas įgyvendinamas naudojant Compile / Run metodus, kurie naudoja „RunInDockerStreaming“, kad būtų vykdomas kodas atskirai.


Š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.

7. Išvada

Šiame straipsnyje aptariama nuotolinio kodo vykdymo platforma, sukurta mikropaslaugų architektūroje, naudojant gRPC + Redis + Docker krūvą. Šis metodas leidžia:


  • Sumažinkite delsą ir užtikrinkite didelį pralaidumą dėl efektyvaus tarpžinybinio ryšio.
  • Užtikrinkite saugumą atskirdami kodo vykdymą atskiruose Docker konteineriuose, kur kiekvienai vartotojo užklausai sukuriamas atskiras konteineris.
  • Lankstus sistemos mastelio keitimas dėl nepriklausomo mikropaslaugų mastelio.
  • Pateikite rezultatus realiuoju laiku per WebSocket, o tai ypač svarbu šiuolaikinėms WEB IDE.


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