4

Spring ベースの Web アプリケーションには、データベース内のいくつかの統計カウンターをインクリメントするサービス実装があります。ユーザーの応答時間を台無しにしたくないので、Spring の @Async を使用して非同期に定義しました。

public interface ReportingService {

    @Async
    Future<Void> incrementLoginCounter(Long userid);

    @Async
    Future<Void> incrementReadCounter(Long userid, Long productId);
}

そして、次のようなスプリング タスク構成:

<task:annotation-driven executor="taskExecutor" />
<task:executor id="taskExecutor" pool-size="10" />

pool-size="10"使用すると、2 つのスレッドがカウンターを含む同じ初期レコードを作成しようとすると、同時実行の問題が発生します。

pool-size="1"これらの競合を避けるために を設定するのは良い考えですか? これには副作用がありますか?統計を更新するために非同期操作を実行する場所がかなりあります。

4

2 に答える 2

9

副作用は、タスクがエグゼキューターに追加される速度と、単一のスレッドがタスクを処理できる速度に依存します。1 秒あたりに追加されるタスクの数が、1 つのスレッドが 1 秒間に処理できる数よりも多い場合、最終的にメモリ不足エラーが発生するまで、時間の経過とともにキューのサイズが増加するリスクがあります。

このページTask Executionのエグゼキュータセクションを確認してください。彼らは、無制限のキューを持つことは良い考えではないと述べています。

追加されるよりも速くタスクを処理できることがわかっている場合は、おそらく安全です。そうでない場合は、キューの容量を追加し、キューがこのサイズに達した場合に入力スレッドのブロックを処理する必要があります。

于 2013-06-24T15:11:02.937 に答える
4

@Async 呼び出しの一定のストリームの代わりに、投稿した 2 つの例を見て、クライアントの要求に応じて JVM ローカル変数を更新することを検討し、バックグラウンド スレッドがそれをデータベースに時々書き込むようにします。次の行に沿って(半疑似コードに注意してください):

class DefaultReportingService implements ReportingService {

    ConcurrentMap<Long, AtomicLong> numLogins;

    public void incrementLoginCounterForUser(Long userId) {
        numLogins.get(userId).incrementAndGet();
    }

    @Scheduled(..)
    void saveLoginCountersToDb() {
        for (Map.Entry<Long, AtomicLong> entry : numLogins.entrySet()) {
            AtomicLong counter = entry.getValue();
            Long toBeSummedWithTheValueInDb = counter.getAndSet(0L);
            // ...
        }
    }   
}
于 2013-06-24T20:59:45.370 に答える