Java – global memory counter, which is thread safe and flushed to MySQL every x increments

Can I create in memory counters that all servlets will use?

This global counter will track the web page views of the web application, and the counter will be specific to the currently logged in user That is, the collection will have a key for each user

globalCounterMap[userId].incrementCounter += 1;

In an interval or web browsing count, I want to save the current count to MySQL (insert a new line), for example:

table_pageviews [id,userId,pageview_count,date]

Therefore, this counter will be reset to 0 after refresh

So, if I have a baseservlet that all servlets will inherit, how do I define this field? (finally, static?)

Is concurrenthashmap appropriate? Maybe I can store an atomiclong value for each entry

During refresh, I can use getandset of atomic long by setting it to 0 and save the value I get: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/atomic/AtomicLong.html

Must I synchronize during refresh to MySQL process? (suppose I do this for every 1K page views)

to update

So even if I have 10 servers and each server has its own memory counter, things still work because they will eventually refresh their counts to the database, and then I will simply aggregate rows to get the final count

Solution

Constantine said something like redis might be a better solution Cassandra counter is also a good way to do such things

If you want to do this in Java, here are some code that can safely increase the count without blocking,

class Counter {

    private final ConcurrentHashMap<String,AtomicInteger> counts = new ConcurrentHashMap<String,AtomicInteger>();

    //increment the count for the user
    public void increment(String user) {
        while(true) {
            AtomicInteger current = counts.get(user);
            if(current == null) {
                //new user,initialize the count
                counts.putIfAbsent(user,new AtomicInteger());
                continue;
            }

            int value = current.incrementAndGet();
            if(value > 0) {
                //we have incremented the counter
                break;
            } else {
                //someone is flushing this key,remove it
                //so we can increment on our next iteration
                counts.replace(user,current,new AtomicInteger());
            }

        }
    }

    //call this periodically to flush keys to the database
    //this will empty the counts map so that users who
    //are not active do not take up space
    public void flush() {
        Map<String,Integer> toFlush = new HashMap<String,Integer>();

        for(Map.Entry<String,AtomicInteger> entry : counts.entrySet()) {
            String user = entry.getKey();
            AtomicInteger currentCount = entry.getValue();
            //stop incrementing this count
            counts.remove(user,currentCount);
            //if someone is trying to increment this AtomicInteger after
            //we remove it,they will see a -ve value from incrementAndGet,and 
            //will kNow their increment did not succeed
            Integer count = currentCount.getAndSet(Integer.MIN_VALUE);
            toFlush.put(user,count);
        }

        for(Map.Entry<String,Integer> clearedEntry : toFlush.entrySet()) {
            writeToDb(clearedEntry.getKey(),clearedEntry.getValue());
        }

    }

    public void writeToDb(String user,int count) {
        //do something with the count here
    }


}

The code is quite complex. As Peter Lawley said, a simple mapping protected with the synchronized keyword may perform well enough and be easier to maintain

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>