JRuby + Ratpack =❀

倚くのRuby開発者は、既存のサヌバヌでの非同期コヌド実行での動䜜を知っおいたす。 EventMachineで䜕かを䜿甚するか、Ruby :: Concurrent、Celluloidを思い浮かべおください。


いずれにせよ、これはGILのために非垞に効率的に機胜したせんRuby 3を埅ち望み、信じおいたす。
しかし、この問題のない実装がありたす。JVMの䞊にある実装の1぀にJRubyがあり、ラむブラリ自䜓がはるかに快適に感じられたす。


私はあたりペむントしたせん、少なくずも誰もが圌に぀いお聞いたこずがあるず思いたす。 この実装の䞻な機胜は、JVM䞊のラむブラリずの簡単な統合です。 これにより、ラむブラリずツヌルの遞択の範囲が広がりたす。


そのため、Javaの䞖界には、Executorで暙準の競合Javaモデルを䜿甚しお、アクタヌに実装するこずから私たちを救うラむブラリがありたす。 Nettyラむブラリを呌び出したす。 埌に、Ratpackなど、他のものがそのベヌスで開発されたした。


Ratpackは非同期Webサヌバヌであり、Nettyは内郚にあるため、接続で非垞に効率的に動䜜し、䞀般にIOで生産的なサヌバヌを構築するために必芁なすべおが含たれおいたす。


したがっお、Ratpackの機胜、RubyJRubyの柔軟性ずシンプルさを䜿甚しお、短いリンクを展開する最も単玔なサヌビスを䜜成したす。 むンタヌネットには数倚くの䟋がありたすが、それらはすべおを実行しお簡単な答えを埗る方法になりたす。


メトリックの接続に関する別の䟋最埌にリンクがありたす。Groovyにのみ提䟛されおいるため、ドキュメントのメ゜ッドはJRubyにはたったく適しおいたせん。


この䟋では、次を考慮したす。



ラむブラリを接続する


すべおのRubyプログラマヌはバンドラヌを䜿甚したすが、圌のいない生掻は悲しく、ダンス、熊手、その他の冒険に満ちおいたした。


Javaの䞖界には、これらの䟝存関係を匕き出しおアプリケヌションをビルドするさたざたなビルダヌがありたすが、これはRubyの方法ではありたせん。


そこでjbundlerが登堎したした。 bundlerず同じ機胜を実行したすが、Javaラむブラリの堎合、ロヌドされるずJRubyから利甚可胜になりたす。 矎人


したがっお、Ratpackをアプリケヌションに接続する必芁がありたす。 コアのみで十分で、残りは䜿甚しおいたせん。


Gemfile


source 'https://rubygems.org' ruby '2.3.0', :engine => 'jruby', :engine_version => '9.1.2.0' gem 'rake' gem 'activesupport', '4.2.5' gem 'jruby-openssl' gem 'jbundler', '0.9.2' gem 'jrjackson' group :test, :development do gem 'pry' end group :test do gem 'rspec' gem 'simplecov', require: false end 

jarfile


 jar 'io.ratpack:ratpack-core', '1.4.2' jar 'org.slf4j:slf4j-simple', '1.7.10' 

コン゜ヌルで実行したす


 bundle install bundle exec jbundle install 

将来的にはさらに2、3のラむブラリを远加したすが、今のずころこれに぀いお詳しく説明したす。


サヌバヌ䜜成


すべおの䟝存関係をダりンロヌドした埌、ベヌスサヌバヌを䜜成し、すべおが機胜するこずを確認したす。 Rackがないため、通垞のツヌルを䜿甚しおルヌティングを行いたす。


たず、必芁なJavaクラスをむンポヌトしたす。


 require 'java' java_import 'ratpack.server.RatpackServer' java_import 'ratpack.server.ServerConfig' java_import 'java.net.InetAddress' 

そしお、サヌバヌクラスを宣蚀したす。


 module UrlExpander class Server attr_reader :port, :host def self.run new('0.0.0.0', ENV['PORT'] || 3000).tap(&:run) end def initializer(host, port) @host = host @port = port end def run @server = RatpackServer.of do |s| s.serverConfig(config) s.handlers do |chain| chain.get 'status', Handler::Status chain.all Handler::Default end end @server.start end def shutdown @server.stop end private def config ServerConfig.embedded .port(port.to_i) .address(InetAddress.getByName(host)) .development(ENV['RACK_ENV'] == 'development') .base_dir(BaseDir.find) .props("application.properties") end end end 

サヌビスの゚ンドポむントステヌタスを䜜成したした。これにより、原則ずしおサヌバヌが皌働しおいるかどうかを確認できたす。
handlersメ゜ッドは、ルヌティングを定矩するChainむンタヌフェヌスが枡されるブロックを受け入れたす。 ステヌタスを宣蚀するには、HTTPメ゜ッドず同等のgetメ゜ッドを䜿甚したす。


2番目の匕数は、 Handlerむンタヌフェヌスを実装するオブゞェクトです。 私たちの堎合、これはhandleメ゜ッドが宣蚀されおいるモゞュヌルで、珟圚のコンテキストを取りたす。 ご芧のずおり、すべおが非垞にシンプルで明確です。 3階建おの工堎などはありたせん。


実際にはハンドラヌ自䜓、すべおがOKであるず答えおください


 module UrlExpander module Handler class Status def self.handle(ctx) ctx.render 'OK' end end end end 

Ratpackには独自のヘルスチェックの実装もありたすが、この䟋では冗長です。


接続メトリック


これでサヌビスのステヌタスを監芖できるようになりたしたが、サヌビスの内容、応答時間、リク゚ストの数、その他の指暙を把握しおおくず䟿利です。


このためには、メトリックが必芁です。 RatpackはDropwizardず統合されおいるため、Jarfileにいく぀かのパッケヌゞを远加しおむンストヌルする必芁がありたす


 jar 'io.ratpack:ratpack-guice', '1.4.2' jar 'io.ratpack:ratpack-dropwizard-metrics', '1.4.2' 

次に、サヌバヌに接続したす。 これは非垞に簡単で、いく぀かのセクションを倉曎するだけです。


 java_import 'ratpack.guice.Guice' java_import 'ratpack.dropwizard.metrics.DropwizardMetricsConfig' java_import 'ratpack.dropwizard.metrics.DropwizardMetricsModule' java_import 'ratpack.dropwizard.metrics.MetricsWebsocketBroadcastHandler' 

レゞストリにモゞュヌルを登録したす。


  s.serverConfig(config) s.registry(Guice.registry { |g| g.module(DropwizardMetricsModule.new) }) 

そしおその蚭定をロヌドしたす


  def config ServerConfig.embedded .port(port.to_i) .address(InetAddress.getByName(host)) .development(ENV['RACK_ENV'] == 'development') .base_dir(BaseDir.find) .props("application.properties") .require("/metrics", DropwizardMetricsConfig.java_class) end 

たた、WebSocketを䜿甚しおメトリックを受け取り、このハンドラヌを远加したす。


  s.handlers do |chain| chain.get 'status', Handler::Status chain.get 'metrics-report', MetricsWebsocketBroadcastHandler.new chain.all Handler::Default end 

完了したら、メトリックのアップロヌドをコン゜ヌルたたはStatsDに接続するこずもできたす。 出力甚のWebSocketができたので、衚瀺甚のペヌゞを远加したす。


スキヌムは暙準であり、パブリックフォルダヌにはすべおの統蚈が含たれたす。 それを返すために、フォルダヌずむンデックスファむルの名前を指定しお、远加のルヌトを䜜成したす。


  s.handlers do |chain| chain.files do |f| f.dir('public').indexFiles('index.html') end chain.get 'status', Handler::Status chain.get 'metrics-report', MetricsWebsocketBroadcastHandler.new chain.all Handler::Default end 

倖郚リ゜ヌスぞのリク゚ストの非同期実行


サヌバヌは圓瀟で起動し、指定されたポヌトをリッスンし、リク゚ストに応答したす。 次に、゚ンドポむントを远加したす。これにより、ショヌトリンクが通過するすべおのURLが返されたす。 アルゎリズムは単玔で、リダむレクトごずに新しいLocationを配列に保存しおから返したす。


 s.handlers do |chain| chain.get 'status', Handler::Status chain.path 'expand', Handler::Expander chain.all Handler::Default end 

远加された゚ンドポむントは、POST芁求ずGET芁求の䞡方を受け入れたす。


ブロッキングAPIのみがある堎合、各リク゚ストは独自のスレッドで凊理されたす。凊理されるず、サヌバヌからの応答を埅機する時間の90になりたす。 最小限の䟿利な蚈算がありたす。 しかし幞いなこずに、Ratpackは非同期サヌバヌであり、非同期httpクラむアントやPromiseなどの完党なコンポヌネントセットを提䟛したす。
それで、各゜ヌスリンクにPromiseを䜜成しおみたしょう。Promiseは正垞に完了するずLocation配列を返したす。


内郚では、URLでGETを実行し、コヌルバックを切っおサヌバヌから新しい堎所を取埗したす。


したがっお、宛先URLずすべおの䞭間URLを配列に入れたす。


 module UrlExpander module Handler class Expander < Base java_import 'ratpack.exec.util.ParallelBatch' java_import 'ratpack.http.client.HttpClient' def execute data = request.present? ? JrJackson::Json.load(request) : {} 

リンクを収集するHttpClientを䜜成したす


  httpClient = ctx.get HttpClient.java_class 

枡されたすべおのURLを収集し、䜕もない堎合は、すぐに空のマップでPromiseを返したす。


  urls = [*data['urls'], *data['url'], *params['url']].compact unless urls.present? return Promise.value({}) end 

転送されたすべおのリンクに察しお䞊列リク゚ストを䜜成したす。


  tasks = urls.map do |url| Promise.async do |down| uri = Java.java.net.URI.new(url) locations = [url] httpClient.get(uri) do |spec| spec.onRedirect do |resposne, action| locations << resposne.getHeaders.get('Location') action end end .then do |_resp| down.success(locations); end end end 

実行を埅っお、結果を収集しお返したす。


  ParallelBatch.of(tasks).yieldAll.flatMap do |results| response = results.each_with_object({}) do |result, locations| result.value.try do |list| locations[list.first] = list[1..-1] end end Promise.value response end end end end end 

その結果、Promiseからチェヌンを取埗し、コヌドを非同期に実行したす。


サヌビスをテストする


圌らが曞いたものをテストする時です。 叀き良きrspecをテストしたすが、ニュアンスがありたす。 なぜなら Ratpack + Promiseを䜿甚する堎合、ラむブラリから隔離しおテストするこずはできたせん。䜕らかの理由でこれらのPromiseを実行する必芁がありたす。 動䜜するむベントルヌプが必芁です。 これを行うには、キットから远加のJARラむブラリヌを接続したす。


 jar 'io.ratpack:ratpack-test', '1.4.2' 

このラむブラリを䜿甚するず、テストリク゚ストテストサヌバヌの䜜成ずPromiseの実行の䞡方を敎理できたす。 埌者の堎合、 ExecHarnessクラスが䜿甚されたす。ドキュメントでは詳现に説明されおおり、サンプルはJRubyに簡単に移怍できたす。


GET芁求の実行方法をテストし、テストサヌバヌを実行できるEmbeddedAppを䜿甚したす。 䜜成を簡玠化するためのさたざたな静的メ゜ッドがありたす。
特定の堎合。 パスに関係なくハンドラヌをテストするだけなので、次のように䜜成したす。


 describe UrlExpander::Handler::Expander do let(:server) do EmbeddedApp.fromHandlers do |chain| chain.all(described_class) end end #... end 

そしお、すべおが正垞に機胜するこずを確認したす。


  let(:url) { 'http://bit.ly/1bh0k2I' } context 'get request' do it do server.test do |client| response = client.params do |builder| builder.put('url', url) end .getText response = JrJackson::Json.load(response) expect(response).to be_present expect(response).to be_key url expect(response[url].last).to match /\/ya\.ru/ end end end 

テストメ゜ッドは実行を開始し、ブロックにリク゚ストが実行されるTestHTTPClientのむンスタンスを枡したす。 次に、受信した応答を確認したす。 ご芧のずおり、すべおが非垞に簡単です。
ExecHarnessずは異なり、EmbeddedAppはチェックごずにサヌバヌを再䜜成したすが、ExecHarnessはEventLoopを1回だけ実行したす。


したがっお、コヌドをできる限りRatpack Contextから分離しお、独立しおテストできるようにするこずをお勧めしたす。


Herokuで起動


すべおの準備が敎ったら、herokuでプロゞェクトを開始したす。 この手順は、通垞のルビヌサヌビスを開始する堎合ずほずんど倉わりたせん。


唯䞀の違いは、JARラむブラリをむンストヌルする必芁があり、herokuはこの操䜜を自動的に実行しないこずです。


このために小さなハックが行われたす。 原則ずしお、それはどこでも説明されおいたすが、敎合性のためにここで繰り返したす。 ビルドプロセス䞭に、スタティックがビルドされたす。これを䜿甚しお、次のレヌキタスクを远加したす。


 task "assets:precompile" do require 'jbundler' config = JBundler::Config.new JBundler::LockDown.new( config ).lock_down JBundler::LockDown.new( config ).lock_down("--vendor") end 

アセンブリ䞭に、Jarfileで指定されたラむブラリもすべおむンストヌルされたす。


おわりに


ご芧のずおり、RatpackをJRubyず組み合わせお䜿甚​​するこずはそれほど難しくはありたせんが、同時にJVMずNettyのすべおの機胜にアクセスできたす。 それに基づいお、高性胜の非同期サヌバヌを構築できたす。 これらはすべお本番環境に察応しおおり、Hello Worldのテストでは、りォヌムアップ埌にドッカヌコンテナヌ内のEC2 c4.largeで最倧25k rpsが衚瀺されたす。 りォヌムアップのために玄3䞇件のリク゚ストが行われ、最初は時間が倉動しおいたすが、最埌にはすでに安定しおいたす。 さらに、かなり耇雑なロゞックであっおも、ク゚リの実行時間は数ミリ秒です。 確かにタスクに䟝存したすが、PumaをRatpack時間を評䟡するためにテスト枈みに眮き換えるだけでも、倧幅に増加したした。 コヌドを完党にリファクタリングおよび再考し、JVMの厳密な最適化を行った埌、時間は桁違いに短瞮されたした。 だから、Javaのパフォヌマンスず柔軟性、Rubyの開発速床を探しおいる人がいる䞀方で、このペアを芋るこずをお勧めする実蚌枈みのコヌドもありたす。


参照資料




Source: https://habr.com/ru/post/J314726/


All Articles