REST Batching: Ograniczanie liczby zapytań

Kilka wpisów wcześniej zacząłem tematykę micro-serwisów oraz wzorca bramki. Jednym z wyzwań podczas rozłupywania monolitu jest zbyt wysoka liczba zapytań do innych serwisów, co powoduje utratę wydajności. 

Jeśli w monolicie była klasa np. CustomersRepository to teraz będzie to kompletnie nowa usługa. Wysłanie wiadomości do takiej usługo odbywa się przez jakiś protokół – w przypadku REST zwykle jest to HTTP. W monolicie nie było ważne to, że wywołaliśmy np. GetCustomerById(1), potem GetCustomerById(2) itp. Mam na myśli, że wywołania do repozytorium były bardzo tanie ponieważ odbywały się w pamięci, w tym samym procesie.

W przypadku HTTP, wiąże to się z ogromnym obciążeniem ze względu np. na potrzebę wysyłania HTTP header za każdym razem. Dlaczego więc nie wysyłać kilku zapytań w jednym pakiecie?

Taki mechanizm nazywa się po prostu batching. Załóżmy, że mamy następujący kontroler REST API:

public class CustomersController : ApiController { public int GetCustomerById(int id) { Random random = new Random(id); return random.Next(100); } }

Jeśli chcemy dane klientów o identyfikatorach 2 i 3, możemy w batchu wysłać je w następujący sposób:

POST http://piotr-pc:8289/api/$batch HTTP/1.1 Content-Type: multipart/mixed; boundary="8530da6b-1778-487a-a058-e78640a096e0" Host: piotr-pc:8289 Content-Length: 336 Expect: 100-continue Connection: Keep-Alive --8530da6b-1778-487a-a058-e78640a096e0 Content-Type: application/http; msgtype=request GET /api/customers/1 HTTP/1.1 Host: piotr-pc:8289 --8530da6b-1778-487a-a058-e78640a096e0 Content-Type: application/http; msgtype=request GET /api/customers/2 HTTP/1.1 Host: piotr-pc:8289 --8530da6b-1778-487a-a058-e78640a096e0--

Widzimy, że mamy jeden nagłówek, a potem w HTTP body przekazujemy konkretne zapytania, w tym przypadku /api/customers/1 oraz /api/customers/2.

Odpowiedz z kolei również przyjdzie w jednej paczce i wygląda następująco:

HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Length: 366 Content-Type: multipart/mixed; boundary="996affb8-9317-4fbd-8899-8cce1223b023" Expires: -1 Server: Microsoft-IIS/8.0 X-AspNet-Version: 4.0.30319 X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUGlvdHJcRG93bmxvYWRzXFNwb3RsaWdodERldmVsb3BlclRlc3RcTGltZXJpY2tcV2ViQXBwbGljYXRpb24xXFdlYkFwcGxpY2F0aW9uMVxhcGlcJGJhdGNo?= X-Powered-By: ASP.NET Date: Sun, 12 Apr 2015 19:24:25 GMT --996affb8-9317-4fbd-8899-8cce1223b023 Content-Type: application/http; msgtype=response HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 24 --996affb8-9317-4fbd-8899-8cce1223b023 Content-Type: application/http; msgtype=response HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 77 --996affb8-9317-4fbd-8899-8cce1223b023--

W tym przypadku są to liczby 24 i 77.

ASP.NET MVC wspiera powyższy mechanizm i wystarczy w pliku konfiguracyjnym określić routing:

config.Routes.MapHttpBatchRoute( routeName: "WebApiBatch", routeTemplate: "api/$batch", batchHandler: new DefaultHttpBatchHandler(GlobalConfiguration.DefaultServer));

I to wszystko! Nie musimy męczyć się z żadną własną implementacją. W przypadku klienta, może to wyglądać następująco:

const string baseAddress = "http://piotr-pc:8289"; HttpClient client = new HttpClient(); var batchRequest = new HttpRequestMessage(HttpMethod.Post, baseAddress + "/api/$batch") { Content = new MultipartContent() { new HttpMessageContent(new HttpRequestMessage(HttpMethod.Get, baseAddress + "/api/customers/1")), new HttpMessageContent(new HttpRequestMessage(HttpMethod.Get, baseAddress + "/api/customers/2")) }}; HttpResponseMessage batchResponse = client.SendAsync(batchRequest).Result; MultipartStreamProvider streamProvider = batchResponse.Content.ReadAsMultipartAsync().Result; foreach (var content in streamProvider.Contents) { HttpResponseMessage response = content.ReadAsHttpResponseMessageAsync().Result; Console.WriteLine(response.Content.ReadAsStringAsync().Result); }

Domyślnie każde zapytanie z batch’a jest wykonywane sekwencyjne. W przypadku powyższego scenariusza nie ma to sensu. Nic nie stoi na przeszkodzie, aby jednocześnie, z dwóch różnych wątków pobierać dane. Wystarczy zmienić konfigurację na:

aultHttpBatchHandler(GlobalConfiguration.DefaultServer) { ExecutionOrder = BatchExecutionOrder.NonSequential }; config.Routes.MapHttpBatchRoute( routeName: "WebApiBatch", routeTemplate: "api/$batch", batchHandler: batchHandler);

Jeśli powyższy batching z jakich względów nie odpowiada nam, możemy zawsze napisać własny, na przykład dziedzicząc po  DefaultHttpBatchHandler.

One thought on “REST Batching: Ograniczanie liczby zapytań”

Leave a Reply

Your email address will not be published.