我有一个用Delphi制造的Web服务器,它负责从MySQL服务器获取数据并检索以JSON
格式的文件。这是一个简单的示例,说明了如何从loteamentos
获取DB
的列表。
type
TWM = class(TWebModule)
...
procedure WMactLoteamentosAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
...
procedure TWM.WMactLoteamentosAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
qryLoteamentos: TFDQuery;
JsonArray: TJSONArray;
JsonObject: TJSONObject;
begin
Response.ContentType := APPLICATION_JSON + '; ' + CHARSET_UTF8;
// Search for loteamentos
qryLoteamentos := TFDQuery.Create(nil);
with qryLoteamentos do
begin
Connection := FDConnection;
Active := False;
SQL.Clear;
Open('SELECT * FROM ' + T_LOTEAMENTO);
if qryLoteamentos.RecordCount > 0 then
begin
JsonArray := TJSONArray.Create;
try
First;
while not Eof do
begin
JsonObject := TJSONObject.Create;
CapturarCamposLoteamento(JsonObject, qryLoteamentos);
JsonArray.AddElement(JsonObject);
Next;
end;
finally
Response.Content := JsonArray.ToString;
JsonArray.DisposeOf;
end;
end
else
handleEmptyResponse(Response);
end;
end;
该方法的逻辑并不太重要,只需要从数据库中获取表并在JSON
中检索表就可以了。
应用程序将在一台计算机上运行,MySQL将来自该计算机的本地主机,并且用户将通过外部IP和端口访问Web服务器。
因此,如果服务器在外部IP例如为45.65.89.187的计算机上的端口9070上运行
该方法将通过以下方式调用:
GET -> http://45.65.89.187/loteamentos
它将为我检索到以下内容:
[{"id":1,"nome":"RESIDENCIAL ...","metros":"348516,57"},
{"id":2,"nome":"RESIDENCIAL ...","metros":"215465,65"}]
问题
我的问题是,假设有100个人在手机上使用我的API。想象有100个人多次呼叫同一端点/loteamentos
。它不会使服务器崩溃吗?
我想知道人们在同一时间呼叫同一端点不会在同一Thread
中创建一条线路并打扰服务器吗?我不应该让Web服务器在MultiThreading中运行吗?
我做了什么
我测试了在4部电话中多次从Web服务器调用终结点。网络服务器开始以2MB的速度运行,经过多次调用后,它在几分钟内达到40MB的运行速度。然后,我停止调用它,但它保持40MB且不会变小。
答案 0 :(得分:1)
当来自客户端的第一个请求进入时,WebBroker应用程序将创建TWebModule的第一个实例。 每当客户端的第二个HTTP请求到达WebBroker应用程序时,WebBroker框架都会搜索先前创建的WebModule实例是否空闲(idle =它没有执行请求操作处理程序)。 如果没有空闲的WebModule实例,则将实例化一个新的TWebModule。 该代码在Web.WebReq.pas中,函数TWebRequestHandler.ActivateWebModules:TComponent; 默认情况下,WebBroker应用程序将在负载很高时最多创建32个TWebModule实例。该数字32由属性Application.MaxConnections定义。 请注意,同步请求已经是多线程的,并且请求处理程序中的所有代码都必须是线程安全的。 一个TWebModule实例一次只能处理1个请求,其他任何并发请求将由TWebModule的其他实例处理。 由于可能有多个TWebModule实例并行处理请求,单个TWebModule实例中的查询应使用其自己的专用DB连接实例。 在测试负载处理时,您可以添加一个较长的Sleep(10000)以具有许多繁忙的Web请求处理程序,并检查您的应用程序如何响应。这样就很容易达到Application.MaxConnections的限制,从而导致异常。
您的Web服务器可能消耗40MB,因为WebBroker框架在负载达到峰值(Application.InactiveConnections + Application.ActiveConnections = 10)时创建了10个TWebModule实例。如果您的TWebModule在其构造函数中分配了对象,或者在DFM中包含许多组件,则它们将全部保留。
还请注意,请求完成后,任何特定于客户端的数据都不应驻留在TWebModule本身中。客户端A可能在第一个请求期间由TWebModule实例1服务,而实例2则同时为客户端B服务。 在下一个同时请求中,实例2可能为客户端A提供服务,实例1可以为客户端B提供服务。
答案 1 :(得分:0)
Am在Delphi 10.1下使用普通的webBroker
我有一个接受Json字符串的服务器。我解析它并将相同的数据发送回客户端。
我的假设是,默认情况下,Webbroker拥有32个线程,随着来自客户端的并发请求开始到达服务器并停止在32个线程(请参阅web.webreq.pas),该线程将逐渐创建。
为了测试这种情况,我创建了一个简单的Client程序,该程序具有一个For循环,该循环将通过带有JSon字符串的请求在服务器上不断触发。
对于10,000个请求,大约需要14秒。仅创建一个webModule实例...这很好,因为for循环以串行和同步模式发送请求。
当我运行客户端程序的另一个并行实例时,创建了WebModule的第二个实例。当我再运行一个客户端的并行实例时,将创建WebModule的第三个实例...等等。
现在是有趣的部分。...
还记得只有我的第一个客户端程序正在运行时,进行10K请求所花费的时间是14秒吗?我观察到以下情况-随着我增加并发客户端的数量, 处理时间也增加了 实际上,完成以下操作需要42秒(总共3万个请求) 3个客户端程序同时触发请求。
如果服务器确实是声称的真正的多线程服务器,那么至少有32个并发客户端请求,那么花费时间应该是SAME处理来自每个客户端的单个10K记录,对吗?
您能否说明Web Broker是否真正是多线程的?如果是这样,我想念的是什么?
我在此处同时附加了客户端和服务器源。
** 遵循客户代码
enter code here
unit URestclient;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, REST.Types, REST.Client,
Data.Bind.Components, Data.Bind.ObjectScope, Vcl.StdCtrls, JsonTools, system.DateUtils;
type
TForm21 = class(TForm)
RESTClient1: TRESTClient;
RESTRequest1: TRESTRequest;
RESTResponse1: TRESTResponse;
Button1: TButton;
Memo1: TMemo;
Label2: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
Label3: TLabel;
Label4: TLabel;
Edit3: TEdit;
procedure Button1Click(Sender: TObject);
private
procedure AfterRun;
function SendJSonDataToIVR(MachineID, JS: String): boolean;
{ Private declarations }
public
{ Public declarations }
end;
var
Form21: TForm21;
RESTClient: TRESTClient;
RESTRequest: TRESTRequest;
RESTResponse: TRESTResponse;
N: TJSONnode;
JS : String;
i : integer;
implementation
{$R *.dfm}
procedure TForm21.AfterRun;
begin
Memo1.Lines.Add(RESTResponse.content);
end;
procedure TForm21.Button1Click(Sender: TObject);
var
st, et : TDatetime;
S : String;
begin
Label2.caption := 'Started!';
Label2.Repaint;
memo1.Clear;
JS := '{"CallStatus":"t","CallType":"N","BookingType":"I","CallerType":"C","RoomNo":"1788882",'
+'"Channel":"1191","OriginalChannel":"1191","MobileNumber":"09123456789","DialTry":"1","MaxTry":"1"'
+',"ServerID":"3","StartTime":"2020-10-13 10:41:40","EndTime":"2020-10-13 10:41:40",'
+'"TransferNumber":"","Dstatus":"","userid":"1"}';
S := 'http://' + trim(edit2.text) + ':' + trim(edit1.text) + '/tconnected';
st := now;
for i := 0 to strtoint(trim(Edit3.Text)) do
SendJSonDataToIVR(S, JS);
et := now;
Label2.Caption := 'Time Taken (in ms) ' + MilliSecondsBetween(et, st).ToString;
end;
Function TForm21.SendJSonDataToIVR(MachineID : String; JS : String) : boolean;
begin
SendJSonDataToIVR := false;
RESTClient := TRESTClient.Create('nil');
RESTRequest := TRESTRequest.Create(nil);
RESTRequest.Client := RESTClient;
RESTClient.BaseURL := MachineID;
RESTRequest.ClearBody;
RESTRequest.AddBody(JS, ctAPPLICATION_JSON);
RESTRequest.Method := TRESTRequestMethod.rmPost;
RESTResponse := TRESTResponse.Create(nil);
RESTRequest.Response := RESTResponse;
RESTRequest.Execute();
//Memo1.Lines.Add(RESTResponse.content);
// RESTRequest.ExecuteAsync(AfterRun, true, True);
end;
end.
** 遵循服务器代码
unit wmTConnected;
interface
uses
System.SysUtils, System.Classes, Web.HTTPApp, JSontools,
Data.DB,
FireDAC.Stan.Def,
FireDAC.Phys.PG,
FireDAC.Phys.PGDef,
FireDAC.DApt,
FireDAC.Stan.Async,
FireDAC.Stan.Option,
FireDAC.Comp.Client, FireDAC.Stan.Intf,
FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Pool,
FireDAC.Phys, FireDAC.ConsoleUI.Wait;
type
TWebModule2 = class(TWebModule)
procedure WebModule2TConnectedAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
private
{ Private declarations }
procedure ParseJson(var Request: TWebRequest);
public
{ Public declarations }
end;
var
WebModuleClass: TComponentClass = TWebModule2;
N : TJsonNode;
implementation
{%CLASSGROUP 'System.Classes.TPersistent'}
{$R *.dfm}
procedure TWebModule2.ParseJson(var Request: TWebRequest);
begin
N := TJsonNode.Create;
try
N.Parse(Request.Content);
except
begin
Response.Content := 'Something went wrong during parsing of Incoming Json from Client machine';
end;
end;//try
end;
procedure TWebModule2.WebModule2TConnectedAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
ParseJson(Request);
writeln(Timetostr(now));
Response.Content := Request.Content;
end;
end.
紧随Unit JsonTools
type
TJsonNode = class
public
{ A parent node owns all children. Only destroy a node if it has no parent.
To destroy a child node use Delete or Clear methods instead. }
destructor Destroy; override;
{ GetEnumerator adds 'for ... in' statement support }
function GetEnumerator: TJsonNodeEnumerator;
{ Loading and saving methods }
procedure LoadFromStream(Stream: TStream);
procedure SaveToStream(Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SaveToFile(const FileName: string);
{ Convert a json string into a value or a collection of nodes. If the
current node is root then the json must be an array or object. }
procedure Parse(const Json: string);
{ The same as Parse, but returns true if no exception is caught }
function TryParse(const Json: string): Boolean;
{ Add a child node by node kind. If the current node is an array then the
name parameter will be discarded. If the current node is not an array or
object the Add methods will convert the node to an object and discard
its current value.
Note: If the current node is an object then adding an existing name will
overwrite the matching child node instead of adding. }
function Add(const Name: string; K: TJsonNodeKind = nkObject): TJsonNode; overload;
function Add(const Name: string; B: Boolean): TJsonNode; overload;
function Add(const Name: string; const N: Double): TJsonNode; overload;
function Add(const Name: string; const S: string): TJsonNode; overload;
{ Delete a child node by index or name }
procedure Delete(Index: Integer); overload;
procedure Delete(const Name: string); overload;
{ Remove all child nodes }
procedure Clear;
{ Get a child node by index. EJsonException is raised if node is not an
array or object or if the index is out of bounds.
See also: Count }
function Child(Index: Integer): TJsonNode; overload;
{ Get a child node by name. If no node is found nil will be returned. }
function Child(const Name: string): TJsonNode; overload;
{ Search for a node using a path string }
function Find(const Path: string): TJsonNode;
{ Format the node and all its children as json }
function ToString: string; override;
{ Root node is read only. A node the root when it has no parent. }
property Root: TJsonNode read GetRoot;
{ Parent node is read only }
property Parent: TJsonNode read FParent;
{ Kind can also be changed using the As methods:
Note: Changes to Kind cause Value to be reset to a default value. }
property Kind: TJsonNodeKind read FKind write SetKind;
{ Name is unique within the scope }
property Name: string read GetName write SetName;
{ Value of the node in json e.g. '[]', '"hello\nworld!"', 'true', or '1.23e2' }
property Value: string read GetValue write Parse;
{ The number of child nodes. If node is not an object or array this
property will return 0. }
property Count: Integer read GetCount;
{ AsJson is the more efficient version of Value. Text returned from AsJson
is the most compact representation of the node in json form.
Note: If you are writing a services to transmit or receive json data then
use AsJson. If you want friendly human readable text use Value. }
property AsJson: string read GetAsJson write Parse;
{ Convert the node to an array }
property AsArray: TJsonNode read GetAsArray;
{ Convert the node to an object }
property AsObject: TJsonNode read GetAsObject;
{ Convert the node to null }
property AsNull: TJsonNode read GetAsNull;
{ Convert the node to a bool }
property AsBoolean: Boolean read GetAsBoolean write SetAsBoolean;
{ Convert the node to a string }
property AsString: string read GetAsString write SetAsString;
{ Convert the node to a number }
property AsNumber: Double read GetAsNumber write SetAsNumber;
end;