|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- using NLog;
- using Shadowsocks.Model;
- using System;
- using System.Collections.Generic;
- using System.Net;
- using System.Text;
-
- namespace Shadowsocks.Controller.Strategy
- {
- class HighAvailabilityStrategy : IStrategy
- {
- private static Logger logger = LogManager.GetCurrentClassLogger();
-
- protected ServerStatus _currentServer;
- protected Dictionary<Server, ServerStatus> _serverStatus;
- ShadowsocksController _controller;
- Random _random;
-
- public class ServerStatus
- {
- // time interval between SYN and SYN+ACK
- public TimeSpan latency;
- public DateTime lastTimeDetectLatency;
-
- // last time anything received
- public DateTime lastRead;
-
- // last time anything sent
- public DateTime lastWrite;
-
- // connection refused or closed before anything received
- public DateTime lastFailure;
-
- public Server server;
-
- public double score;
- }
-
- public HighAvailabilityStrategy(ShadowsocksController controller)
- {
- _controller = controller;
- _random = new Random();
- _serverStatus = new Dictionary<Server, ServerStatus>();
- }
-
- public string Name
- {
- get { return I18N.GetString("High Availability"); }
- }
-
- public string ID
- {
- get { return "com.shadowsocks.strategy.ha"; }
- }
-
- public void ReloadServers()
- {
- // make a copy to avoid locking
- var newServerStatus = new Dictionary<Server, ServerStatus>(_serverStatus);
-
- foreach (var server in _controller.GetCurrentConfiguration().configs)
- {
- if (!newServerStatus.ContainsKey(server))
- {
- var status = new ServerStatus();
- status.server = server;
- status.lastFailure = DateTime.MinValue;
- status.lastRead = DateTime.Now;
- status.lastWrite = DateTime.Now;
- status.latency = new TimeSpan(0, 0, 0, 0, 10);
- status.lastTimeDetectLatency = DateTime.Now;
- newServerStatus[server] = status;
- }
- else
- {
- // update settings for existing server
- newServerStatus[server].server = server;
- }
- }
- _serverStatus = newServerStatus;
-
- ChooseNewServer();
- }
-
- public Server GetAServer(IStrategyCallerType type, System.Net.IPEndPoint localIPEndPoint, EndPoint destEndPoint)
- {
- if (type == IStrategyCallerType.TCP)
- {
- ChooseNewServer();
- }
- if (_currentServer == null)
- {
- return null;
- }
- return _currentServer.server;
- }
-
- /**
- * once failed, try after 5 min
- * and (last write - last read) < 5s
- * and (now - last read) < 5s // means not stuck
- * and latency < 200ms, try after 30s
- */
- public void ChooseNewServer()
- {
- ServerStatus oldServer = _currentServer;
- List<ServerStatus> servers = new List<ServerStatus>(_serverStatus.Values);
- DateTime now = DateTime.Now;
- foreach (var status in servers)
- {
- // all of failure, latency, (lastread - lastwrite) normalized to 1000, then
- // 100 * failure - 2 * latency - 0.5 * (lastread - lastwrite)
- status.score =
- 100 * 1000 * Math.Min(5 * 60, (now - status.lastFailure).TotalSeconds)
- -2 * 5 * (Math.Min(2000, status.latency.TotalMilliseconds) / (1 + (now - status.lastTimeDetectLatency).TotalSeconds / 30 / 10) +
- -0.5 * 200 * Math.Min(5, (status.lastRead - status.lastWrite).TotalSeconds));
- logger.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.FriendlyName(), status.latency, status.score));
- }
- ServerStatus max = null;
- foreach (var status in servers)
- {
- if (max == null)
- {
- max = status;
- }
- else
- {
- if (status.score >= max.score)
- {
- max = status;
- }
- }
- }
- if (max != null)
- {
- if (_currentServer == null || max.score - _currentServer.score > 200)
- {
- _currentServer = max;
- logger.Info($"HA switching to server: {_currentServer.server.FriendlyName()}");
- }
- }
- }
-
- public void UpdateLatency(Model.Server server, TimeSpan latency)
- {
- logger.Debug($"latency: {server.FriendlyName()} {latency}");
-
- ServerStatus status;
- if (_serverStatus.TryGetValue(server, out status))
- {
- status.latency = latency;
- status.lastTimeDetectLatency = DateTime.Now;
- }
- }
-
- public void UpdateLastRead(Model.Server server)
- {
- logger.Debug($"last read: {server.FriendlyName()}");
-
- ServerStatus status;
- if (_serverStatus.TryGetValue(server, out status))
- {
- status.lastRead = DateTime.Now;
- }
- }
-
- public void UpdateLastWrite(Model.Server server)
- {
- logger.Debug($"last write: {server.FriendlyName()}");
-
- ServerStatus status;
- if (_serverStatus.TryGetValue(server, out status))
- {
- status.lastWrite = DateTime.Now;
- }
- }
-
- public void SetFailure(Model.Server server)
- {
- logger.Debug($"failure: {server.FriendlyName()}");
-
- ServerStatus status;
- if (_serverStatus.TryGetValue(server, out status))
- {
- status.lastFailure = DateTime.Now;
- }
- }
- }
- }
|