背景
最近特别忙,博客久未更新。
回顾了一下2010-2011年的一些.Net项目代码,觉得对初学者可能有一定参考作用,这里share一下。
主要包括:
- TCP反向代理
- Socket连接池
- 数据包解析
反向代理
一般的Web反向代理大家很熟悉了,主要是通过在客户端和服务端之间架设一层代理服务器,转发客户端的请求至服务端或数据库,并将结果回复给客户端。
其特点主要有:
1、缓存一些数据库I/O过重、却更新不频繁的数据,或者静态数据,如文件、图片等。
2、隔离客户端(公网)和服务端(windows服务、Web服务、文件服务),仅将反向代理服务器的IP、名称、host和端口等暴露给公网。
3、基于第2点,其应该是轻量的、可随时重启的,这在服务端自身所在的服务器重启代价较高或不能忍受重启的条件下,极为有用。
比如服务端本身需要处理大量业务逻辑,可能涉及重计算(cpu和内存要求高)、重I/O(磁盘和网络要求高)或者混合类型,那么服务端的机器成本就很高,因为需要更强力的cpu,更大容量的内存,和更快的磁盘和网络,如果还需要考虑DDOS和CC防御能力,服务端的机器成本将急剧上升。
此时,可考虑反向代理技术,选择带硬防、配置比服务端低很多的廉价机器,来作为反向代理服务器(比如阿x云的云主机,带5G硬防,但其非SSD的云磁盘I/O能力很差,此时不能作为业务服务端的宿主机器,但可以作为反向代理服务器),来组成反向代理分布集群。
DDOS攻击,流量需要聚合到一个峰值,才会打死带防机器,而根据DDOS攻击者所具备的流量打压机器和网络条件的不同,这通常需要一段时间,通过反向代理分布集群,一台反向代理被打死,死亡或黑洞窗口通常在半小时至数小时内,如果能保证有相对充裕的反向代理储备,使得整个集群阵亡前,能有跳出黑洞复生的代理机重新加入集群为客户端提供服务,那么就可以形成对抗。即使储备不足,至少可以为受到攻击时的决策赢得更多时间。
综上所述,反向代理技术通过增加额外的网络传输时间,却获得了很多客户端与服务端直接连接所不具备的优势。
通常的web应用服务器,如nginx都可提供反向代理能力。
但tcp级别的反向代理还是比较少的。
当时的项目倒逼出了这么一个需求,其中用到一些基础组件如下。
连接池
如果客户端使用tcp协议和反向代理服务器通讯,比如常见的桌面客户端,那么可以考虑单个长连接 + 异步的方式连接至代理服务器。
而多台代理服务器和真正的业务服务端之间,由于代理和服务端之间多为同步通讯,为了效率,可考虑使用多连接 + 池化的技术,让连接介于长、短之间,综合两者的长处。
下面给出项目中真实使用过的连接池代码,实现中参考了当时MongoDB的C#驱动部分:
/// <summary>
/// 连接池
/// 特性及更新:
/// 1:从单个移除不可用连接,变为批量移除
/// 2:移除连接不再防止,暴露重连风暴风险
/// 目的:尽快尽多发现不可用连接,防止请求失败
/// 考虑:一般只开放200个连接,没什么大问题.
/// 5:增大排队线程数和排队超时时间,考虑:网络抖动和业务层慢操作
/// 6:增大连接最大存活和空闲时间,考虑:网络抖动和业务层慢操作
/// 7:尽最大可能负载请求并减轻 某一瞬间 传递给主力的请求和连接数目
/// </summary>
public class SessionPool
{
private object _poolLock = new object();
public int PoolSize { get; set; }
public IList<SyncTcpSession> AvaliableSessions
{
get { return _avaliableSessions; }
}
public ILog Logger;
private int _waitQueueSize;
private bool _inMaintainPoolSize;
private bool _inEnsureMinConnectionPoolSizeWorkItem;
private IList<SyncTcpSession> _avaliableSessions = new List<SyncTcpSession>();
public int MaxWaitQueueSize { get; set; }
public int MaxConnectionPoolSize { get; set; }
public int MinConnectionPoolSize { get; set; }
public TimeSpan WaitQueueTimeout { get; set; }
/// <summary>
/// 连接最大存活时间(分)
/// </summary>
public TimeSpan MaxConnectionLifeTime { get; set; }
/// <summary>
/// 连接最大空闲时间(秒)
/// </summary>
public TimeSpan MaxConnectionIdleTime { get; set; }
public IPEndPoint RemoteAddr { get; set; }
public SessionPool(ILog log)
{
Logger = log;
}
/// <summary>
/// 获取可用连接
/// </summary>
/// <returns></returns>
public SyncTcpSession GetAvaliableSession()
{
lock (_poolLock)
{
//等待获取连接的线程发生了严重积压
//说明连接数量不足以应付业务,或者业务层处理积压
//考虑优化业务层和数据库或者增加超时时间
if (_waitQueueSize >= MaxWaitQueueSize)
{
var ex = new Exception("等待获取连接的线程数过多!");
Logger.Error(ex.Message, ex);
return null;
}
_waitQueueSize += 1;
try
{
DateTime timeoutAt = DateTime.Now + WaitQueueTimeout;
while (true)
{
//有可用连接
if (_avaliableSessions.Count > 0)
{
//先尝试找到已经打开过的连接
for (int i = _avaliableSessions.Count - 1; i >= 0; i--)
{
if (_avaliableSessions[i].State == SessionState.Open)
{
var connection = _avaliableSessions[i];
_avaliableSessions.RemoveAt(i);
return connection;
}
}
//否则去掉最近最少使用的连接,并返回新连接
AvaliableSessions[0].Close();
AvaliableSessions.RemoveAt(0);
return new SyncTcpSession(this);
}
//无可用连接,新建连接
if (PoolSize < MaxConnectionPoolSize)
{
var connection = new SyncTcpSession(this);
PoolSize += 1;
return connection;
}
//不能创建新的连接也没有可用连接,等待连接被回收.
var timeRemaining = timeoutAt - DateTime.Now;
if (timeRemaining > TimeSpan.Zero)
{
Monitor.Wait(_poolLock, timeRemaining);
}
else
{
//等待超时,说明连接数量不足以应付业务,或者业务层处理积压,考虑优化业务层和数据库或者增加超时时间
var ex = new TimeoutException("等待SyncTcpSession已超时.");
Logger.Error(ex.Message, ex);
}
}
}
finally
{
_waitQueueSize -= 1;
}
}
}
/// <summary>
/// 清空连接池