Backend Development 12 min read

Implementing Request Debounce in Backend Systems Using Redis and Redisson

This article explains why request debouncing is essential for backend APIs, identifies the types of endpoints that need it, and provides two distributed solutions—shared Redis cache and Redisson locks—along with complete Java annotation and aspect implementations, testing results, and best‑practice recommendations.

Architecture Digest
Architecture Digest
Architecture Digest
Implementing Request Debounce in Backend Systems Using Redis and Redisson

As a seasoned backend Java developer, the author shares experiences with multi‑tenant systems, message centers, and open‑platform integrations, emphasizing that proper coding standards and practical techniques can prevent serious production issues.

What is debounce? It prevents duplicate submissions caused by user errors or network jitter. Front‑end solutions (e.g., button loading states) handle user‑side issues, while back‑end logic must also guard against repeated processing.

An ideal debounce component should be logically correct, fast, easy to integrate, and provide clear user feedback.

Which APIs need debounce?

User input APIs (search, form fields) – throttle requests until input stabilizes.

Button click APIs (submit, save) – wait for a pause before processing.

Scroll‑load APIs (infinite scroll, pull‑to‑refresh) – delay until scrolling stops.

How to detect duplicate requests?

Define a time window, compare key parameters (not necessarily all fields), and optionally compare request URLs.

Distributed deployment solutions

Two approaches are presented:

1. Shared cache (Redis)

Store a temporary key for each request; if the key already exists, reject the request.

2. Distributed lock (Redisson)

Acquire a lock per request; if the lock cannot be obtained, the request is considered a duplicate.

Implementation details

Define a custom annotation to mark methods that require debouncing:

@RequestLock

Example controller method:

@PostMapping("/add")
@RequiresPermissions(value = "add")
@Log(methodDesc = "添加用户")
public ResponseEntity<String> add(@RequestBody AddReq addReq) {
    return userService.add(addReq);
}

Data transfer object:

@Data
public class AddReq {
    private String userName;
    private String userPhone;
    private List<Long> roleIdList;
}

Generate a unique lock key using another annotation @RequestKeyParam on method parameters or object fields, and a utility class RequestKeyGenerator that builds the key from annotated values.

Redis‑based aspect:

@Aspect
@Configuration
@Order(2)
public class RedisRequestLockAspect {
    private final StringRedisTemplate stringRedisTemplate;
    @Autowired
    public RedisRequestLockAspect(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Around("execution(public * * (..)) && @annotation(com.summo.demo.config.requestlock.RequestLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        String lockKey = RequestKeyGenerator.getLockKey(joinPoint);
        Boolean success = stringRedisTemplate.execute((RedisCallback
) conn ->
            conn.set(lockKey.getBytes(), new byte[0],
                Expiration.from(requestLock.expire(), requestLock.timeUnit()),
                RedisStringCommands.SetOption.SET_IF_ABSENT));
        if (!success) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,请稍后重试");
        }
        try {
            return joinPoint.proceed();
        } catch (Throwable t) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "系统异常");
        }
    }
}

Redisson‑based aspect (requires the Redisson client dependency):

@Aspect
@Configuration
@Order(2)
public class RedissonRequestLockAspect {
    private final RedissonClient redissonClient;
    @Autowired
    public RedissonRequestLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
    @Around("execution(public * * (..)) && @annotation(com.summo.demo.config.requestlock.RequestLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        String lockKey = RequestKeyGenerator.getLockKey(joinPoint);
        RLock lock = redissonClient.getLock(lockKey);
        boolean acquired = false;
        try {
            acquired = lock.tryLock();
            if (!acquired) {
                throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,请稍后重试");
            }
            lock.lock(requestLock.expire(), requestLock.timeUnit());
            return joinPoint.proceed();
        } catch (Throwable t) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "系统异常");
        } finally {
            if (acquired && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

Testing shows the first request succeeds, rapid duplicate submissions are blocked with error code BIZ‑0001, and after the lock expires the request succeeds again. The author notes that true idempotency also requires database unique constraints and additional business checks.

In summary, implementing request debounce with Redis or Redisson provides an effective safeguard against duplicate submissions, but it should be complemented by proper database design and comprehensive business logic.

backendJavaRedisSpringAnnotationRedissonDebounce
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.