我们有一个每天接收数百万个请求的Web应用程序,我们使用拦截器审核请求计数和响应状态,该实习生调用带有ConcurrentHashMap
注释spring的类,该类基本上将它们添加到映射并在配置的时间间隔后保留映射。
由于我们有固定的api集,我们维护class Audit{
CounterObject =null;
if(APIMap.contains(apiname){
// fetch existing object
CounterObject=APIMap.get(apiname);
}
else{
//create new object and put it to the map
CounterObject=new CounterObject();
}
// Increment count,note response status and other operations of the CounterObject recieved
}
映射,其中API名称为密钥,其计数和响应状态对象为value.So表示每个请求一个api我们检查它是否存在于我们的地图中,如果存在我们取对象,否则我们创建一个对象并将其放在地图中。对于前任
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
fetchAndSaveUserImage(request.object).then(function() {
response.success();
}, function(error) {
response.error(error);
});
}
function fetchAndSaveUserImage(user) {
if (user.dirtyKeys().indexOf("imageurl") == -1) { return false; }
var image = new Image(); // more like docs, so we have the image in any block below
var params = { url: request.object.get("imageurl") };
return Parse.Cloud.httpRequest(params).then(function(response) {
console.log("pablo# after load imageUrl");
return image.setData(response.buffer);
}).then(function() {
return image.data(); // this is likely the same as response.buffer, but the docs do it, so just to be certain...
}).then(function(buffer) {
var base64 = buffer.toString("base64");
var fileTitle = user.id + ".png";
console.log(fileTitle);
var file = new Parse.File(String(fileTitle), { base64: base64 });
return file.save();
}).then(function(file) {
request.object.set("profileImage", file);
return true;
});
}
然后我们对收到的对象(无论是从地图还是新创建的)和更新计数器执行一些计算。
我们聚合特定时间间隔的映射值并将其提交到数据库。
这对于较少的打击效果很好,但在高负荷下我们会遇到一些问题。像
1。第一个线程得到了对象并更新了计数,但在更新第二个线程之前,得到的值不是最新的,此时第一个线程已完成更改并提交值,但第二个线程更新它先前获得的值并更新它们。但是,对于两个线程执行操作的键是相同的,计数器被最后写入的线程覆盖。
2。我不想将同步关键字放在具有更新计数器逻辑的块上。即使处理是异步的并且用户甚至在我们检查地图中的apiname之前仍然得到响应,如果使用synchronized关键字,则在高负载下消耗的应用程序资源将更高,这可能导致延迟响应或在最坏的情况下导致死锁。 / p>
任何人都可以提出一种解决方案,它可以在不使用synchronized关键字的情况下以并发方式更新计数器
注意:: 我已经在使用 ConcurrentHashMap ,但由于多个线程在高负载时锁定保持和释放速度非常快,因此计数器不匹配。
答案 0 :(得分:1)
在您的情况下,您可以在没有锁定的情况下查看解决方案(或至少使用非常本地锁定)。只要你做简单的操作,你应该能够解决这个问题。
首先,您必须确保只创建一个new CounterObject
,而不是让多个线程创建自己的一个,最后一个覆盖之前的对象。
ConcurrentHashMap
对此有一个非常有用的功能:putIfAbsent
。如果没有对象,它将故事对象并在调用后立即返回地图中的对象(尽管文档没有直接声明它,代码示例也是如此)。它的工作原理如下:
CounterObject counter = APIMap.putIfAbsent("key", new CounterObject());
counter.countStuff();
以上的缺点是你总是创建一个新的CounterObject
,这可能很昂贵。如果是这种情况,您可以使用Java 8 computeIfAbsent
,如果没有与该键相关联的内容,它将仅调用lambda来创建对象。
最后你必须确保CounterObject是线程安全的,最好没有锁定/同步(尽管你有很多CounterObjects,锁定它会比锁定整个map更糟糕,因为更少的线程会尝试锁定它同时反对。)
为了在没有锁定的情况下使CounterObject
安全,您可以查看AtomicInteger
等类,这些类可以在不锁定的情况下执行许多简单的操作。
请注意,每当我说锁定时,它意味着使用显式锁类或使用同步。