You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

HighAvailabilityStrategy.cs 6.4 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. using NLog;
  2. using Shadowsocks.Model;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Net;
  6. using System.Text;
  7. namespace Shadowsocks.Controller.Strategy
  8. {
  9. class HighAvailabilityStrategy : IStrategy
  10. {
  11. private static Logger logger = LogManager.GetCurrentClassLogger();
  12. protected ServerStatus _currentServer;
  13. protected Dictionary<Server, ServerStatus> _serverStatus;
  14. ShadowsocksController _controller;
  15. Random _random;
  16. public class ServerStatus
  17. {
  18. // time interval between SYN and SYN+ACK
  19. public TimeSpan latency;
  20. public DateTime lastTimeDetectLatency;
  21. // last time anything received
  22. public DateTime lastRead;
  23. // last time anything sent
  24. public DateTime lastWrite;
  25. // connection refused or closed before anything received
  26. public DateTime lastFailure;
  27. public Server server;
  28. public double score;
  29. }
  30. public HighAvailabilityStrategy(ShadowsocksController controller)
  31. {
  32. _controller = controller;
  33. _random = new Random();
  34. _serverStatus = new Dictionary<Server, ServerStatus>();
  35. }
  36. public string Name
  37. {
  38. get { return I18N.GetString("High Availability"); }
  39. }
  40. public string ID
  41. {
  42. get { return "com.shadowsocks.strategy.ha"; }
  43. }
  44. public void ReloadServers()
  45. {
  46. // make a copy to avoid locking
  47. var newServerStatus = new Dictionary<Server, ServerStatus>(_serverStatus);
  48. foreach (var server in _controller.GetCurrentConfiguration().configs)
  49. {
  50. if (!newServerStatus.ContainsKey(server))
  51. {
  52. var status = new ServerStatus();
  53. status.server = server;
  54. status.lastFailure = DateTime.MinValue;
  55. status.lastRead = DateTime.Now;
  56. status.lastWrite = DateTime.Now;
  57. status.latency = new TimeSpan(0, 0, 0, 0, 10);
  58. status.lastTimeDetectLatency = DateTime.Now;
  59. newServerStatus[server] = status;
  60. }
  61. else
  62. {
  63. // update settings for existing server
  64. newServerStatus[server].server = server;
  65. }
  66. }
  67. _serverStatus = newServerStatus;
  68. ChooseNewServer();
  69. }
  70. public Server GetAServer(IStrategyCallerType type, System.Net.IPEndPoint localIPEndPoint, EndPoint destEndPoint)
  71. {
  72. if (type == IStrategyCallerType.TCP)
  73. {
  74. ChooseNewServer();
  75. }
  76. if (_currentServer == null)
  77. {
  78. return null;
  79. }
  80. return _currentServer.server;
  81. }
  82. /**
  83. * once failed, try after 5 min
  84. * and (last write - last read) < 5s
  85. * and (now - last read) < 5s // means not stuck
  86. * and latency < 200ms, try after 30s
  87. */
  88. public void ChooseNewServer()
  89. {
  90. ServerStatus oldServer = _currentServer;
  91. List<ServerStatus> servers = new List<ServerStatus>(_serverStatus.Values);
  92. DateTime now = DateTime.Now;
  93. foreach (var status in servers)
  94. {
  95. // all of failure, latency, (lastread - lastwrite) normalized to 1000, then
  96. // 100 * failure - 2 * latency - 0.5 * (lastread - lastwrite)
  97. status.score =
  98. 100 * 1000 * Math.Min(5 * 60, (now - status.lastFailure).TotalSeconds)
  99. -2 * 5 * (Math.Min(2000, status.latency.TotalMilliseconds) / (1 + (now - status.lastTimeDetectLatency).TotalSeconds / 30 / 10) +
  100. -0.5 * 200 * Math.Min(5, (status.lastRead - status.lastWrite).TotalSeconds));
  101. logger.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.FriendlyName(), status.latency, status.score));
  102. }
  103. ServerStatus max = null;
  104. foreach (var status in servers)
  105. {
  106. if (max == null)
  107. {
  108. max = status;
  109. }
  110. else
  111. {
  112. if (status.score >= max.score)
  113. {
  114. max = status;
  115. }
  116. }
  117. }
  118. if (max != null)
  119. {
  120. if (_currentServer == null || max.score - _currentServer.score > 200)
  121. {
  122. _currentServer = max;
  123. logger.Info($"HA switching to server: {_currentServer.server.FriendlyName()}");
  124. }
  125. }
  126. }
  127. public void UpdateLatency(Model.Server server, TimeSpan latency)
  128. {
  129. logger.Debug($"latency: {server.FriendlyName()} {latency}");
  130. ServerStatus status;
  131. if (_serverStatus.TryGetValue(server, out status))
  132. {
  133. status.latency = latency;
  134. status.lastTimeDetectLatency = DateTime.Now;
  135. }
  136. }
  137. public void UpdateLastRead(Model.Server server)
  138. {
  139. logger.Debug($"last read: {server.FriendlyName()}");
  140. ServerStatus status;
  141. if (_serverStatus.TryGetValue(server, out status))
  142. {
  143. status.lastRead = DateTime.Now;
  144. }
  145. }
  146. public void UpdateLastWrite(Model.Server server)
  147. {
  148. logger.Debug($"last write: {server.FriendlyName()}");
  149. ServerStatus status;
  150. if (_serverStatus.TryGetValue(server, out status))
  151. {
  152. status.lastWrite = DateTime.Now;
  153. }
  154. }
  155. public void SetFailure(Model.Server server)
  156. {
  157. logger.Debug($"failure: {server.FriendlyName()}");
  158. ServerStatus status;
  159. if (_serverStatus.TryGetValue(server, out status))
  160. {
  161. status.lastFailure = DateTime.Now;
  162. }
  163. }
  164. }
  165. }