如何在并发环境中记录Web请求?

时间:2015-12-18 16:51:12

标签: java multithreading spring interceptor synchronized

我们有一个每天接收数百万个请求的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 ,但由于多个线程在高负载时锁定保持和释放速度非常快,因此计数器不匹配。

1 个答案:

答案 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等类,这些类可以在不锁定的情况下执行许多简单的操作。

请注意,每当我说锁定时,它意味着使用显式锁类或使用同步。