feat: Change RAbbitMQ Conenction to Pool and Add k6 Test Script for max request per second.
This commit is contained in:
parent
0bac3630ec
commit
f5355728ab
7 changed files with 186 additions and 44 deletions
|
|
@ -37,6 +37,7 @@
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.8" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="6.31.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="6.31.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
|
||||||
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="6.0.5.128" />
|
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="6.0.5.128" />
|
||||||
|
|
@ -49,6 +50,7 @@
|
||||||
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
|
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -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.Commands;
|
||||||
using BMA.EHR.Application.Repositories.Leaves.LeaveRequests;
|
using BMA.EHR.Application.Repositories.Leaves.LeaveRequests;
|
||||||
using BMA.EHR.Application.Repositories.Leaves.TimeAttendants;
|
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.DutyTime;
|
||||||
using BMA.EHR.Leave.Service.DTOs.LeaveRequest;
|
using BMA.EHR.Leave.Service.DTOs.LeaveRequest;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Org.BouncyCastle.Ocsp;
|
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using Serilog;
|
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
@ -58,6 +55,10 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
|
|
||||||
private readonly string _bucketName = "check-in";
|
private readonly string _bucketName = "check-in";
|
||||||
|
|
||||||
|
private readonly ObjectPool<IModel> _objectPool;
|
||||||
|
private readonly string _fakeCheckInQueue = "fake-bma-checkin-queue";
|
||||||
|
private readonly string _realCheckInQueue = "bma-checkin-queue";
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region " Constuctor and Destructor "
|
#region " Constuctor and Destructor "
|
||||||
|
|
@ -75,7 +76,8 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
AdditionalCheckRequestRepository additionalCheckRequestRepository,
|
AdditionalCheckRequestRepository additionalCheckRequestRepository,
|
||||||
UserCalendarRepository userCalendarRepository,
|
UserCalendarRepository userCalendarRepository,
|
||||||
CommandRepository commandRepository,
|
CommandRepository commandRepository,
|
||||||
LeaveRequestRepository leaveRequestRepository)
|
LeaveRequestRepository leaveRequestRepository,
|
||||||
|
ObjectPool<IModel> objectPool)
|
||||||
{
|
{
|
||||||
_dutyTimeRepository = dutyTimeRepository;
|
_dutyTimeRepository = dutyTimeRepository;
|
||||||
_context = context;
|
_context = context;
|
||||||
|
|
@ -92,6 +94,7 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
_commandRepository = commandRepository;
|
_commandRepository = commandRepository;
|
||||||
_leaveRequestRepository = leaveRequestRepository;
|
_leaveRequestRepository = leaveRequestRepository;
|
||||||
|
|
||||||
|
_objectPool = objectPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -428,6 +431,7 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
public async Task<ActionResult<ResponseObject>> CheckInAsync([FromForm] CheckTimeDto data)
|
public async Task<ActionResult<ResponseObject>> CheckInAsync([FromForm] CheckTimeDto data)
|
||||||
{
|
{
|
||||||
|
// prepare data and convert request body and send to queue
|
||||||
var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId);
|
var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId);
|
||||||
var currentDate = DateTime.Now;
|
var currentDate = DateTime.Now;
|
||||||
var checkFileBytes = new byte[0];
|
var checkFileBytes = new byte[0];
|
||||||
|
|
@ -458,30 +462,49 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
Token = AccessToken ?? ""
|
Token = AccessToken ?? ""
|
||||||
};
|
};
|
||||||
|
|
||||||
// create connection
|
var channel = _objectPool.Get();
|
||||||
var factory = new ConnectionFactory()
|
try
|
||||||
{
|
{
|
||||||
HostName = _configuration["Rabbit:Host"],
|
channel.QueueDeclare(queue: _realCheckInQueue, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||||
UserName = _configuration["Rabbit:User"],
|
|
||||||
Password = _configuration["Rabbit:Password"],
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// แปลง Object เป็น JSON สตริง
|
|
||||||
var serializedObject = JsonConvert.SerializeObject(checkData);
|
var serializedObject = JsonConvert.SerializeObject(checkData);
|
||||||
|
|
||||||
// แปลง JSON สตริงเป็น byte array
|
|
||||||
var body = Encoding.UTF8.GetBytes(serializedObject);
|
var body = Encoding.UTF8.GetBytes(serializedObject);
|
||||||
|
|
||||||
channel.BasicPublish(exchange: "", routingKey: "checkin-queue", basicProperties: null, body: body);
|
channel.BasicPublish(exchange: "",
|
||||||
Console.WriteLine($"Send to Queue: {serializedObject}");
|
routingKey: _realCheckInQueue,
|
||||||
|
basicProperties: null,
|
||||||
|
body: body);
|
||||||
|
|
||||||
return Success(new { date = currentDate });
|
return Success(new { date = currentDate });
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_objectPool.Return(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
//// create connection
|
||||||
|
//var factory = new ConnectionFactory()
|
||||||
|
//{
|
||||||
|
// HostName = _configuration["Rabbit:Host"],
|
||||||
|
// UserName = _configuration["Rabbit:User"],
|
||||||
|
// Password = _configuration["Rabbit:Password"],
|
||||||
|
//};
|
||||||
|
|
||||||
|
//// 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);
|
||||||
|
|
||||||
|
//// แปลง 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 });
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fake Check in
|
/// Fake Check in
|
||||||
|
|
@ -491,7 +514,7 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
/// <response code="200">เมื่อทำรายการสำเร็จ</response>
|
/// <response code="200">เมื่อทำรายการสำเร็จ</response>
|
||||||
/// <response code="401">ไม่ได้ Login เข้าระบบ</response>
|
/// <response code="401">ไม่ได้ Login เข้าระบบ</response>
|
||||||
/// <response code="500">เมื่อเกิดข้อผิดพลาดในการทำงาน</response>
|
/// <response code="500">เมื่อเกิดข้อผิดพลาดในการทำงาน</response>
|
||||||
[HttpPost("check-in"), DisableRequestSizeLimit]
|
[HttpPost("fake-check-in"), DisableRequestSizeLimit]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
|
@ -499,31 +522,27 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
public ActionResult<ResponseObject> FakeCheckIn([FromBody] FakeCheckTimeDto data)
|
public ActionResult<ResponseObject> FakeCheckIn([FromBody] FakeCheckTimeDto data)
|
||||||
{
|
{
|
||||||
var currentDate = DateTime.Now;
|
var currentDate = DateTime.Now;
|
||||||
|
var channel = _objectPool.Get();
|
||||||
// create connection
|
try
|
||||||
var factory = new ConnectionFactory()
|
|
||||||
{
|
{
|
||||||
HostName = _configuration["Rabbit:Host"],
|
channel.QueueDeclare(queue: _fakeCheckInQueue, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||||
UserName = _configuration["Rabbit:User"],
|
|
||||||
Password = _configuration["Rabbit:Password"],
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// แปลง Object เป็น JSON สตริง
|
|
||||||
var serializedObject = JsonConvert.SerializeObject(data);
|
var serializedObject = JsonConvert.SerializeObject(data);
|
||||||
|
|
||||||
// แปลง JSON สตริงเป็น byte array
|
|
||||||
var body = Encoding.UTF8.GetBytes(serializedObject);
|
var body = Encoding.UTF8.GetBytes(serializedObject);
|
||||||
|
|
||||||
channel.BasicPublish(exchange: "", routingKey: "fake-checkin-queue", basicProperties: null, body: body);
|
channel.BasicPublish(exchange: "",
|
||||||
Console.WriteLine($"Send to Queue: {serializedObject}");
|
routingKey: _fakeCheckInQueue,
|
||||||
|
basicProperties: null,
|
||||||
|
body: body);
|
||||||
|
|
||||||
return Success(new { date = currentDate });
|
return Success(new { date = currentDate });
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_objectPool.Return(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check in Processing
|
/// Check in Processing
|
||||||
|
|
|
||||||
|
|
@ -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<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||||
|
services.AddSingleton<ObjectPool<IModel>>(sp =>
|
||||||
|
{
|
||||||
|
var provider = sp.GetRequiredService<ObjectPoolProvider>();
|
||||||
|
return provider.Create(new RabbitMqConnectionPoolPolicy(configuration));
|
||||||
|
});
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ using System.Transactions;
|
||||||
using BMA.EHR.Leave.Service.Filters;
|
using BMA.EHR.Leave.Service.Filters;
|
||||||
using Hangfire.Common;
|
using Hangfire.Common;
|
||||||
using BMA.EHR.Application.Repositories.Leaves.TimeAttendants;
|
using BMA.EHR.Application.Repositories.Leaves.TimeAttendants;
|
||||||
|
using BMA.EHR.Leave.Service.Extensions;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
{
|
{
|
||||||
|
|
@ -100,6 +101,8 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
builder.Services.AddRabbitMqConnectionPooling(builder.Configuration);
|
||||||
|
|
||||||
// Add Hangfire services.
|
// Add Hangfire services.
|
||||||
var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection");
|
var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||||
|
|
||||||
|
|
|
||||||
53
BMA.EHR.Leave/Services/RabbitMqConnectionPoolPolicy.cs
Normal file
53
BMA.EHR.Leave/Services/RabbitMqConnectionPoolPolicy.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
|
||||||
|
namespace BMA.EHR.Leave.Service.Services;
|
||||||
|
|
||||||
|
public class RabbitMqConnectionPoolPolicy : IPooledObjectPolicy<IModel>
|
||||||
|
{
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
@ -31,7 +31,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BMA.EHR.Insignia", "BMA.EHR
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BMA.EHR.Leave", "BMA.EHR.Leave\BMA.EHR.Leave.csproj", "{B06C5612-2346-44B1-9D50-F8373885BD88}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BMA.EHR.Leave", "BMA.EHR.Leave\BMA.EHR.Leave.csproj", "{B06C5612-2346-44B1-9D50-F8373885BD88}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
|
||||||
40
checkin_load_test.js
Normal file
40
checkin_load_test.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue