Designing a 10 WQPS Redis Counter Component: A Systematic Timer Solution
This article presents a complete, step‑by‑step analysis of a high‑concurrency Redis counter component that supports up to 100 000 QPS, covering business pain points, architectural design, two core counting strategies, sharding, local batch optimization, code walkthroughs, and performance benchmark results.
High‑Concurrency Counter Component 10WQPS Deep Practice
Counters appear in virtually every application—article reads, video likes, API rate limiting, online user statistics—yet under burst traffic a poorly designed counter can become a fatal bottleneck.
Chapter 1: From Simple to Complex Counter Requirements
1.1 Business Pain: A Small Counter Caused a Disaster
During a major promotion an e‑commerce platform’s inventory counter failed to handle a sudden surge of requests. The lock mechanism broke, the inventory key was “hit‑through,” and thousands of items were oversold, causing severe financial loss and customer complaints.
1.2 Typical Application Scenarios
Different scenarios demand different trade‑offs among consistency, performance and reliability. The table below summarizes core requirements and the recommended strategy for each case.
Article Read Count : final consistency, very high write frequency, medium reliability – SIMPLE counting.
API Rate Limiting : strong consistency, very high write frequency, high reliability – SLIDING_WINDOW counting.
Online User Statistics : eventual consistency, high write frequency, medium reliability – SLIDING_WINDOW counting.
E‑commerce Inventory : strict strong consistency, high write frequency, very high reliability – distributed lock + database.
Video Likes : final consistency, very high write frequency, high reliability – SIMPLE counting with sharding.
Chapter 2: Architecture Design – A Declarative Counter Component
2.1 Design Goals: High Cohesion, Low Coupling
Why use annotation + AOP instead of manual Redis calls? The reasons are:
Separation of Concerns : business logic stays focused on its core task; counting is a cross‑cutting concern.
Reduced Cognitive Load : developers only add @Count to a method and do not need to know Redis commands or network latency.
Improved Maintainability : changing the counting strategy or underlying implementation only requires modifying CountAspect, not business code.
2.2 Core Architecture and Class Diagram
The component consists of four parts: @Count annotation – entry point for developers. CounterType enum – defines supported strategies (SIMPLE, SLIDING_WINDOW, SHARD, LOCAL_BATCH). CountAspect – AOP advice that runs after the annotated method returns. CounterConfiguration – Spring Boot auto‑configuration.
2.3 Two Core Counting Strategies
Simple Counter (SIMPLE)
Implemented with Redis INCRBY, an O(1) atomic command.
Advantages : extremely high performance, trivial implementation, atomicity guaranteed by Redis single‑threaded model.
Drawbacks : only cumulative increments; cannot count events within a time window (e.g., “how many in the last minute”).
Applicable Scenarios : article reads, total points, any metric that does not need time‑window granularity.
Sliding Window Counter (SLIDING_WINDOW)
First version stores each event as a member of a Redis ZSET where the score is the timestamp.
ZADD key score1 member1
ZADD key score1 member1 score2 member2 ...Workflow:
When an event occurs, generate a unique member (e.g., UUID) and add it with the current timestamp as the score.
Immediately execute ZREMRANGEBYSCORE to remove members older than currentTime - windowSize, keeping only the recent window.
Use ZCARD to obtain the number of members, which equals the count for the current window.
Advantages : precise time‑window statistics, memory‑efficient compared to creating a separate key per event.
Drawbacks : higher CPU and network cost than SIMPLE; if a minute sees millions of events the ZSET becomes a “big key”.
Applicable Scenarios : API rate limiting, anti‑spam, per‑minute active‑user counting.
Second version replaces the single ZSET with per‑second sub‑keys (e.g., key:0 … key:59) to avoid the big‑key problem. Each sub‑key stores the count for its second and expires slightly after one minute, achieving the same sliding effect with constant memory usage.
Chapter 3: Deep Practice of redis-counter-starter
3.1 Core Code Analysis
@Count Annotation
package org.dromara.common.counter.annotation;
import org.dromara.common.counter.enums.CounterType;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Count {
/** Redis key, supports Spring EL expression. */
String key();
/** Increment value, default 1. Ignored for SLIDING_WINDOW. */
long value() default 1L;
/** Counter type, default SLIDING_WINDOW. */
CounterType type() default CounterType.SLIDING_WINDOW;
}The key attribute supports Spring Expression Language, allowing dynamic keys such as 'article:read:count:' + #id.
CountAspect
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class CountAspect {
private final RedisService redisService;
@After("@annotation(count)")
public void doAfter(JoinPoint joinPoint, Count count) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String key = SpringExpressionUtil.parseExpression(method, joinPoint.getArgs(), count.key());
if (count.type() == CounterType.SIMPLE) {
redisService.incrBy(key, count.value());
} else if (count.type() == CounterType.SLIDING_WINDOW) {
long now = System.currentTimeMillis();
String member = UUID.randomUUID().toString() + ":" + now;
redisService.getZSetCache().add(key, member, now);
redisService.getZSetCache().removeRangeByScore(key, 0, now - TimeUnit.MINUTES.toMillis(1));
}
}
}Processing steps:
Parse the SpEL key to obtain the final Redis key.
Route to the appropriate strategy based on count.type().
Execute the corresponding Redis command ( INCRBY or ZSET operations).
3.2 Starter Dependency Packaging
The project follows the Spring Boot starter layout, providing auto‑configuration, properties, and a Maven pom that pulls in Spring Boot, AOP, Redis, Lombok, and Spring Expression dependencies.
src/main/java/.../annotation/Count.java
src/main/java/.../aspect/CountAspect.java
src/main/java/.../config/RedisCounterAutoConfiguration.java
src/main/java/.../enums/CounterType.java
src/main/java/.../util/SpringExpressionUtil.java
3.3 Using the Component
Example 1 – Article Read Count (SIMPLE)
@Count(key = "'article:read:count:' + #id", type = CounterType.SIMPLE)
@GetMapping("/article/{id}")
public R<ArticleVo> getArticle(@PathVariable Long id) {
// business logic to fetch article
return R.ok(articleVo);
}When id = 123, the final Redis key becomes article:read:count:123 and the value is atomically incremented by 1.
Example 2 – IP Rate Limiting (SLIDING_WINDOW)
@Count(key = "'limit:ip:' + #ip", type = CounterType.SLIDING_WINDOW)
public void doSensitiveOperation(String ip) {
// business logic
}The sliding window size is fixed at 1 minute in the current implementation.
Chapter 4: Ultra‑High Concurrency Sharding Counter (First Upgrade)
When a single key cannot sustain tens of thousands of QPS, the load is distributed across N shard keys (e.g., key:0 … key:99).
Each request randomly selects a shard and performs INCR. To obtain the total count, the application issues MGET for all shards and sums the results.
Advantages: linear scalability, effective mitigation of hotspot keys.
Drawbacks: read operations become heavier because they must aggregate many keys.
Chapter 5: Massive Concurrency Two‑Level Counter (Second Upgrade)
This strategy adds a local JVM cache ( ConcurrentHashMap<String, AtomicLong>) that records increments instantly without network round‑trips. A scheduled task runs every second, flushing the accumulated deltas to Redis with INCRBY and then clearing the local counters.
public class LocalBatchCounter {
private final ConcurrentHashMap<String, AtomicLong> localCache = new ConcurrentHashMap<>();
private final RedisService redisService;
@Scheduled(fixedRate = 1000)
public void syncToRedis() {
localCache.forEach((key, count) -> {
long delta = count.getAndSet(0);
if (delta > 0) {
redisService.incrBy(key, delta);
}
});
}
public void increment(String key) {
localCache.computeIfAbsent(key, k -> new AtomicLong(0)).incrementAndGet();
}
public long get(String key) {
long local = localCache.getOrDefault(key, new AtomicLong(0)).get();
Long redis = redisService.getCacheObject(key);
return local + (redis != null ? redis : 0);
}
}Advantages : virtually no network latency for writes, achieving >100 k QPS in benchmarks.
Drawbacks : reads are eventually consistent; a crash before the scheduled flush can lose recent increments.
Chapter 6: Summary
The redis-counter-starter embodies the principles of “convention over configuration” and declarative programming. By annotating a method with @Count, developers obtain a fully featured, high‑performance counter without touching Redis commands.
Key advantages:
Non‑intrusive – AOP separates counting from business logic.
Declarative – developers only declare intent.
Configurable – supports SpEL dynamic keys and multiple strategies.
Extensible – new strategies can be added by extending CounterType and CountAspect.
Performance evolution demonstrated by benchmarks:
SIMPLE : ~11 000 QPS (baseline INCR).
SLIDING_WINDOW : ~9 500 QPS (extra timestamp handling).
SHARD (100 shards) : ~32 000 QPS (load distributed).
LOCAL_BATCH : >105 000 QPS (local cache + batch sync).
Developers should choose the strategy that best matches their consistency requirements and traffic characteristics, ranging from simple cumulative counts to ultra‑high‑throughput, locally cached counters.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tech Freedom Circle
Crazy Maker Circle (Tech Freedom Architecture Circle): a community of tech enthusiasts, experts, and high‑performance fans. Many top‑level masters, architects, and hobbyists have achieved tech freedom; another wave of go‑getters are hustling hard toward tech freedom.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
