Bulut bilişim ve mikro hizmet mimarisinin hızla gelişmesi bağlamında, çeşitli programlama dilleri için güvenlik, ölçeklenebilirlik ve yüksek performans garantisiyle kodu dinamik olarak yürütme olanağı sağlama ihtiyacı artmaktadır. Bu makale, izole bir ortamda kod yürütmeyi uygulayan bir projeyi açıklar ve modern bir WEB IDE için seçilen mimari çözümün avantajlarını tartışır. Sistem şu şekilde oluşturulmuştur:
Sistemin ana bileşenlerinin nasıl yapılandırıldığını, alternatif çözümlerden nasıl farklılaştığını ve bu teknolojilerin seçiminin neden yüksek performans ve güvenlik elde etmeyi sağladığını detaylı olarak açıklayacağız.
Proje, işlevselliği bağımsız hizmetlere bölmenize olanak tanıyan mikro hizmet mimarisi ilkesi üzerine inşa edilmiştir. Her bileşen, sistemin esnekliğini, ölçeklenebilirliğini ve hata toleransını sağlayan oldukça uzmanlaşmış bir görevden sorumludur.
Ana bileşenler:
Go'nun Avantajları:
Performans ve ölçeklenebilirlik: Go, özellikle çok sayıda paralel isteğin işlenmesi için önemli olan yüksek bir yürütme hızına sahiptir.
Dahili eşzamanlılık desteği: Goroutine ve kanalların mekanizmaları, karmaşık çoklu iş parçacığı desenleri olmadan bileşenler arasında eşzamansız etkileşimin uygulanmasına olanak tanır.
gRPC’nin Avantajları:
Karşılaştırma: REST API'nin aksine gRPC, yüksek oranda eşzamanlı sistemler için kritik öneme sahip olan servisler arası iletişimi daha verimli ve güvenilir hale getirir.
Neden Redis?
Yüksek performans: Redis saniyede çok sayıda işlemi işleyebilir, bu da onu görev ve sonuç kuyrukları için ideal hale getirir.
Pub/Sub ve Liste Desteği: Kuyruk ve abonelik mekanizmalarının uygulanmasının basitliği, servisler arasındaki asenkron etkileşimlerin düzenlenmesini kolaylaştırır.
Diğer mesaj brokerlarıyla karşılaştırma: RabbitMQ veya Kafka'nın aksine Redis daha az yapılandırma gerektirir ve gerçek zamanlı sistemler için yeterli performans sağlar.
Docker'ın rolü:
Ortam izolasyonu: Docker konteynerları, kodun tamamen izole edilmiş bir ortamda çalıştırılmasına olanak tanır; bu da yürütme güvenliğini artırır ve ana sistemle çakışma riskini azaltır.
Yönetilebilirlik ve tutarlılık: Docker'ı kullanmak, ana bilgisayar sisteminden bağımsız olarak kodu derlemek ve yürütmek için aynı ortamı sağlar.
Karşılaştırma: Kodu doğrudan ana bilgisayarda çalıştırmak güvenlik riski oluşturabilir ve bağımlılık çatışmalarına yol açabilir, ancak Docker bu sorunları çözmenize olanak tanır.
Bu proje, bir dizi önemli avantajı olan bir mikro servis yaklaşımını kullanıyor:
Uzaktan kod yürütme için modern WEB IDE'leri oluştururken, genellikle çeşitli mimari çözümler karşılaştırılır. İki yaklaşımı ele alalım:
Yaklaşım A: Mikroservis mimarisi (gRPC + Redis + Docker)
Özellikler:
Bu yaklaşım, hızlı ve güvenilir hizmetler arası iletişim, kod yürütmenin yüksek izolasyonu ve konteynerleştirme nedeniyle esnek ölçekleme sağlar. Tepkiselliğin ve güvenliğin önemli olduğu modern WEB IDE'leri için mükemmeldir.
Yaklaşım B: Geleneksel Monolitik Mimari (HTTP REST + Merkezi Yürütme)
Özellikler:
Genellikle web IDE'lerinin erken sürümlerinde kullanılan monolitik çözümler, HTTP REST ve merkezi kod yürütmeye dayanır. Bu tür sistemler ölçekleme sorunları, artan gecikme ve başka birinin kodunu yürütürken güvenliği sağlamada zorluklarla karşı karşıyadır.
Not: WEB IDE geliştirmenin modern bağlamında, HTTP REST ve merkezi yürütme yaklaşımı, gerekli esnekliği ve ölçeklenebilirliği sağlamadığı için mikroservis mimarisinin avantajlarına göre daha düşüktür.
Grafikte mikroservis mimarisinin (Yaklaşım A) monolitik çözüme (Yaklaşım B) kıyasla daha düşük gecikme, daha yüksek verim, daha iyi güvenlik ve ölçeklenebilirlik sağladığı açıkça görülmektedir.
Sistem güvenliği ve istikrarının temel unsurlarından biri Docker kullanımıdır. Çözümümüzde, tüm hizmetler ayrı kapsayıcılarda dağıtılır ve bu da şunları sağlar:
Aşağıda sistemin nasıl çalıştığını gösteren ana kod bölümlerinin küçültülmüş bir versiyonu bulunmaktadır:
Sistem, her dilin kendi çalıştırıcısına sahip olduğu küresel bir kayıt defteri kullanır. Bu, yeni diller için desteği kolayca eklemenize olanak tanır, çalıştırıcı arayüzünü uygulamak ve kaydetmek yeterlidir:
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") }
Yeni bir dil kaydetme örneği:
func init() { languages.Register("python", NewGenericRunner("python")) languages.Register("javascript", NewGenericRunner("javascript")) }
Bu nedenle bir istek alındığında sistem şunu çağırır:
runner, err := languages.GetRunner(req.Language)
ve kodu çalıştırmak için karşılık gelen koşucuyu alır.
Her kullanıcı kodu isteği için ayrı bir Docker konteyneri oluşturulur. Bu, koşucu yöntemlerinin içinde yapılır (örneğin, Run'da). Konteyneri çalıştırmanın ana mantığı RunInDockerStreaming işlevindedir:
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") } }
Bu fonksiyon, şu şekilde docker run komutunu üretir:
Bu nedenle runner'ın Run metodu çağrıldığında aşağıdakiler gerçekleşir:
Kod yürütmenin ana mantığının (executor.ExecuteCode) küçültülmüş parçası:
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 } }
Bu minimal örnekte:
Bu temel parçalar, sistemin genişletilebilirliği (yeni dillerin kolayca eklenmesi) nasıl desteklediğini ve her istek için ayrı bir Docker konteyneri oluşturarak izolasyon sağladığını gösterir. Bu yaklaşım, özellikle modern WEB IDE'leri için önemli olan platformun güvenliğini, kararlılığını ve ölçeklenebilirliğini iyileştirir.
Bu makale, gRPC + Redis + Docker yığınını kullanan bir mikro hizmet mimarisi üzerine inşa edilmiş uzaktan kod yürütme platformunu ele almaktadır. Bu yaklaşım şunları yapmanıza olanak tanır:
Karşılaştırmalı bir analiz, mikroservis mimarisinin tüm temel metriklerde geleneksel monolitik çözümlerden önemli ölçüde daha iyi performans gösterdiğini göstermektedir. Bu yaklaşımın avantajları gerçek verilerle doğrulanmıştır ve bu da onu yüksek performanslı ve hataya dayanıklı sistemler oluşturmak için çekici bir çözüm haline getirir.
Yazar: Oleksii Bondar
Tarih: 2025–02–07