Hodeiko informatikaren eta mikrozerbitzuen arkitekturaren garapen azkarraren testuinguruan, gero eta beharra handiagoa dago hainbat programazio-lengoaietarako kodea dinamikoki exekutatzeko gaitasuna eskaintzeko, segurtasun, eskalagarritasun eta errendimendu handiko bermearekin. Artikulu honek ingurune isolatu batean kodearen exekuzioa inplementatzen duen proiektu bat deskribatzen du eta WEB IDE moderno baterako aukeratutako irtenbide arkitektonikoaren abantailak eztabaidatzen ditu. Sistema eraikita dago
Xehetasunez deskribatuko dugu nola egituratzen diren sistemaren osagai nagusiak, nola desberdintzen diren irtenbide alternatiboetatik eta zergatik teknologia hauek aukeratzeak errendimendu eta segurtasun handia lortzea ahalbidetzen duen.
Proiektua mikrozerbitzuen arkitekturaren printzipioan oinarritzen da, funtzionaltasuna zerbitzu independenteetan banatzeko aukera ematen duena. Osagai bakoitza zeregin oso espezializatu baten arduraduna da, eta sistemaren malgutasuna, eskalagarritasuna eta akatsen tolerantzia bermatzen ditu.
Osagai nagusiak:
Go-ren abantailak:
Errendimendua eta eskalagarritasuna: Go-k exekuzio abiadura handia du, eta hori bereziki garrantzitsua da eskaera paralelo ugari kudeatzeko.
Aldiberekotasun-laguntza integratua: goroutine eta kanalen mekanismoek osagaien arteko elkarrekintza asinkronoa ezartzea ahalbidetzen dute hari anitzeko eredu konplexurik gabe.
gRPCren abantailak:
Konparazioa: REST APIa ez bezala, gRPC-k zerbitzuen arteko komunikazio eraginkorragoa eta fidagarriagoa eskaintzen du, eta hori funtsezkoa da sistema oso aldiberekoentzat.
Zergatik Redis?
Errendimendu handia: Redis-ek segundoko eragiketa ugari kudeatu ditzake, eta horrek zeregin eta emaitzen ilaretarako aproposa da.
Pub/Sub eta Zerrenden laguntza: ilarak eta harpidetza-mekanismoak ezartzearen sinpletasunak errazten du zerbitzuen arteko interakzio asinkronoak antolatzea.
Beste mezu-artekari batzuekin alderatzea: RabbitMQ edo Kafka ez bezala, Redis-ek konfigurazio gutxiago behar du eta denbora errealeko sistemetarako nahikoa errendimendu eskaintzen du.
Dockerren rola:
Ingurunearen isolamendua: Docker edukiorrek kodea guztiz isolatu batean exekutatzeko aukera ematen dute, eta horrek exekuzioaren segurtasuna areagotzen du eta sistema nagusiarekin gatazkak izateko arriskua murrizten du.
Kudeatzeko gaitasuna eta koherentzia: Docker erabiltzeak ingurune bera eskaintzen du kodea konpilatzeko eta exekutatzeko, ostalari-sistema edozein dela ere.
Konparazioa: Ostalari zuzenean kodea exekutatzeak segurtasun arriskua sor dezake eta menpekotasun-gatazkak sor ditzake, Docker-ek arazo horiek konpontzeko aukera ematen dizu.
Proiektu honek mikrozerbitzuen ikuspegia erabiltzen du, eta horrek abantaila esanguratsu batzuk ditu:
Urrutiko kodea exekutatzeko WEB IDE modernoak eraikitzean, hainbat irtenbide arkitektoniko alderatu ohi dira. Azter ditzagun bi ikuspegi:
A ikuspegia: mikrozerbitzuen arkitektura (gRPC + Redis + Docker)
Ezaugarriak:
Ikuspegi honek zerbitzuen arteko komunikazio azkarra eta fidagarria eskaintzen du, kodearen exekuzioaren isolamendu handia eta eskalatze malgua eskaintzen du edukiontziaren ondorioz. Ezin hobea da WEB IDE modernoetarako, non sentikortasuna eta segurtasuna garrantzitsuak diren.
B Planteamendua: Arkitektura Monolitiko Tradizionala (HTTP REST + Exekuzio Zentralizatua)
Ezaugarriak:
Irtenbide monolitikoak, sarritan web IDEen lehen bertsioetan erabiliak, HTTP REST eta kode zentralizatuan exekutatzeko oinarritzen dira. Sistema horiek eskalatzeko arazoak, latentzia handiagoa eta segurtasuna bermatzeko zailtasunak dituzte beste norbaiten kodea exekutatzean.
Oharra: WEB IDE garapenaren testuinguru modernoan, HTTP REST eta exekuzio zentralizatuaren ikuspegia mikrozerbitzuen arkitekturaren abantailen aldean txikiagoa da, ez baitu beharrezko malgutasuna eta eskalagarritasuna ematen.
Grafikoak argi erakusten du mikrozerbitzuen arkitekturak (A Planteamendua) latentzia txikiagoa, errendimendu handiagoa, segurtasun eta eskalagarritasun hobea eskaintzen duela soluzio monolitikoarekin alderatuta (B Planteamendua).
Sistemaren segurtasunaren eta egonkortasunaren funtsezko elementuetako bat Docker-en erabilera da. Gure soluzioan, zerbitzu guztiak edukiontzi bereizietan zabaltzen dira, eta horrek bermatzen du:
Jarraian, sistema nola erakusten duen kodearen atal nagusien bertsio txikitua dago:
Sistemak erregistro global bat erabiltzen du, non hizkuntza bakoitzak bere korrikalaria duen. Honek hizkuntza berrietarako laguntza erraz gehitzeko aukera ematen du, nahikoa da korrikalariaren interfazea ezartzea eta erregistratzea:
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") }
Hizkuntza berri bat erregistratzeko adibidea:
func init() { languages.Register("python", NewGenericRunner("python")) languages.Register("javascript", NewGenericRunner("javascript")) }
Horrela, eskaera bat jasotzean, sistemak dei egiten du:
runner, err := languages.GetRunner(req.Language)
eta dagokion korrikalaria jasotzen du kodea exekutatzeko.
Erabiltzaile-kode eskaera bakoitzerako, Docker edukiontzi bereizi bat sortzen da. Hau korrikalari metodoen barruan egiten da (adibidez, Run-en). Edukiontzia exekutatzeko logika nagusia RunInDockerStreaming funtzioan dago:
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") } }
Funtzio honek docker run komandoa sortzen du, non:
Horrela, korrikalariaren Run metodoa deitzean, honako hau gertatzen da:
Kode exekutatzeko logika nagusiaren zati minimoa (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 } }
Adibide minimo honetan:
Gako-zati hauek sistemak hedagarritasuna (hizkuntza berriak gehitzea erraza) onartzen duen erakusten dute eta isolamendua eskaintzen du eskaera bakoitzerako Docker edukiontzi bat sortuz. Ikuspegi honek plataformaren segurtasuna, egonkortasuna eta eskalagarritasuna hobetzen ditu, eta hori bereziki garrantzitsua da WEB IDE modernoentzat.
Artikulu honek gRPC + Redis + Docker pila erabiliz mikrozerbitzuen arkitekturan eraikitako urruneko kodea exekutatzeko plataforma bat aztertzen du. Ikuspegi honek aukera ematen dizu:
Analisi konparatibo batek erakusten du mikrozerbitzuen arkitekturak soluzio monolitiko tradizionalak nabarmen gainditzen dituela funtsezko metrika guztietan. Ikuspegi honen abantailak datu errealek berresten dituzte, eta horrek irtenbide erakargarria bihurtzen du errendimendu handiko eta akatsekiko tolerantzia duten sistemak sortzeko.
Egilea: Oleksii Bondar
Data: 2025-02-07