-
Notifications
You must be signed in to change notification settings - Fork 50
Семыкин Степан Лаб. 2 Группа 6511 #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2e1b493
41c62d1
6ef594b
8b50667
1d32d72
78cfa89
5c44af6
b98ce4c
9b5c802
d65ff1e
d3230a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Ocelot" Version="24.1.0" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\AppHost\AppHost.ServiceDefaults\AppHost.ServiceDefaults.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| using Ocelot.LoadBalancer.Errors; | ||
| using Ocelot.LoadBalancer.Interfaces; | ||
| using Ocelot.Responses; | ||
| using Ocelot.Values; | ||
|
|
||
| namespace ApiGateway.LoadBalancer; | ||
|
|
||
| /// <summary> | ||
| /// Балансировщик с взвешенным случайным выбором реплики сервиса. | ||
| /// </summary> | ||
| /// <param name="services">Все доступные экземпляры сервиса из service discovery.</param> | ||
| /// <param name="weights">Набор весов для эндпоинтов в формате name:"service-name-{i}" value:"weight". | ||
| /// </param> | ||
| public class WeightedRandomBalancer(Func<Task<List<Service>>> services, Dictionary<string, double> weights) : ILoadBalancer | ||
| { | ||
| public string Type => nameof(WeightedRandomBalancer); | ||
|
|
||
| /// <summary> | ||
| /// Выбирает реплику сервиса по алгоритму weighted random. | ||
| /// </summary> | ||
| /// <param name="services">Список доступных сервисов.</param> | ||
| /// <param name="weights">Словарь весов для всех сервисов.</param> | ||
| /// <returns>Выбранная реплика сервиса.</returns> | ||
| private static Service GetServiceByWeight(List<Service> services, Dictionary<string, double> weights) | ||
| { | ||
| var cumulativeWeights = new double[services.Count]; | ||
| var sum = 0.0; | ||
| for (var i = 0; i < services.Count; ++i) | ||
| { | ||
| var service = services[i]; | ||
| var key = $"{service.HostAndPort.DownstreamHost}_{service.HostAndPort.DownstreamPort}"; | ||
| var weight = weights.TryGetValue(key, out var w) ? w : 1.0; | ||
|
|
||
| sum += weight; | ||
| cumulativeWeights[i] = sum; | ||
| } | ||
|
|
||
| if (sum <= 0) | ||
| { | ||
| return services[Random.Shared.Next(services.Count)]; | ||
| } | ||
|
|
||
| var randomValue = Random.Shared.NextDouble() * sum; | ||
|
|
||
| var index = Array.BinarySearch(cumulativeWeights, randomValue); | ||
| if (index < 0) | ||
| { | ||
| index = ~index; | ||
| } | ||
| index = Math.Min(index, services.Count - 1); | ||
|
|
||
| return services[index]; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Выдает эндпоинт сервиса для текущего запроса. | ||
| /// </summary> | ||
| /// <param name="httpContext">Контекст входящего запроса.</param> | ||
| /// <returns>Выбранный адрес сервиса или ошибка, если сервисы недоступны.</returns> | ||
| public async Task<Response<ServiceHostAndPort>> LeaseAsync(HttpContext httpContext) | ||
| { | ||
| var allServices = await services.Invoke(); | ||
| if (allServices == null || allServices.Count == 0) | ||
| { | ||
| return new ErrorResponse<ServiceHostAndPort>( | ||
| new ServicesAreNullError("No services available") | ||
| ); | ||
| } | ||
|
|
||
| var selectedService = GetServiceByWeight(allServices, weights); | ||
|
|
||
| return new OkResponse<ServiceHostAndPort>(selectedService.HostAndPort); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Освобождает ранее выданный эндпоинт. | ||
| /// </summary> | ||
| /// <param name="serviceHostAndPort">Адрес освобождаемого сервиса.</param> | ||
| public void Release(ServiceHostAndPort serviceHostAndPort) { } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| using ApiGateway.LoadBalancer; | ||
| using AppHost.ServiceDefaults; | ||
| using Ocelot.DependencyInjection; | ||
| using Ocelot.Middleware; | ||
| using System.Globalization; | ||
|
|
||
| static (Dictionary<string, string?> RouteOverrides, Dictionary<string, double> weights) | ||
| BuildEnv(IConfiguration configuration) | ||
| { | ||
| var namedWeights = configuration | ||
| .GetSection("LoadBalancerWeights") | ||
| .Get<Dictionary<string, double>>() ?? []; | ||
|
|
||
| var routeOverrides = new Dictionary<string, string?>(); | ||
| var weights = new Dictionary<string, double>(); | ||
|
|
||
| Uri? firstEndpoint = null; | ||
| for (var i = 0; i < 5; ++i) | ||
| { | ||
| var envKey = $"services__generation-service-{i}__http__0"; | ||
| var raw = Environment.GetEnvironmentVariable(envKey); | ||
|
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Можно из конфигурации забирать: var envKey = $"services:generation-service-{i}:http:0";
var raw = configuration.GetValue<string?>(envKey, null); |
||
| if (string.IsNullOrWhiteSpace(raw)) | ||
| continue; | ||
|
|
||
| if (!Uri.TryCreate(raw, UriKind.Absolute, out var uri)) | ||
| throw new InvalidOperationException($"Invalid endpoint in {envKey}: {raw}"); | ||
|
|
||
| firstEndpoint ??= uri; | ||
|
|
||
| routeOverrides[$"Routes:0:DownstreamHostAndPorts:{i}:Host"] = uri.Host; | ||
| routeOverrides[$"Routes:0:DownstreamHostAndPorts:{i}:Port"] = | ||
| uri.Port.ToString(CultureInfo.InvariantCulture); | ||
|
|
||
| var key = $"{uri.Host}_{uri.Port}"; | ||
| var serviceName = $"generation-service-{i}"; | ||
| weights[key] = namedWeights.TryGetValue(serviceName, out var w) ? w : 1.0; | ||
| } | ||
|
|
||
| routeOverrides["Routes:0:DownstreamScheme"] = firstEndpoint!.Scheme; | ||
|
|
||
| return (routeOverrides, weights); | ||
| } | ||
|
|
||
| var builder = WebApplication.CreateBuilder(args); | ||
|
|
||
| builder.AddServiceDefaults(); | ||
| builder.Services.AddServiceDiscovery(); | ||
| builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); | ||
|
|
||
| var (routeOverrides, weights) = BuildEnv(builder.Configuration); | ||
| builder.Configuration.AddInMemoryCollection(routeOverrides); | ||
|
|
||
| builder.Services | ||
| .AddOcelot() | ||
| .AddCustomLoadBalancer<WeightedRandomBalancer>((_, _, discoveryProvider) => new(discoveryProvider.GetAsync, weights)); | ||
|
|
||
| var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>(); | ||
| var allowedMethods = builder.Configuration.GetSection("Cors:AllowedMethods").Get<string[]>(); | ||
| var allowedHeaders = builder.Configuration.GetSection("Cors:AllowedHeaders").Get<string[]>(); | ||
| builder.Services.AddCors(options => | ||
| { | ||
| options.AddDefaultPolicy(policy => | ||
| { | ||
| if (allowedOrigins != null) | ||
| { | ||
| _ = allowedOrigins.Contains("*") | ||
| ? policy.AllowAnyOrigin() | ||
| : policy.WithOrigins(allowedOrigins); | ||
| } | ||
|
|
||
| if (allowedMethods != null) | ||
| { | ||
| _ = allowedMethods.Contains("*") | ||
| ? policy.AllowAnyMethod() | ||
| : policy.WithMethods(allowedMethods); | ||
| } | ||
|
|
||
| if (allowedHeaders != null) | ||
| { | ||
| _ = allowedHeaders.Contains("*") | ||
| ? policy.AllowAnyHeader() | ||
| : policy.WithHeaders(allowedHeaders); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| var app = builder.Build(); | ||
|
|
||
| app.MapDefaultEndpoints(); | ||
| app.UseCors(); | ||
|
|
||
| await app.UseOcelot(); | ||
|
|
||
| app.Run(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "$schema": "http://json.schemastore.org/launchsettings.json", | ||
| "profiles": { | ||
| "http": { | ||
| "commandName": "Project", | ||
| "dotnetRunMessages": true, | ||
| "launchBrowser": false, | ||
| "applicationUrl": "http://localhost:5254", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development" | ||
| } | ||
| }, | ||
| "https": { | ||
| "commandName": "Project", | ||
| "dotnetRunMessages": true, | ||
| "launchBrowser": false, | ||
| "applicationUrl": "https://localhost:7268;http://localhost:5254", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| }, | ||
| "AllowedHosts": "*", | ||
| "ConnectionStrings": { | ||
| "RedisCache": "https://localhost:2843" | ||
| }, | ||
| "Cors": { | ||
| "AllowedOrigins": [ "https://localhost:7282" ], | ||
| "AllowedMethods": [ "GET" ], | ||
| "AllowedHeaders": [ "*" ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "Routes": [ | ||
| { | ||
| "DownstreamPathTemplate": "/patient", | ||
| "DownstreamScheme": "http", | ||
| "DownstreamHostAndPorts": [], | ||
| "UpstreamPathTemplate": "/patient", | ||
| "UpstreamHttpMethod": [ "Get" ], | ||
| "LoadBalancerOptions": { | ||
| "Type": "WeightedRandomBalancer" | ||
| } | ||
| } | ||
| ], | ||
| "LoadBalancerWeights": { | ||
| "generation-service-0": 0.4, | ||
| "generation-service-1": 0.25, | ||
| "generation-service-2": 0.15, | ||
| "generation-service-3": 0.1, | ||
| "generation-service-4": 0.1 | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Проблема такого подхода в том, что если поменять порты в aspire, то все перестанет работать, а так быть не должно, нужно передавать из aspire нужные адреса для подключения через переменные окружения |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <Project Sdk="Aspire.AppHost.Sdk/13.1.2"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <UserSecretsId>96fbf34e-00b4-4158-9be4-e6f641d5c362</UserSecretsId> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Aspire.Hosting.AppHost" Version="9.5.2" /> | ||
| <PackageReference Include="Aspire.Hosting.Redis" Version="9.5.2" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\ApiGateway\ApiGateway.csproj" /> | ||
| <ProjectReference Include="..\..\Client.Wasm\Client.Wasm.csproj" /> | ||
| <ProjectReference Include="..\..\GenerationService\GenerationService.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| var builder = DistributedApplication.CreateBuilder(args); | ||
|
|
||
| var cache = builder.AddRedis("cache") | ||
| .WithRedisInsight(containerName: "cache-insight"); | ||
|
|
||
| var gateway = builder.AddProject<Projects.ApiGateway>("apigateway"); | ||
|
|
||
| for (var i = 0; i < 5; ++i) | ||
| { | ||
| var generationService = builder.AddProject<Projects.GenerationService>($"generation-service-{i}", launchProfileName: null) | ||
| .WithHttpEndpoint(8000 + i) | ||
| .WithReference(cache, "RedisCache") | ||
| .WaitFor(cache) | ||
| .WithHttpHealthCheck("/health"); | ||
| gateway | ||
| .WithReference(generationService) | ||
| .WaitFor(generationService); | ||
| } | ||
|
|
||
| builder.AddProject<Projects.Client_Wasm>("client") | ||
| .WaitFor(gateway); | ||
|
|
||
| builder.Build().Run(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/launchsettings.json", | ||
| "profiles": { | ||
| "https": { | ||
| "commandName": "Project", | ||
| "dotnetRunMessages": true, | ||
| "launchBrowser": true, | ||
| "applicationUrl": "https://localhost:17103;http://localhost:15134", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development", | ||
| "DOTNET_ENVIRONMENT": "Development", | ||
| "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21139", | ||
| "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23225", | ||
| "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22067" | ||
| } | ||
| }, | ||
| "http": { | ||
| "commandName": "Project", | ||
| "dotnetRunMessages": true, | ||
| "launchBrowser": true, | ||
| "applicationUrl": "http://localhost:15134", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development", | ||
| "DOTNET_ENVIRONMENT": "Development", | ||
| "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19075", | ||
| "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18212", | ||
| "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20001" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning", | ||
| "Aspire.Hosting.Dcp": "Warning" | ||
| } | ||
| }, | ||
| "Cache": { | ||
| "CacheTime": 60 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <IsAspireSharedProject>true</IsAspireSharedProject> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
|
|
||
| <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.1.0" /> | ||
| <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.1.0" /> | ||
| <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" /> | ||
| <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" /> | ||
| <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" /> | ||
| <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" /> | ||
| <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.14.0" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
Uh oh!
There was an error while loading. Please reload this page.