Ошибка соединения между мастер-узлом и рабочими узлами в распределенной системе
Я делаю распределенную систему на ASP.NET и docker с вычислением простых чисел по заданному диапазону. У меня есть центральный узел master и 3 рабочих узла worker, которым я отдаю часть диапазона для вычислений. Я не могу к ним подключиться. У меня ошибка соединения. В чем проблема? Логи из браузера:
Sending request:
Object { start: "1", end: "500" }
end: "500"
start: "1"
<prototype>: Object { … }
index.html:27:21
Data:
Object { error: "Internal Server Error", logs: (6) […] }
error: "Internal Server Error"
logs: Array(6) [ "Start processing request on master", "Received request: start=1, end=500", "Assigning worker 1: 1 - 167", … ]
0: "Start processing request on master"
1: "Received request: start=1, end=500"
2: "Assigning worker 1: 1 - 167"
3: "Assigning worker 2: 167 - 333"
4: "Assigning worker 3: 333 - 500"
5: "Error: System.Net.Http.HttpRequestException: Connection refused (worker1:8081)\n ---> System.Net.Sockets.SocketException (111): Connection refused\n at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)\n at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)\n at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)\n at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)\n --- End of inner exception stack trace ---\n at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)\n at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)\n at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)\n at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)\n at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)\n at Program.<<Main>$>g__SendToWorker|0_3(Int32 workerIndex, BigInteger start, BigInteger end) in /src/Program.cs:line 122\n at Program.<>c.<<<Main>$>b__0_1>d.MoveNext() in /src/Program.cs:line 51"
length: 6
<prototype>: Array []
<prototype>: Object { … }
index.html:37:25
Uncaught (in promise) TypeError: data.Primes is undefined
<anonymous> http://localhost:8080/index.html:38
index.html:38:32
index.html(в wwwroot):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Prime Number Finder</title>
</head>
<body>
<h1>Prime Number Finder</h1>
<form id="primeForm">
<label for="start">Start:</label>
<input type="number" id="start" name="start" required>
<label for="end">End:</label>
<input type="number" id="end" name="end" required>
<button type="submit">Find Primes</button>
</form>
<div id="result"></div>
<script>
document.getElementById('primeForm').addEventListener('submit', async function (event) {
event.preventDefault();
const start = parseInt(document.getElementById('start').value, 10); // Преобразуем в число
const end = parseInt(document.getElementById('end').value, 10); // Преобразуем в число
console.log("Sending request:", { start, end }); // Лог запроса
const response = await fetch("/api/master/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ start: start, end: end }) // Передаем числа, а не строки
});
if (response.ok) {
const data = await response.json();
console.log("Data:", data);
const primes = data.Primes.join(', ');
document.getElementById('result').innerText = 'Primes: ' + primes;
} else {
document.getElementById('result').innerText = 'Error retrieving primes.';
}
});
</script>
</body>
</html>
Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["DistributedSystems_Lab1.csproj", ""]
RUN dotnet restore "./DistributedSystems_Lab1.csproj"
COPY . .
RUN dotnet publish "DistributedSystems_Lab1.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "DistributedSystems_Lab1.dll"]
Docker-compose.yml:
services:
master:
container_name: master
image: my_app:latest
environment:
- ROLE=master
ports:
- "8080:8080"
networks:
- app_net
deploy:
replicas: 1
restart_policy:
condition: on-failure
depends_on:
- worker1
- worker2
- worker3
worker1:
container_name: worker1
image: my_app:latest
environment:
- ROLE=worker
networks:
- app_net
ports:
- "8081:8080"
deploy:
restart_policy:
condition: on-failure
worker2:
container_name: worker2
image: my_app:latest
environment:
- ROLE=worker
networks:
- app_net
ports:
- "8082:8080"
deploy:
restart_policy:
condition: on-failure
worker3:
container_name: worker3
image: my_app:latest
environment:
- ROLE=worker
networks:
- app_net
ports:
- "8083:8080"
deploy:
restart_policy:
condition: on-failure
networks:
app_net:
driver: bridge
PrimalityTest.cs:
using System.Numerics;
namespace DistributedSystems_Lab1
{
public static class PrimalityTest
{
// Проверка числа на простоту
public static bool IsPrime(BigInteger number)
{
if (number < 2) return false;
// Вычисление целочисленного квадратного корня числа
BigInteger sqrt = Sqrt(number);
for (BigInteger i = 2; i <= sqrt; i++)
{
if (number % i == 0) return false;
}
return true;
}
// Функция для вычисления целочисленного квадратного корня BigInteger
public static BigInteger Sqrt(BigInteger value)
{
if (value < 0) throw new ArgumentException("value must be non-negative");
BigInteger x = value;
BigInteger y = (x + 1) / 2;
// Итеративный метод Ньютона для приближения
while (y < x)
{
x = y;
y = (x + value / x) / 2;
}
return x;
}
}
}
PrimeCheckRequest.cs:
namespace DistributedSystems_Lab1
{
public class PrimeCheckRequest
{
public BigInteger Start { get; set; }
public BigInteger End { get; set; }
}
}
PrimeCheckResponse.cs:
namespace DistributedSystems_Lab1
{
public class PrimeCheckResponse
{
public List<string> Primes { get; set; } = new();
}
}
PrimeController.cs:
namespace DistributedSystems_Lab1
{
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Numerics;
[ApiController]
[Route("api/[controller]")]
public class PrimeController : ControllerBase
{
[HttpPost("check")]
public ActionResult<PrimeCheckResponse> CheckPrimes([FromBody] PrimeCheckRequest request)
{
BigInteger start = BigInteger.Parse(request.Start);
BigInteger end = BigInteger.Parse(request.End);
List<string> primes = new();
for (BigInteger i = start; i <= end; i++)
{
if (PrimalityTest.IsPrime(i))
{
primes.Add(i.ToString());
}
}
return new PrimeCheckResponse { Primes = primes };
}
}
}
Program.cs:
using DistributedSystems_Lab1;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using System.Net.Http.Json;
using System.Numerics;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string role = Environment.GetEnvironmentVariable("ROLE") ?? "worker";
if (role == "master")
{
app.UseStaticFiles(); // Разрешает отдавать статические файлы
app.MapGet("/", () => Results.Redirect("/index.html")); // Перенаправляем на страницу с формой
// Эндпоинт для старта вычислений простых чисел
app.MapPost("/api/master/start", async (HttpContext context) =>
{
List<string> logs = new();
logs.Add("Start processing request on master");
try
{
var request = await context.Request.ReadFromJsonAsync<PrimeCheckRequest>();
if (request == null)
{
logs.Add("Invalid request received");
return Results.Json(new { Error = "Invalid request", Logs = logs });
}
BigInteger start = request.Start; // Теперь это уже BigInteger
BigInteger end = request.End;
int workerCount = 3;
BigInteger step = (end - start) / workerCount;
List<Task<(List<string> primes, List<string> workerLogs)>> tasks = new();
logs.Add($"Received request: start={start}, end={end}");
for (int i = 0; i < workerCount; i++)
{
BigInteger subStart = start + i * step;
BigInteger subEnd = (i == workerCount - 1) ? end : subStart + step;
logs.Add($"Assigning worker {i + 1}: {subStart} - {subEnd}");
tasks.Add(SendToWorker(i, subStart, subEnd));
}
var results = await Task.WhenAll(tasks);
var primes = results.SelectMany(x => x.primes).ToList();
logs.AddRange(results.SelectMany(x => x.workerLogs));
logs.Add("End processing request on master");
return Results.Json(new { Primes = primes, Logs = logs });
}
catch (Exception ex)
{
logs.Add($"Error: {ex}");
return Results.Json(new { Error = "Internal Server Error", Logs = logs });
}
});
}
else
{
// Эндпоинт для обработки вычислений простых чисел на воркере
app.MapPost("/api/worker/check", async (HttpContext context) =>
{
List<string> logs = new();
logs.Add("Worker processing started");
var request = await context.Request.ReadFromJsonAsync<PrimeCheckRequest>();
if (request == null)
{
logs.Add("Invalid request received");
logs.Add("Worker processing finished with error");
return Results.Json(new { Error = "Invalid request", Logs = logs });
}
BigInteger start = BigInteger.Parse(request.Start.ToString());
BigInteger end = BigInteger.Parse(request.End.ToString());
List<string> primes = new();
logs.Add($"Worker received: start={start}, end={end}");
for (BigInteger i = start; i <= end; i++)
{
if (PrimalityTest.IsPrime(i))
{
primes.Add(i.ToString());
}
}
logs.Add($"Worker found primes: {string.Join(", ", primes)}");
logs.Add("Worker processing finished");
return Results.Json(new { Primes = primes, Logs = logs });
});
}
app.Run();
// Отправка задания на воркер
static async Task<(List<string> primes, List<string> workerLogs)> SendToWorker(int workerIndex, BigInteger start, BigInteger end)
{
using var client = new HttpClient();
var workerUrls = new[]
{
"http://worker1:8081",
"http://worker2:8082",
"http://worker3:8083"
};
List<string> logs = new();
string workerUrl = workerUrls[workerIndex % workerUrls.Length];
logs.Add($"Start sending request to {workerUrl}: start={start}, end={end}");
var response = await client.PostAsJsonAsync($"{workerUrl}/api/worker/check", new PrimeCheckRequest
{
Start = start,
End = end
});
if (!response.IsSuccessStatusCode)
{
logs.Add($"Worker request failed: {response.StatusCode}");
logs.Add($"End sending request to {workerUrl}");
return (new List<string>(), logs);
}
var result = await response.Content.ReadFromJsonAsync<PrimeCheckResponse>();
logs.Add($"Worker {workerUrl} returned: {string.Join(", ", result?.Primes ?? new List<string>())}");
logs.Add($"End sending request to {workerUrl}");
return (result?.Primes ?? new List<string>(), logs);
}
Ответы (1 шт):
Воркер должен быть такой
workerN:
container_name: workerN
image: my_app:latest
environment:
- ROLE=worker
networks:
- app_net
ports:
- "5000"
deploy:
restart_policy:
condition: on-failure
Мастер должен быть такой
master:
container_name: master
image: my_app:latest
environment:
- ROLE=master
ports:
- "8080:5000"
networks:
- app_net
deploy:
replicas: 1
restart_policy:
condition: on-failure
depends_on:
- worker1
- worker2
- worker3
Подключаться к воркеру с мастера: http://workerN:5000
Подключаться к мастеру с браузера: http://localhost:8080
5000 - порт по умолчанию для Kestrel в ASP.NET Core. Как его настроить, написано в документации.
Например так
workerN:
container_name: workerN
image: my_app:latest
environment:
- ROLE=worker
- ASPNETCORE_HTTP_PORTS=1234
networks:
- app_net
ports:
- "1234"
deploy:
restart_policy:
condition: on-failure
Либо можно создать файл с именем .env там же где лежит docker-compose.yml и прописать туда порты
WORKER_PORT=1234
И использовать вот так
workerN:
container_name: workerN
image: my_app:latest
environment:
- ROLE=worker
- ASPNETCORE_HTTP_PORTS=$WORKER_PORT
networks:
- app_net
ports:
- $WORKER_PORT
deploy:
restart_policy:
condition: on-failure
Как настраивать порты в docker-compose, также подробно написано в документации.
Как правильно писать Dockerfile, тоже можете почитать здесь.