Redis+Luaに基づく分散型ストリーム制限


一、Mavneプロジェクトを新規作成し、rateと名付けた.Limiterは,Lombokとguavaの依存性を導入した.

    org.projectlombok
    lombok


    com.google.guava
    guava
    29.0-jre

二、rate_Limiterプロジェクトの下にratelimiterという名前の新しいannotationのサブモジュールで、モジュールのpomファイルにredisの依存を追加します.

    org.springframework.boot
    spring-boot-starter-data-redis

三、ratelimiter_annotationモジュールのsrc/main/javaディレクトリの下にサービスパッケージを作成し、サービスパッケージの下にAccessLimiterというクラスを作成します.
@Service
@Slf4j
public class AccessLimiter {
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * DefaultRedisScript        ,             lua       ,
     *                   ,                。
     *      4        (Long, Boolean, List, or deserialized value type)
    */
    @Autowired
    private DefaultRedisScript rateLimiterLua;

    public void limitAccess(String key,Integer limit){
        //  lua  
        boolean acquire=redisTemplate.execute(
                rateLimiterLua,
                Lists.newArrayList(key),
                limit.toString());
        if (!acquire){
            log.error("your access is blocked,key={}",key);
            throw new RuntimeException("your access is blocked");
        }
    }
}

四、configパッケージを新規作成し、RedisConfigurationという構成クラスを作成する
@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(
            RedisConnectionFactory factory
    ){
        return new StringRedisTemplate(factory);
    }

    @Bean
    public DefaultRedisScript loadRedisScript(){
        DefaultRedisScript redisScript=new DefaultRedisScript();
        //  lua  
        redisScript.setLocation(new ClassPathResource("ratelimiter.lua"));
        //      
        redisScript.setResultType(java.lang.Boolean.class);
        return redisScript;
    }
}

五、resourcesディレクトリの下でluaスクリプトファイルratelimiterを新規作成する.lua.
--
-- Created by IntelliJ IDEA.
-- User: wanglei
--
--  lua   ,        ,     redis              ,
--    KEYS、ARGV。

--        KEYS        , lua                。

--     ,   ARGV       ,          ,    Lua    ,
--    ARGV      ,              。


--   KEYS        
local methodKey = KEYS[1]
redis.log(redis.LOG_DEBUG, 'key is', methodKey)

--   ARGV      
local limit = tonumber(ARGV[1])

--         
local count = tonumber(redis.call('get', methodKey) or "0")

--         
if count + 1 > limit then
    --       
    return false
else
    --       
    --          +1
    redis.call("INCRBY", methodKey, 1)
    --       
    redis.call("EXPIRE", methodKey, 1)
    --   
    return true
end

六、rate_Limiterプロジェクトにratelimiterを追加testのサブモジュールは、前のスクリプトをテストするために使用されます.ratelimiter_testには以下の依存性が導入されている.

    org.springframework.boot
    spring-boot-starter-web



    ${project.groupId}
    ratelimiter_annotation
    ${project.version}

七、ratelimiter_testのsrc/main/javaでcontrollerパッケージを新規作成し、controllerパッケージの下でTestControllerのクラスを作成します.
@RestController
@Slf4j
public class TestController {
    @Autowired
    private AccessLimiter accessLimiter;

    @GetMapping("test")
    public String test(){
        accessLimiter.limitAccess("ratelimiter-test",1);
        return "success";
    }
}

八、アプリケーションで.propertiesにredisを追加する構成
spring.redis.database=0
spring.redis.host=localhsot
spring.redis.port=6379
spring.redis.password=root

九、起動クラスを作成し、プロジェクトを起動し、postmanで制限フローの結果をテストします.
@SpringBootApplication
public class RatelimiterTestApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(RatelimiterTestApplication.class, args);
    }

}

十、以上のいくつかのステップを通じて、Redis+Luaに基づくストリーム制限を実現したが、コードはまだ完璧ではない.今、プロジェクトを改造して、カスタムの注釈でプロジェクトのどこでもストリーム制限を実現することができる.
まずratelimiter_annotationモジュールにaopの依存性を導入する.

    org.springframework.boot
    spring-boot-starter-aop

そしてratelimiter_annotationモジュールにannotationのパッケージを新規作成し、annotationパッケージの下にAccessLimiterという注釈を作成します.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimiter {
    int limit();
    String methodKey() default "";
}

もう1つのaspectのパッケージを作成し、AccessLimiterAspectというクラスを作成します.
@Slf4j
@Aspect
@Component
public class AccessLimiterAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private DefaultRedisScript rateLimiterLua;

    @Pointcut("@annotation(com.wl.annotation.AccessLimiter)")
    public void cut(){
        log.info("cut");
    }

    @Before("cut()")
    public void before(JoinPoint joinPoint){
        //1、      ,  method key
        MethodSignature methodSignature= (MethodSignature) joinPoint.getSignature();
        Method method=methodSignature.getMethod();
        AccessLimiter annotation=method.getAnnotation(AccessLimiter.class);
        if (annotation==null){
            return;
        }
        String key=annotation.methodKey();
        Integer limit=annotation.limit();

        //      methodKey,             
        if (StringUtils.isEmpty(key)){
            Class[] type=method.getParameterTypes();
            key=method.getName();

            if (type!=null){
                String paramTypes= Arrays.stream(type)
                        .map(Class::getName)
                        .collect(Collectors.joining(","));
                log.info("param types: "+paramTypes);
                key+="#"+paramTypes;
            }
        }

        //2、  redis
        boolean acquire=redisTemplate.execute(
                rateLimiterLua,
                Lists.newArrayList(key),
                limit.toString());
        if (!acquire){
            log.error("your access is blocked,key={}",key);
            throw new RuntimeException("your access is blocked");
        }
    }
}

これで9つの注釈を使用することができます.TestControllerで新しい方法を追加します.
@GetMapping("test-annotation")
@com.wl.annotation.AccessLimiter(limit = 1)
public String testAnnotation(){
    return "success";
}

クラスを起動してプロジェクトを再起動し、testAnnotationインタフェースをテストします.