我有一张简单的表格:
IPAddress (PK, string)
Requests (int)
它是一个泛滥限制器。每分钟删除表数据。每个页面请求,给定Requests
的{{1}}计数增量。
由于我们的产品和网站的性质,我们确实遭受了一些偶然/有意识的有效DDOS,因此效果很好,我们的网站性能显着提高。
唯一的问题是,当IP因某种原因每分钟向我们的网站发送数千个请求时,我们会突然出现这些错误:
违反PRIMARY KEY约束' PK_v2SiteIPRequests'。无法在对象' dbo.v2SiteIPRequests'中插入重复的密钥。重复键值为([IP_ADDRESS])。声明已经终止。
进行插入的代码是:
IPAddress
处理此异常的最佳方法是什么,为什么会被抛出?这里/// <summary>
/// Call everytime a page view is requested
/// </summary>
private static void DoRequest(string ipAddress)
{
using (var db = new MainContext())
{
var rec = db.v2SiteIPRequests.SingleOrDefault(c => c.IPAddress == ipAddress);
if (rec == null)
{
var n = new v2SiteIPRequest {IPAddress = ipAddress, Requests = 1};
db.v2SiteIPRequests.InsertOnSubmit(n);
db.SubmitChanges();
}
else
{
rec.Requests++;
db.SubmitChanges();
// Ban?
if (rec.Requests >= Settings.MAX_REQUESTS_IN_INTERVAL)
{
BanIP(ipAddress);
}
}
}
}
最好吗?
答案 0 :(得分:5)
如果您同时收到两个请求,则会发生以下情况:
Request one: is it in the database?
Request two: is it in the database?
Request one: No, not yet
Request two: No, not yet
Request one: INSERT
Request two: INSERT
Request one: WORKS
Request two: FAILS (already inserted a split second before)
这里没有什么可以做的,但抓住异常并优雅地处理它。也许通过使用简单的“再试一次”逻辑。
答案 1 :(得分:1)
那里有一些竞争条件,特别是在有并发连接时。
您可能需要更改方法,并始终存储每个请求,然后查询时间范围内是否有更多请求,并采取您需要的任何操作
答案 2 :(得分:0)
以下是基于建议的解决方案。它很难看,但据我所知,它可以工作。
/// <summary>
/// Call everytime a page view is requested
/// </summary>
private static void DoRequest(string ipAddress)
{
using (var db = new MainContext())
{
var rec = db.v2SiteIPRequests.SingleOrDefault(c => c.IPAddress == ipAddress);
if (rec == null)
{
// Catch insert race condition for PK violation. Especially susceptible when being hammered by requests from 1 IP
try
{
var n = new v2SiteIPRequest {IPAddress = ipAddress, Requests = 1};
db.v2SiteIPRequests.InsertOnSubmit(n);
db.SubmitChanges();
}
catch (Exception e)
{
try
{
// Can't reuse original context as it caches
using (var db2 = new MainContext())
{
var rec2 = db2.v2SiteIPRequests.Single(c => c.IPAddress == ipAddress);
rec2.Requests++;
db2.SubmitChanges();
if (rec2.Requests >= Settings.MAX_REQUESTS_IN_INTERVAL)
{
BanIP(ipAddress);
}
}
}
catch (Exception ee)
{
// Shouldn't reach here
Error.Functions.NewError(ee);
}
}
}
else
{
rec.Requests++;
db.SubmitChanges();
// Ban?
if (rec.Requests >= Settings.MAX_REQUESTS_IN_INTERVAL)
{
BanIP(ipAddress);
}
}
}
}