-
Notifications
You must be signed in to change notification settings - Fork 54
Медников Матвей #66
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
base: master
Are you sure you want to change the base?
Медников Матвей #66
Changes from all commits
41f01f8
d885421
304cea4
4a04116
424b2d8
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,34 @@ | ||
| using System.Collections.Concurrent; | ||
| using System.Linq; | ||
|
|
||
| namespace ClusterClient.Clients | ||
| { | ||
| public static class ReplicaStatistics | ||
| { | ||
| private static readonly ConcurrentDictionary<string, ConcurrentBag<long>> _responseTimes = new(); | ||
| private const int MaxStoredTimes = 100; | ||
|
|
||
| public static void RecordResponseTime(string replicaAddress, long elapsedMilliseconds) | ||
| { | ||
| var bag = _responseTimes.GetOrAdd(replicaAddress, _ => new ConcurrentBag<long>()); | ||
| bag.Add(elapsedMilliseconds); | ||
|
|
||
| var newBag = new ConcurrentBag<long>(bag.TakeLast(MaxStoredTimes)); | ||
|
Contributor
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.
|
||
| _responseTimes.TryUpdate(replicaAddress, newBag, bag); | ||
|
Contributor
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.
Для текущей задачи с "приблизительной" статистикой, потерять данные не критично, но в реальных задачах это может стать источником "плавающих багов") |
||
| } | ||
|
|
||
| private static double GetAverageResponseTime(string replicaAddress) | ||
| { | ||
| if (!_responseTimes.TryGetValue(replicaAddress, out var times) || times.IsEmpty) | ||
| { | ||
| return double.MaxValue; | ||
| } | ||
| return times.Average(); | ||
| } | ||
|
|
||
| public static string[] OrderBySpeed(string[] replicaAddresses) | ||
| { | ||
| return replicaAddresses.OrderBy(GetAverageResponseTime).ToArray(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,5 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Diagnostics; | ||
| using System.Threading.Tasks; | ||
| using log4net; | ||
|
|
||
|
|
@@ -13,11 +11,37 @@ public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresse | |
| { | ||
| } | ||
|
|
||
| public override Task<string> ProcessRequestAsync(string query, TimeSpan timeout) | ||
| public override async Task<string> ProcessRequestAsync(string query, TimeSpan timeout) | ||
| { | ||
| throw new NotImplementedException(); | ||
| var orderedReplicas = ReplicaStatistics.OrderBySpeed(ReplicaAddresses); | ||
|
|
||
| var deadline = DateTime.UtcNow + timeout; | ||
| var addressesLeft = orderedReplicas.Length; | ||
|
|
||
| foreach (var replica in orderedReplicas) | ||
| { | ||
| var remaining = deadline - DateTime.UtcNow; | ||
| var replicaTimeout = remaining / addressesLeft; | ||
|
Comment on lines
+23
to
+24
Contributor
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 webRequest = CreateRequest(replica + "?query=" + query); | ||
| Log.InfoFormat($"Processing {webRequest.RequestUri}"); | ||
|
|
||
| var timer = Stopwatch.StartNew(); | ||
| var resultTask = ProcessRequestAsync(webRequest); | ||
|
|
||
| await Task.WhenAny(resultTask, Task.Delay(replicaTimeout)); | ||
| timer.Stop(); | ||
|
|
||
| if (resultTask.IsCompletedSuccessfully) | ||
| { | ||
| ReplicaStatistics.RecordResponseTime(replica, timer.ElapsedMilliseconds); | ||
|
Contributor
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. Здесь и в |
||
| return await resultTask; | ||
| } | ||
| addressesLeft--; | ||
| } | ||
| throw new TimeoutException(); | ||
| } | ||
|
|
||
| protected override ILog Log => LogManager.GetLogger(typeof(RoundRobinClusterClient)); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using log4net; | ||
|
|
||
|
|
@@ -12,12 +12,74 @@ public class SmartClusterClient : ClusterClientBase | |
| public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) | ||
| { | ||
| } | ||
|
|
||
| public override Task<string> ProcessRequestAsync(string query, TimeSpan timeout) | ||
| public override async Task<string> ProcessRequestAsync(string query, TimeSpan timeout) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
| var orderedReplicas = ReplicaStatistics.OrderBySpeed(ReplicaAddresses); | ||
|
|
||
| var deadline = DateTime.UtcNow + timeout; | ||
| var runningTasks = new List<(Task<string> task, string address, Stopwatch timer)>(); | ||
| var addressesLeft = orderedReplicas.Length; | ||
|
|
||
| foreach (var replica in orderedReplicas) | ||
| { | ||
| var remaining = deadline - DateTime.UtcNow; | ||
| var replicaTimeout = remaining / addressesLeft; | ||
|
Comment on lines
+26
to
+27
Contributor
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 webRequest = CreateRequest(replica + "?query=" + query); | ||
| Log.InfoFormat($"Processing {webRequest.RequestUri}"); | ||
|
|
||
| var timer = Stopwatch.StartNew(); | ||
| var task = ProcessRequestAsync(webRequest); | ||
| runningTasks.Add((task, replica, timer)); | ||
|
|
||
| var finished = await WaitOneAsync(runningTasks, replicaTimeout); | ||
| if(finished != null) | ||
| { | ||
| var (resultTask, resultReplica, resultTimer) = finished.Value; | ||
| resultTimer.Stop(); | ||
| ReplicaStatistics.RecordResponseTime(resultReplica, resultTimer.ElapsedMilliseconds); | ||
| return resultTask.Result; | ||
| } | ||
| addressesLeft--; | ||
| } | ||
|
|
||
| while (runningTasks.Count > 0) | ||
| { | ||
| var remaining = deadline - DateTime.UtcNow; | ||
| if (remaining <= TimeSpan.Zero) | ||
| throw new TimeoutException(); | ||
|
|
||
| var finished = await WaitOneAsync(runningTasks, remaining); | ||
| if (finished == null) continue; | ||
| var (resultTask, resultReplica, resultTimer) = finished.Value; | ||
| resultTimer.Stop(); | ||
| ReplicaStatistics.RecordResponseTime(resultReplica, resultTimer.ElapsedMilliseconds); | ||
| return resultTask.Result; | ||
| } | ||
| throw new TimeoutException(); | ||
| } | ||
|
|
||
| protected override ILog Log => LogManager.GetLogger(typeof(SmartClusterClient)); | ||
|
|
||
| private async Task<(Task<string>, string, Stopwatch)?> WaitOneAsync | ||
| (List<(Task<string> task, string address, Stopwatch timer)> runningTasks, TimeSpan timeout) | ||
| { | ||
| if (timeout <= TimeSpan.Zero) | ||
| return null; | ||
|
|
||
| var delayTask = Task.Delay(timeout); | ||
| var tasksList = runningTasks.Select(Task (x) => x.task).Append(delayTask).ToList(); | ||
|
Contributor
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 completed = await Task.WhenAny(tasksList); | ||
|
|
||
| if (completed == delayTask) | ||
| return null; | ||
|
|
||
| var finishedTask = (Task<string>)completed; | ||
| var result = runningTasks.First(x => x.task == finishedTask); | ||
| runningTasks.Remove(result); | ||
|
|
||
| return finishedTask.IsCompletedSuccessfully ? (finishedTask, result.address, result.timer) : null; | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тут можно было сделать чуть проще, перед
await resultпросто проврять наresult.IsFault, получилось бы чуть короче и без "выбрасывания" исключения.Но и так тоже можно)