在ASP.NET Core微服务架构下使用数据库切分和扩展, 并用JMeter进行负载测试
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在ASP.NET Core微服务架构下使用数据库切分和扩展, 并用JMeter进行负载测试相关的知识,希望对你有一定的参考价值。
原文链接:https://itnext.io/how-to-scale-an-asp-net-core-microservice-and-sharded-database-load-test-with-jmeter-1a8c7292e7e3
现在,您将扩展应用程序并运行多个微服务和数据库的容器实例。您将使用Docker Compose和HAProxy负载均衡器:
然后运行JMeter负载测试,以查看应用程序在使用不同数量的实例时如何伸缩。最后,您还将发布和接收来自RabbitMQ的消息。
1.运行多个数据库和微服务实例
将微服务容器化
使用上一篇文章中的代码和环境作为基础。
将Visual Studio解决方案资源管理器中的文件“Dockerfile”重命名为“dockerfile”(第一个字符小写)。然后右键单击Docker文件并选择“创建Docker镜像”。这也会将镜像推送到docker。
在Docker中运行应用程序
创建docker-compose.yml文件:
version: '2'
services:
webservice:
image: 'postservice:latest'
cpus: 0.5
scale: 4
environment:
- PostDbConnectionStrings__Shard0=server=database0; port=3306; database=post; user=root; password=pw; Persist Security Info=False; Connect Timeout=300
- PostDbConnectionStrings__Shard1=server=database1; port=3306; database=post; user=root; password=pw; Persist Security Info=False; Connect Timeout=300
# - PostDbConnectionStrings__Shard2=server=database2; port=3306; database=post; user=root; password=pw; Persist Security Info=False; Connect Timeout=300
loadbalancer:
image: dockercloud/haproxy
links:
- webservice
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 5001:80
database0:
image: 'mysql:5.6'
cpus: 0.5
ports:
- 3312:3306
environment:
- MYSQL_ROOT_PASSWORD=pw
database1:
image: 'mysql:5.6'
cpus: 0.5
ports:
- 3313:3306
environment:
- MYSQL_ROOT_PASSWORD=pw
database2:
image: 'mysql:5.6'
cpus: 0.5
ports:
- 3314:3306
environment:
- MYSQL_ROOT_PASSWORD=pw
docker文件配置3个数据库容器和4个Post服务实例。目前,Post服务实例只使用了2个数据库。稍后可以删除注释以使用第三个数据库。HAProxy负载均衡器公开端口5001上的Post服务容器。
每个容器有0.5cpu的限制,以帮助在本地机器上进行实际的负载测试。在我的12核笔记本上,仍然有未使用的资源,因此添加更多的服务和数据库实例可以带来好处。
启动应用程序
C:\\dev>docker-compose up -d
初始化数据库
打开浏览器访问http://localhost:5001/swagger/index.html
初始化至少有100个用户和10个类别的数据库。
您可以创建更多的用户和类别,但由于CPU的限制,这需要一些时间。
2.缩放应用程序并使用JMeter进行负载测试
创建JMeter测试计划
安装并打开JMeter。
创建测试计划和线程组:
32个线程是一个很好的开始。在线程的每个循环上,它添加一个帖子并读取10个帖子。
添加HTTP请求以创建帖子:
Servername: localhost
Port: 5001
HTTP-Request: POST
Path: /api/Posts
Body Data:
{
"title": "MyTitle",
"content": "MyContent",
"userId": ${__Random(1,100)},
"categoryId": "Category${__Random(1,10)}"
}
它为随机用户(ID 1–100)和类别(1–10)创建一个帖子。
向请求添加内容类型application/json HTTP头:
随机阅读10篇文章:
Servername: localhost
Port: 5001
HTTP-Request: GET
Path: /api/Posts
Send Parameters with the Request:
NAME | VALUE | CONTENT-TYPE
category | Category${__Random(1,10)} | text/plain
count | 10 | text/plain
运行测试
在测试运行时查看摘要报告:
等待一段时间,直到平均值(响应时间)和吞吐量稳定。
修改测试参数
停止JMeter中的测试。
您可以更改测试计划中的线程。将它们提升到64或128个线程。或者将线程数减少到16甚至1。
在编辑docker-compose.yml之前关闭应用程序:
C:\\dev>docker-compose down
您可以通过“scale”属性更改Post服务实例的数量。更改数据库数的“environment”属性(添加/删除注释):
更改后启动应用程序:
C:\\dev>docker-compose up -d
数据库服务器运行需要一段时间。并记住初始化数据库:
3.试验结果示例
在我的电脑上,两个Post服务与一个数据库的比率会产生很好的效果。我可以将它扩展到六个服务和三个数据库,直到我的硬件达到极限。平均时间保持在500ms以下。增加高于64的线程会产生错误。
结果取决于我的环境和CPU限制。它们在你的机器上是不同的。
每秒吞吐量与实例数成比例:
在CPU受限的容器中,每秒305个请求,每天大约有2500万个请求。这将允许100万用户每天写10篇帖子和阅读帖子。当然,现实生活中的应用程序会更复杂,但我希望示例能够显示基本的思想。
4.微服务间通信和复制用户更改
Post服务通过Rabbitmq消息从User微服务接收对用户的更改:
你也可以用JMeter来模拟。
创建RabbitMQ容器
发出以下命令(在控制台窗口中)以启动具有管理UI的RabbitMQ容器
C:\\dev>docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=test -e RABBITMQ_DEFAULT_PASS=test --hostname my-rabbit --name some-rabbit rabbitmq:3-management
该命令将“test”配置为用户和密码,而不是默认的Guest账户。Guest仅限于localhost,但在docker中,容器位于不同的主机上。
修改后微服务
在Visual Studio中安装RabbitMQ.Client NuGet包。
添加IntegrationEventListenerService类:
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Linq;
using PostService.Data;
using PostService.Entities;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace PostService
{
public class IntegrationEventListenerService : BackgroundService
{
private async Task ListenForIntegrationEvents(CancellationToken stoppingToken)
{
try
{
ConnectionFactory factory = new ConnectionFactory
{
UserName = "test",
Password = "test"
};
var endpoints = new System.Collections.Generic.List<AmqpTcpEndpoint>
{
new AmqpTcpEndpoint("host.docker.internal"),
new AmqpTcpEndpoint("localhost")
};
var connection = factory.CreateConnection(endpoints);
var channel = connection.CreateModel();
var consumer = new EventingBasicConsumer(channel);
var arguments = new Dictionary<String, object>
{
{ "x-single-active-consumer", true }
};
channel.QueueDeclare("user.postservicesingleactiveconsumer", false, false, false, arguments);
channel.ExchangeDeclare("userloadtest", "fanout");
channel.QueueBind("user.postservicesingleactiveconsumer", "userloadtest", "");
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("IntegrationEvent {0}", message);
var data = JObject.Parse(message);
var type = ea.RoutingKey;
var user = new User()
{
ID = data["id"].Value<int>(),
Name = data["name"].Value<string>(),
Version = data["version"].Value<int>()
};
if (type == "user.add")
{
_dataAccess.AddUser(user);
}
else if (type == "user.update")
{
_dataAccess.UpdateUser(user);
}
channel.BasicAck(ea.DeliveryTag, false);
};
channel.BasicConsume(queue: "user.postservicesingleactiveconsumer",
autoAck: false,
consumer: consumer);
try
{
await Task.Delay(Timeout.Infinite, stoppingToken);
}
catch (OperationCanceledException)
{
Console.WriteLine("Shutting down.");
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private readonly DataAccess _dataAccess;
public IntegrationEventListenerService(DataAccess dataAccess)
{
_dataAccess = dataAccess;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await ListenForIntegrationEvents(stoppingToken);
}
}
}
}
后台服务使用“test”帐户访问RabbitMQ和host.docker.internal以及localhost作为主机。这允许从容器和Visual Studio调试器内部进行连接。
单个活动的消费者保证只有一个Post-service实例接收消息。
如果活动的实例崩溃,那么下一个实例将接管。稍后可以通过停止docker中当前接收的实例来尝试。
如果exchange和管道尚不存在,则代码将创建它们。它使用手动确认。
修改Startup.cs以运行IntegrationEventListenerService:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "PostService", Version = "v1" });
});
services.AddSingleton<DataAccess>();
services.AddSingleton<IntegrationEventListenerService>();
services.AddHostedService<IntegrationEventListenerService>(provider => provider.GetService<IntegrationEventListenerService>());
}
在Docker中运行已更改的微服务
关闭docker中的应用程序:
C:\\dev>docker-compose down
编译Post服务,将其容器化并发布到docker。
更改后启动应用程序:
C:\\dev>docker-compose up -d
数据库服务器运行需要一段时间。并记住初始化数据库:
修改JMeter测试
在JMeter测试计划中,添加一个只有一个线程的线程组:
添加计时器:
恒定计时器将测试限制为每秒一条消息。每秒一条消息非常常见。即使半年内有100万用户注册,每分钟也只有4个新用户。
添加HTTP请求以将消息发布到RabbitMQ:
Servername: localhost
Port: 15672
HTTP-Request: POST
Path: /api/exchanges/%2F/userloadtest/publish
Body Data:
{
"properties":{},
"routing_key":"user.update",
"payload":"
{
id:1,name:\\"NewName${__counter(TRUE,)}\\",version:${__counter(TRUE,)}
}",
"payload_encoding":"string"
}
用户实体有一个version字段来处理无序消息。为了保持测试的简单性,它只更新单个用户并增加version字段的值。性能影响应该保持不变。
将Content-Type和对RabbitMQ的授权添加到HTTP头。
Content-Type | application/json
Authorization | Basic dGVzdDp0ZXN0
*“dGVzdDp0ZXN0”是base64编码的用户和密码“test”。
运行测试
吞吐量应该与前面的测试类似。
您还可以通过查看控制台输出来识别docker中的活动的RabbitMQ消费者。您可以停止容器,则另一个实例将接管消息处理。
5.最后的想法和展望
您创建了一个微服务架构并实现了应用层数据库分片。然后用多个容器实例扩展应用程序并对其进行负载测试。您还用RabbitMQ处理了用户更改事件。
这只是一个示例应用程序。您必须调整代码才能在生产环境中使用它。
下一步是什么?要获取帖子最多的前10个类别的数据,需要查询多个数据库实例。这可能会导致延迟和性能下降。Redis可以是查询聚合数据的解决方案。我将在下一篇文章中展示它。
欢迎关注我的个人公众号”My IO“
以上是关于在ASP.NET Core微服务架构下使用数据库切分和扩展, 并用JMeter进行负载测试的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core Web API下事件驱动型架构的实现:一个简单的实现
ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT)
ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT)
ASP.NET Core 搭载 Envoy 实现微服务的可视化监控