マむクロサヌビスアヌキテクチャ甚のLAppSアプリケヌションサヌバヌ

画像







背景



昚幎の12月20日、私は2週間も䌑暇を取りたした。 䌑暇䞭にすべきこず そうです-コヌド付き。 営業時間䞭に行う時間がないコヌド。 ここ数幎、私はコヌディングをほずんどしおいたせん。 手を぀ないでいた。 圌らは䌑暇䞭にどのようなコヌドを曞きたすか あなたのこずは知りたせんが、自転車を曞きたす。 なんで 倚くの理由があるかもしれたせんが、䞻な理由は私にずっお興味深いものです。 C ++ずLuaが倧奜きです。 bashずawkも倧奜きです。 石を投げないでください、それは個人的なものです、それは起こりたした。 私はJavaScriptがあたり奜きではありたせんただし、JSで䜕かが゚ンコヌドされた堎合、過去2幎間、これも個人的なものです。







最終的に䜕が起こったのか



䌑暇䞭の退屈の結果は、 LAppS-Lua Application Serverでした。 この䌑暇のコヌディングは6か月間続きたしたもちろん、12月以降、コヌドにあたり時間をかけたせんでした。これはgithubのコミットからわかりたす。 しかし、過去2週間で、䜕かを機胜させるために倚くの時間を奪いたした。







これは䜕ですか



名前が瀺すように、 LAppSは Luaアプリケヌションサヌバヌです。 Luaは非垞に人気のある蚀語であり、Nginx + LuaずOpenRestyラむブラリの組み合わせが䞖界䞭で積極的に䜿甚されおおり、Cloudflareには䟡倀がありたす。 開発の初期には、前述のNginxおよびTarantool甚のLuaモゞュヌルであるluvit.ioがすでに存圚しおいたした。 しかし、それらのどれもWebSocketをサポヌトしおいたせんでした。 LAppSはHTTPをサポヌトしおいたせん。 しかし今では、 LAppSはuWebSocketsよりもパフォヌマンスに優れおいたす倧芏暡なコンピュヌティングリ゜ヌスを消費したす。







䞻なアむデアは、マむクロサヌビスの開発サむクルを最小限に抑えるこずでした。 Luaの゚ントリのしきい倀はJavaScriptよりも䜎くなっおいたす。 䞭孊生でも簡単にLuaでプログラミングを始めるこずができるず確信しおいたす。 しかし、Lua甚のWebアプリケヌションを開発するために利甚できるすべおのツヌルは次のずおりであり、そのような最小゚ントリヌしきい倀はありたせん。







したがっお、開発における䞻な重点は、最小限のAPIずアプリケヌション䜜成の容易さ、およびアプリケヌションサヌバヌずサヌビスの構成の容易さでした。







詳现



LAppSの LuaアプリケヌションサヌビスはI / Oをブロックしたせん。 これはおそらく、Luaでスクリプトを䜜成するWebサヌバヌずの最倧の違いです。 LAppSは2぀の構成ファむルを䜿甚しお、WebSocketsサヌバヌの動䜜を構成し、アプリケヌションを展開したす。







デフォルトでは、構成ファむルがない堎合、LAppSはディストリビュヌションで実行されおいるデモアプリケヌションの実行を詊みたす。







ws.json-WebSocketsサヌバヌ構成ファむル



{ "listeners" : 1, "connection_weight": 0.7, "ip" : "0.0.0.0", "port" : 5083, "workers": { "workers" : 3, "max_connections" : 1000 }, "tls" : true, "tls_certificates" : { "ca" : "/opt/lapps/etc/ssl/cert.pem", "cert" : "/opt/lapps/conf/ssl/cert.pem", "key" : "/opt/lapps/conf/ssl/key.pem" }, "auto_fragment" : true, "max_inbound_message_size" : 300000 }
      
      







lapps.json-サヌビス構成ファむル



 { "directories" : { "applications" : "apps", "app_conf_dir" : "etc", "tmp": "tmp", "workdir": "workdir" }, "services" : { "echo" : { "internal" : false, "request_target" : "/echo", "protocol" : "raw", "instances" : 3 }, "echo_lapps" : { "internal" : false, "request_target" : "/echo_lapps", "protocol" : "LAppS", "instances" : 3 } } }
      
      







䞊蚘の䟋では、echoずecho_lappsの2぀のデモサヌビスが構成されおいたす。 サヌビスの名前はデフォルトであり、Luaアプリケヌションモゞュヌルの怜玢パスです。 本質的に、 LAppSのアプリケヌションは、特定のむンタヌフェむスに埓うモゞュヌルです。







内郚パラメヌタヌに加えお、他のすべおのサヌビス蚭定が必芁です。









組み立おず蚭眮



組み立おおよびむンストヌル手順は、プロゞェクトwikiペヌゞで読むこずができたす







準備されたdebパッケヌゞを䜿甚しお、ubuntu-xenialにむンストヌルできたす。







アプリケヌション/サヌビス



アプリケヌションは実際にはLuaモゞュヌルであり、動䜜が事前定矩されたいく぀かの事前定矩メ゜ッドが必芁です。









泚 onMessageメ゜ッドはブヌル倀を返す必芁がありたす。 倀を返すずき、それはfalseであり、メ゜ッドが呌び出された接続は壊れおいたすコヌド1000を閉じたす。







゚コヌサヌバヌrawプロトコルを実装する最も単玔なスケルトンアプリケヌション。



 myapp = {} myapp.__index = myapp; myapp["onStart"]=function() -- do something on start end myapp["onDisconnect"]=function(handler) -- handler - is a unique client identifier -- react on client disconnect end myapp["onShutdown"]=function() -- do something on shutdown end myapp["onMessage"]=function(handler,opcode, message) -- it is an echo, - we return back the same message local result, errmsg=ws:send(handler,opcode,message); if(not result) then print("myapp::OnMessage(): "..(errmsg or "none")); end return result; end return myapp;
      
      





このサヌビスの構成







  "myapp" : { "internal" : false, "request_target" : "/myapp", "instances" : 1, "protocol": "raw" }
      
      





LAppSプロトコル



LAppSプロトコル仕様は、Google JSON-RPC仕様に基づいおおり、次の重芁な違いがありたす。









仕様はgithubで入手できたす







LAppS察応アプリケヌション



完党なアプリケヌションコヌドは提䟛したせん。onMessageメ゜ッドの実装のみを提䟛したす。 デモアプリケヌションの詳现は、githubの゜ヌスにありたす。







 echo_lapps["onMessage"]=function(handler,msg_type, message) --       local switch={ [1] = function() --        -- (      ) --    local err_msg=nljson.decode([[{ "status" : 0, "error" : { "code" : -32600, "message": "This server does not accept Client Notifications without params" }, "cid" : 0 }]]); --     ws:send(handler,err_msg); --  WebSocket   1003 - " " ws:close(handler,1003); end, [2] = function() -- CN  . . local method=methods._cn_w_params_method[message.method] or echo_lapps.method_not_found; method(handler,message.params); end, [3] = function() --      local method=echo_lapps.method_not_found; method(handler); end, [4] = function() --     local method=methods._request_w_params_method[message.method] or echo_lapps.method_not_found; method(handler,message.params); end } --   switch[msg_type](); return true; end
      
      





自動ロヌドモゞュヌル



LAppSは、サヌビスを開始する前に、 nljson 、 ws 、 bcastのいく぀かのモゞュヌルをロヌドしたす。 モゞュヌルの仕様の詳现は、プロゞェクトwikiにありたす。







簡単に









モゞュヌルでの䜜業は、ネむティブLuaテヌブルでの䜜業ず倧差ありたせん。 さらに、Luaテヌブルは、単玔な割り圓おでnljsonナヌザヌデヌタに倉換されたす。 ただし、Luaはオブゞェクトキヌ倀ず配列を区別しないため、たずえば、空のLuaテヌブルの堎合、それらをnljsonに倉換するこずは障害です。 䟋えば







  local object=nljson.decode({})
      
      





これにより、オブゞェクトずいう名前のJSON配列が䜜成されたす。 したがっお、この初期化を䜿甚するこずをお勧めしたす。







  local object=nljson.decode('{}')
      
      





この定矩により、JSONオブゞェクトが䞀意に䜜成されたす。







さらに、このオブゞェクトはネむティブluaテヌブルずしお䜿甚できたす。







  object["test"]=""; print(object.test) object["map"]={ ["key1"] = "value", ["key2"] = 33 } print(object)
      
      





nljsonオブゞェクトの速床は、ネむティブのLuaテヌブルずほずんど異なりたせん。









クラむアントアプリケヌション



ここでは、すべおが蒞しカブよりも単玔です-ブラりザヌ向けのWebSockets APIの利点は、考え抜かれたシンプルなものです。

必芁なラむブラリcbor.js。 Webixは棒グラフの衚瀺に䜿甚されたす。

コヌドテキストにパスワヌドを入力しないでください。 これは単なるデモです。







顧客デモコヌド
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> </head> <body> <link rel="stylesheet" href="http://cdn.webix.com/edge/webix.css" type="text/css"> <script src="http://cdn.webix.com/edge/webix.js" type="text/javascript"></script> <script src="cbor.js" type="text/javascript"></script> <div id="chart" style="width:100%;height:300px;margin:3px"></div> <div id="stime" style="width:100%;height:300px;margin:3px"></div> <script> // globals window["secs_since_start"]=0; window["roundtrips"]=0; window["subscribed"]=false; window["lapps"]={ authkey : 0 }; // initial data set for the chart var dataset = [ { id:1, rps:0, second:0 } ] // the chart webix.ui({ id:"barChart", container:"chart", view:"chart", type:"bar", value:"#rps#", label:"#rps#", radius:0, gradient:"rising", barWidth:40, tooltip:{ template:"#rps#" }, xAxis:{ title:"Ticking RPS", template:"#second#", lines: false }, padding:{ left:10, right:10, top:50 }, data: dataset }); // might be a dialog instead. never do this in production. var login = { lapps : 1, method: "login", params: [ { user : "admin", password : "admin" } ] }; // echo request var echo= { lapps : 1, method: "echo", params: [ { authkey : 0 }, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25] ] }; // create a websocket var websocket = new WebSocket("wss://127.0.0.1:5083/echo_lapps"); websocket.binaryType = "arraybuffer"; // on response websocket.onmessage = function(event) { window.roundtrips=window.roundtrips+1; // CBOR to native JavaScript object var message = CBOR.decode(event.data); // Verifying the channel if(message.cid === 0) { if(message.status === 1) { if(window.lapps.authkey === 0) { if(typeof message.result[0].authkey !== "undefined") // authkey is arrived { window.lapps.authkey=message.result[0].authkey; echo.params[0].authkey = window.lapps.authkey; websocket.send(CBOR.encode(echo)); } else { console.log("No authkey: "+JSON.stringify(message)); } } else { websocket.send(CBOR.encode(echo)); // already authenticaed, may subscribe to OONs if(!window.subscribed) { var subscribe={ lapps : 1, method: "subscribe", params: [ { authkey: window.lapps.authkey } ], cid: 5 }; websocket.send(CBOR.encode(subscribe)); window.subscribed=true; } } } else { console.log("ERROR: "+JSON.stringify(message)); } } else if(message.cid === 5) // server time OON { console.log("OON is received"); webix.message({ text : message.message[0], type: "info", expire: 999 }); window.secs_since_start++; $$("barChart").add({rps: window.roundtrips, second: window.secs_since_start}); window.roundtrips=0; if(window.secs_since_start > 30 ) { $$("barChart").remove($$("barChart").getFirstId()); } } else // other OONs are just printed to console { console.log("OON: "+JSON.stringify(message)); } }; // login on connection websocket.onopen=function() { console.log('is open'); window.teststart=Date.now()/1000; websocket.send(CBOR.encode(login)); } // close connection if peer sent close frame websocket.onclose=function() { console.log("is closed"); } </script> </body> </html>
      
      





クラむアントアプリケヌションは、LAppSプロトコルの゚コヌクラむアントです。 アプリケヌションのサヌバヌ郚分は、時間を1秒に1回ブロヌドキャストし、このOONに埓っおスケゞュヌルが曎新されたす。







泚ブラりザで耇数のクラむアントを実行するず、ブロヌドキャストの数が増加したす。 ブロヌドキャストはonMessageから1秒に1回送信されたす。







これを修正するには、LAppSスタックの残りず通信するスタンドアロンアプリケヌションを実装する必芁がありたす。 この郚分は珟圚開発䞭です。








All Articles