全局内存计数器是线程安全的,并且每 x 增量刷新到 mysql

cod*_*ing 3 java concurrency servlets

是否可以创建一个所有 servlet 都会使用的内存计数器?

此全局计数器将跟踪 Web 应用程序的页面浏览量,并且该计数器将特定于当前登录的用户。即该集合将为每个用户提供一个密钥。

globalCounterMap[userId].incrementCounter += 1;
Run Code Online (Sandbox Code Playgroud)

在一定的时间间隔或页面浏览量,我想将当前计数保存到mysql(插入新行),例如:

table_pageviews [id, userId, pageview_count, date]
Run Code Online (Sandbox Code Playgroud)

因此,刷新后该计数器将重置为 0。

因此,如果我有一个所有 servlet 都继承自的 BaseServlet,我将如何定义该字段?(最终,静态?)

ConcurrentHashMap 合适吗? 也许我可以为每个条目的值存储一个 AtomicLong 。

在刷新期间,我可以通过设置为 0 来使用原子 long 的 getAndSet,并保存我“获取”的值: http: //docs.oracle.com/javase/1.5.0/docs/api/java/ util/concurrent/atomic/AtomicLong.html

在刷新到 mysql 过程中是否必须同步?(假设我每浏览 1K 浏览量就执行一次)

更新

因此,即使我有 10 台服务器,每台服务器都有自己的内存计数器,事情仍然会正常工作,因为它们最终都会将计数刷新到数据库,然后我将简单地聚合行以获得最终计数。

sbr*_*ges 5

正如 Konstantin 所说,像 redis 这样的东西可能是更好的解决方案。Cassandra 计数器也是完成此类操作的一个非常好的方法。

如果你想用java来做到这一点,这里有一些代码可以安全地增加计数并且不会阻塞,

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
    }


}
Run Code Online (Sandbox Code Playgroud)

代码相当复杂,正如 Peter Lawrey 所说,用同步关键字保护的简单映射可能性能足够好并且更容易维护。