Implementing Configurable Rate Limiting, Switches, and Circuit Breaking with Redis and Hystrix in a Java Backend

This article describes a backend solution that uses Redis for configurable rate‑limiting and interface switches, and Hystrix for circuit‑breaking, detailing the requirements, AOP‑based approach, and full Java code implementation for managing non‑core query overloads.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Configurable Rate Limiting, Switches, and Circuit Breaking with Redis and Hystrix in a Java Backend

Requirements

In the online system, non‑core query interfaces cause database CPU spikes when they time out or error, because users keep retrying. The manager asked for three features: configurable rate‑limiting, a switch to turn off problematic interfaces, and a configurable circuit‑breaker.

The first two features will be implemented with Redis; the circuit‑breaker will use Hystrix (later Archaius) because it is the simplest solution for the current project.

Approach

Interface shielding is achieved via AOP. When a method is invoked, the AOP advice reads a switch value from Redis using the interface key; if the switch is off the request is rejected. Rate‑limiting also uses AOP: the current count is incremented in Redis, compared with the configured limit, and the request is blocked if the limit is exceeded.

Code

AccessLimiter interface defines methods for checking limits, refreshing limits, and reading status and configuration from Redis.

package com.hcfc.auto.util.limit;
import java.util.concurrent.TimeUnit;
/**
 * @创建人 peng.wang
 * @描述 访问限制器
 */
public interface AccessLimiter {
    /**
     * 检查指定的key是否收到访问限制
     * @param key   限制接口的标识
     * @param times 访问次数
     * @param per   一段时间
     * @param unit  时间单位
     * @return
     */
    public boolean isLimited(String key, long times, long per, TimeUnit unit);
    /**
     * 移除访问限制
     * @param key
     */
    public void refreshLimited(String key);
    /**
     * 接口是否打开
     * @return
     */
    public boolean isStatus(String redisKey);
    /**
     * 接口的限流大小
     * @param redisKeyTimes
     * @return
     */
    public long getTimes(String redisKeyTimes);
    /**
     * 接口限流时间段
     * @param redisKeyPer
     * @return
     */
    public long getPer(String redisKeyPer);
    /**
     * 接口的限流时间单位
     * @param redisKeyUnit
     * @return
     */
    public TimeUnit getUnit(String redisKeyUnit);
    /**
     * 是否删除接口限流
     * @param redisKeyIsRefresh
     * @return
     */
    public boolean getIsRefresh(String redisKeyIsRefresh);
}

RedisAccessLimiter implements the interface using Spring’s RedisTemplate.

package com.hcfc.auto.util.limit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
 * @创建人 peng.wang
 * @描述 基于Redis的实现
 */
@Component
public class RedisAccessLimiter implements AccessLimiter {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisAccessLimiter.class);
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public boolean isLimited(String key, long times, long per, TimeUnit unit) {
        Long curTimes = redisTemplate.boundValueOps(key).increment(1);
        LOGGER.info("curTimes {}",curTimes);
        if(curTimes > times) {
            LOGGER.debug("超频访问:[{}]",key);
            return true;
        } else {
            if(curTimes == 1) {
                LOGGER.info(" set expire ");
                redisTemplate.boundValueOps(key).expire(per, unit);
                return false;
            } else {
                return false;
            }
        }
    }
    @Override
    public void refreshLimited(String key) {
        redisTemplate.delete(key);
    }
    @Override
    public boolean isStatus(String redisKey) {
        try {
            return (boolean)redisTemplate.opsForValue().get(redisKey+"IsOn");
        } catch(Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKey);
            return false;
        }
    }
    @Override
    public long getTimes(String redisKeyTimes) {
        try {
            return (long)redisTemplate.opsForValue().get(redisKeyTimes+"Times");
        } catch(Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyTimes);
            return 0;
        }
    }
    @Override
    public long getPer(String redisKeyPer) {
        try {
            return (long)redisTemplate.opsForValue().get(redisKeyPer+"Per");
        } catch(Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyPer);
            return 0;
        }
    }
    @Override
    public TimeUnit getUnit(String redisKeyUnit) {
        try {
            return (TimeUnit) redisTemplate.opsForValue().get(redisKeyUnit+"Unit");
        } catch(Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyUnit);
            return TimeUnit.SECONDS;
        }
    }
    @Override
    public boolean getIsRefresh(String redisKeyIsRefresh) {
        try {
            return (boolean)redisTemplate.opsForValue().get(redisKeyIsRefresh+"IsRefresh");
        } catch(Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyIsRefresh);
            return false;
        }
    }
}

Limit annotation marks methods that need protection.

package com.hcfc.auto.util.limit;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limit {}

LimitAspect contains the AOP logic that reads configuration, checks the switch, applies rate‑limiting, and optionally refreshes the limit after a successful call.

package com.hcfc.auto.util.limit;
import com.hcfc.auto.vo.response.ResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
 * @创建人 peng.wang
 * @创建时间 2019/10/11
 * @描述
 */
@Slf4j
@Aspect
@Component
public class LimitAspect {
    private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
    @Autowired
    private AccessLimiter limiter;
    @Autowired
    GenerateRedisKey generateRedisKey;
    @Pointcut("@annotation(com.hcfc.auto.util.limit.Limit)")
    public void limitPointcut() {}
    @Around("limitPointcut()")
    public Object doArround(ProceedingJoinPoint joinPoint) throws Throwable {
        String redisKey = generateRedisKey.getMethodUrlConvertRedisKey(joinPoint);
        long per = limiter.getPer(redisKey);
        long times = limiter.getTimes(redisKey);
        TimeUnit unit = limiter.getUnit(redisKey);
        boolean isRefresh = limiter.getIsRefresh(redisKey);
        boolean methodLimitStatus = limiter.isStatus(redisKey);
        String bindingKey = genBindingKey(joinPoint);
        if (methodLimitStatus) {
            logger.info("method is closed, key: ", bindingKey);
            return ResponseDto.fail("40007", "method is closed, key:" + bindingKey);
        }
        if(bindingKey != null){
            boolean isLimited = limiter.isLimited(bindingKey, times, per, unit);
            if(isLimited){
                logger.info("limit takes effect: {}", bindingKey);
                return ResponseDto.fail("40006", "access over limit, key: " + bindingKey);
            }
        }
        Object result = null;
        result = joinPoint.proceed();
        if(bindingKey!=null && isRefresh) {
            limiter.refreshLimited(bindingKey);
            logger.info("limit refreshed: {}", bindingKey);
        }
        return result;
    }
    private String genBindingKey(ProceedingJoinPoint joinPoint) {
        try{
            Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
            return joinPoint.getTarget().getClass().getName() + "." + m.getName();
        }catch(Throwable e){
            return null;
        }
    }
}

GenerateRedisKey builds Redis keys from Spring MVC @RequestMapping values.

package com.hcfc.auto.util.limit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
/**
 * @创建人 peng.wang
 * @描述
 */
@Component
public class GenerateRedisKey {
    public String getMethodUrlConvertRedisKey(ProceedingJoinPoint joinPoint)
    {
        StringBuilder redisKey =new StringBuilder("");
        Method m = ((MethodSignature)joinPoint.getSignature()).getMethod();
        RequestMapping methodAnnotation = m.getAnnotation(RequestMapping.class);
        if (methodAnnotation != null) {
            String[] methodValue = methodAnnotation.value();
            String dscUrl = diagonalLineToCamel(methodValue[0]);
            return redisKey.append("RSK:").append("interfaceIsOpen:").append(dscUrl).toString();
        }
        return redisKey.toString();
    }
    private String diagonalLineToCamel(String param)
    {
        char UNDERLINE='/';
        if (param==null||"".equals(param.trim())){
            return "";
        }
        int len=param.length();
        StringBuilder sb=new StringBuilder(len);
        for (int i = 1; i < len; i++) {
            char c=param.charAt(i);
            if (c==UNDERLINE){
                if (++i<len){
                    sb.append(Character.toUpperCase(param.charAt(i)));
                }
            }else{
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

Summary

The core logic increments a Redis key for each request, checks whether the count exceeds the configured limit, and sets an expiration when the key is first created. Although more mature solutions such as Guava exist, the team chose a Redis‑based implementation for distributed compatibility.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

aopspringrate limitingHystrix
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

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.