From f5355728abd0a936992a7a68b0ad8f38850d5799 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Aug 2024 10:37:47 +0700 Subject: [PATCH] feat: Change RAbbitMQ Conenction to Pool and Add k6 Test Script for max request per second. --- BMA.EHR.Leave/BMA.EHR.Leave.csproj | 2 + BMA.EHR.Leave/Controllers/LeaveController.cs | 105 +++++++++++------- .../RabbitMqServiceCollectionExtensions.cs | 20 ++++ BMA.EHR.Leave/Program.cs | 3 + .../Services/RabbitMqConnectionPoolPolicy.cs | 53 +++++++++ BMA.EHR.Solution.sln | 7 +- checkin_load_test.js | 40 +++++++ 7 files changed, 186 insertions(+), 44 deletions(-) create mode 100644 BMA.EHR.Leave/Extensions/RabbitMqServiceCollectionExtensions.cs create mode 100644 BMA.EHR.Leave/Services/RabbitMqConnectionPoolPolicy.cs create mode 100644 checkin_load_test.js diff --git a/BMA.EHR.Leave/BMA.EHR.Leave.csproj b/BMA.EHR.Leave/BMA.EHR.Leave.csproj index c462d37a..63814a03 100644 --- a/BMA.EHR.Leave/BMA.EHR.Leave.csproj +++ b/BMA.EHR.Leave/BMA.EHR.Leave.csproj @@ -37,6 +37,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -49,6 +50,7 @@ + diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 5d644061..eb51780d 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1,5 +1,4 @@ -using Amazon.S3.Model; -using BMA.EHR.Application.Repositories; +using BMA.EHR.Application.Repositories; using BMA.EHR.Application.Repositories.Commands; using BMA.EHR.Application.Repositories.Leaves.LeaveRequests; using BMA.EHR.Application.Repositories.Leaves.TimeAttendants; @@ -15,12 +14,10 @@ using BMA.EHR.Leave.Service.DTOs.CheckIn; using BMA.EHR.Leave.Service.DTOs.DutyTime; using BMA.EHR.Leave.Service.DTOs.LeaveRequest; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.ObjectPool; using Newtonsoft.Json; -using Org.BouncyCastle.Ocsp; using RabbitMQ.Client; -using Serilog; using Swashbuckle.AspNetCore.Annotations; using System.ComponentModel.DataAnnotations; using System.Security.Claims; @@ -58,6 +55,10 @@ namespace BMA.EHR.Leave.Service.Controllers private readonly string _bucketName = "check-in"; + private readonly ObjectPool _objectPool; + private readonly string _fakeCheckInQueue = "fake-bma-checkin-queue"; + private readonly string _realCheckInQueue = "bma-checkin-queue"; + #endregion #region " Constuctor and Destructor " @@ -75,7 +76,8 @@ namespace BMA.EHR.Leave.Service.Controllers AdditionalCheckRequestRepository additionalCheckRequestRepository, UserCalendarRepository userCalendarRepository, CommandRepository commandRepository, - LeaveRequestRepository leaveRequestRepository) + LeaveRequestRepository leaveRequestRepository, + ObjectPool objectPool) { _dutyTimeRepository = dutyTimeRepository; _context = context; @@ -92,6 +94,7 @@ namespace BMA.EHR.Leave.Service.Controllers _commandRepository = commandRepository; _leaveRequestRepository = leaveRequestRepository; + _objectPool = objectPool; } #endregion @@ -428,6 +431,7 @@ namespace BMA.EHR.Leave.Service.Controllers [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CheckInAsync([FromForm] CheckTimeDto data) { + // prepare data and convert request body and send to queue var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var currentDate = DateTime.Now; var checkFileBytes = new byte[0]; @@ -458,29 +462,48 @@ namespace BMA.EHR.Leave.Service.Controllers Token = AccessToken ?? "" }; - // create connection - var factory = new ConnectionFactory() + var channel = _objectPool.Get(); + try { - HostName = _configuration["Rabbit:Host"], - UserName = _configuration["Rabbit:User"], - Password = _configuration["Rabbit:Password"], - }; + channel.QueueDeclare(queue: _realCheckInQueue, durable: true, exclusive: false, autoDelete: false, arguments: null); + var serializedObject = JsonConvert.SerializeObject(checkData); + var body = Encoding.UTF8.GetBytes(serializedObject); - // create channel - using var connection = factory.CreateConnection(); - using var channel = connection.CreateModel(); - channel.QueueDeclare(queue: "checkin-queue", durable: false, exclusive: false, autoDelete: false, arguments: null); + channel.BasicPublish(exchange: "", + routingKey: _realCheckInQueue, + basicProperties: null, + body: body); - // แปลง Object เป็น JSON สตริง - var serializedObject = JsonConvert.SerializeObject(checkData); + return Success(new { date = currentDate }); + } + finally + { + _objectPool.Return(channel); + } - // แปลง JSON สตริงเป็น byte array - var body = Encoding.UTF8.GetBytes(serializedObject); + //// create connection + //var factory = new ConnectionFactory() + //{ + // HostName = _configuration["Rabbit:Host"], + // UserName = _configuration["Rabbit:User"], + // Password = _configuration["Rabbit:Password"], + //}; - channel.BasicPublish(exchange: "", routingKey: "checkin-queue", basicProperties: null, body: body); - Console.WriteLine($"Send to Queue: {serializedObject}"); + //// create channel + //using var connection = factory.CreateConnection(); + //using var channel = connection.CreateModel(); + //channel.QueueDeclare(queue: "checkin-queue", durable: false, exclusive: false, autoDelete: false, arguments: null); - return Success(new { date = currentDate }); + //// แปลง Object เป็น JSON สตริง + //var serializedObject = JsonConvert.SerializeObject(checkData); + + //// แปลง JSON สตริงเป็น byte array + //var body = Encoding.UTF8.GetBytes(serializedObject); + + //channel.BasicPublish(exchange: "", routingKey: "checkin-queue", basicProperties: null, body: body); + //Console.WriteLine($"Send to Queue: {serializedObject}"); + + //return Success(new { date = currentDate }); } /// @@ -491,7 +514,7 @@ namespace BMA.EHR.Leave.Service.Controllers /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน - [HttpPost("check-in"), DisableRequestSizeLimit] + [HttpPost("fake-check-in"), DisableRequestSizeLimit] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] @@ -499,30 +522,26 @@ namespace BMA.EHR.Leave.Service.Controllers public ActionResult FakeCheckIn([FromBody] FakeCheckTimeDto data) { var currentDate = DateTime.Now; - - // create connection - var factory = new ConnectionFactory() + var channel = _objectPool.Get(); + try { - HostName = _configuration["Rabbit:Host"], - UserName = _configuration["Rabbit:User"], - Password = _configuration["Rabbit:Password"], - }; + channel.QueueDeclare(queue: _fakeCheckInQueue, durable: true, exclusive: false, autoDelete: false, arguments: null); - // create channel - using var connection = factory.CreateConnection(); - using var channel = connection.CreateModel(); - channel.QueueDeclare(queue: "fake-checkin-queue", durable: false, exclusive: false, autoDelete: false, arguments: null); + var serializedObject = JsonConvert.SerializeObject(data); - // แปลง Object เป็น JSON สตริง - var serializedObject = JsonConvert.SerializeObject(data); + var body = Encoding.UTF8.GetBytes(serializedObject); - // แปลง JSON สตริงเป็น byte array - var body = Encoding.UTF8.GetBytes(serializedObject); + channel.BasicPublish(exchange: "", + routingKey: _fakeCheckInQueue, + basicProperties: null, + body: body); - channel.BasicPublish(exchange: "", routingKey: "fake-checkin-queue", basicProperties: null, body: body); - Console.WriteLine($"Send to Queue: {serializedObject}"); - - return Success(new { date = currentDate }); + return Success(new { date = currentDate }); + } + finally + { + _objectPool.Return(channel); + } } /// diff --git a/BMA.EHR.Leave/Extensions/RabbitMqServiceCollectionExtensions.cs b/BMA.EHR.Leave/Extensions/RabbitMqServiceCollectionExtensions.cs new file mode 100644 index 00000000..1dd9d3e8 --- /dev/null +++ b/BMA.EHR.Leave/Extensions/RabbitMqServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using BMA.EHR.Leave.Service.Services; +using Microsoft.Extensions.ObjectPool; +using RabbitMQ.Client; + +namespace BMA.EHR.Leave.Service.Extensions +{ + public static class RabbitMqServiceCollectionExtensions + { + public static IServiceCollection AddRabbitMqConnectionPooling(this IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + services.AddSingleton>(sp => + { + var provider = sp.GetRequiredService(); + return provider.Create(new RabbitMqConnectionPoolPolicy(configuration)); + }); + return services; + } + } +} diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 715a9c8b..217aedaa 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -21,6 +21,7 @@ using System.Transactions; using BMA.EHR.Leave.Service.Filters; using Hangfire.Common; using BMA.EHR.Application.Repositories.Leaves.TimeAttendants; +using BMA.EHR.Leave.Service.Extensions; var builder = WebApplication.CreateBuilder(args); { @@ -100,6 +101,8 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddHealthChecks(); + builder.Services.AddRabbitMqConnectionPooling(builder.Configuration); + // Add Hangfire services. var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); diff --git a/BMA.EHR.Leave/Services/RabbitMqConnectionPoolPolicy.cs b/BMA.EHR.Leave/Services/RabbitMqConnectionPoolPolicy.cs new file mode 100644 index 00000000..296c596c --- /dev/null +++ b/BMA.EHR.Leave/Services/RabbitMqConnectionPoolPolicy.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.ObjectPool; +using RabbitMQ.Client; + +namespace BMA.EHR.Leave.Service.Services; + +public class RabbitMqConnectionPoolPolicy : IPooledObjectPolicy +{ + #region " Fields " + + private readonly IConnection _connection; + private readonly IConfiguration _configuration; + + #endregion + + #region " Constructor and Destructor " + + public RabbitMqConnectionPoolPolicy(IConfiguration configuration) + { + _configuration = configuration; + + var factory = new ConnectionFactory() + { + HostName = _configuration["Rabbit:Host"], + UserName = _configuration["Rabbit:User"], + Password = _configuration["Rabbit:Password"] + }; + _connection = factory.CreateConnection(); + } + + #endregion + + #region " Methods " + + public IModel Create() + { + return _connection.CreateModel(); + } + + public bool Return(IModel obj) + { + if (obj.IsOpen) + { + return true; + } + else + { + obj.Dispose(); + return false; + } + } + + #endregion +} diff --git a/BMA.EHR.Solution.sln b/BMA.EHR.Solution.sln index a65e792b..44e71937 100644 --- a/BMA.EHR.Solution.sln +++ b/BMA.EHR.Solution.sln @@ -31,7 +31,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BMA.EHR.Insignia", "BMA.EHR EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BMA.EHR.Leave", "BMA.EHR.Leave\BMA.EHR.Leave.csproj", "{B06C5612-2346-44B1-9D50-F8373885BD88}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BMA.EHR.CheckInConsumer", "BMA.EHR.CheckInConsumer\BMA.EHR.CheckInConsumer.csproj", "{B36F7E5A-2745-42B5-8493-D648EF8CC43C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BMA.EHR.CheckInConsumer", "BMA.EHR.CheckInConsumer\BMA.EHR.CheckInConsumer.csproj", "{B36F7E5A-2745-42B5-8493-D648EF8CC43C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k6_scripts", "k6_scripts", "{367D82BE-0AF9-4739-A45D-C88194E7820C}" + ProjectSection(SolutionItems) = preProject + checkin_load_test.js = checkin_load_test.js + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/checkin_load_test.js b/checkin_load_test.js new file mode 100644 index 00000000..be5bbd0f --- /dev/null +++ b/checkin_load_test.js @@ -0,0 +1,40 @@ +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export let options = { + scenarios: { + constant_rate: { + executor: 'constant-arrival-rate', + rate: 1000, // จำกัดที่ 1000 requests ต่อวินาที + timeUnit: '1s', + duration: '5m', // ใช้เวลารันทดสอบ 5 นาที + preAllocatedVUs: 50, + }, + }, +}; + +export default function () { + // ข้อมูล payload ที่จะส่งไปยัง server + let payload = JSON.stringify({ + lat: 1, + lon: 1, + poi: 'FPT', + isLocation: true + }); + + // ตัวเลือก headers + let headers = { + 'Content-Type': 'application/json', + }; + + // ส่ง POST request + let response = http.post('https://localhost:7283/api/v1/leave/fake-check-in', payload, { headers: headers }); + + // ตรวจสอบการตอบสนอง + check(response, { + 'is status 200': (r) => r.status === 200, + }); + + // หน่วงเวลา 1 วินาที + sleep(1); +}