From c16c44f21f0320cb4bc1f43e565f41389e7ef1e1 Mon Sep 17 00:00:00 2001 From: orangej Date: Wed, 21 Jul 2021 21:26:28 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E4=B8=BAJbootRe?= =?UTF-8?q?dis=E5=A2=9E=E5=8A=A0eval=E6=96=B9=E6=B3=95=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E6=89=A7=E8=A1=8Clua=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/jboot/support/redis/JbootRedis.java | 1 + .../redis/jedis/JbootJedisClusterImpl.java | 8 ++++-- .../support/redis/jedis/JbootJedisImpl.java | 9 +++++++ .../redis/lettuce/JbootLettuceImpl.java | 5 ++++ .../redis/redisson/JbootRedissonImpl.java | 5 ++++ .../java/io/jboot/test/redis/RedisTester.java | 26 +++++++++++++++++++ 6 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/jboot/support/redis/JbootRedis.java b/src/main/java/io/jboot/support/redis/JbootRedis.java index 9d9f14b9..a6ec93f1 100644 --- a/src/main/java/io/jboot/support/redis/JbootRedis.java +++ b/src/main/java/io/jboot/support/redis/JbootRedis.java @@ -659,6 +659,7 @@ public interface JbootRedis { public List valueListFromBytesList(Collection data); + Object eval(String script, int keyCount, String... params); } diff --git a/src/main/java/io/jboot/support/redis/jedis/JbootJedisClusterImpl.java b/src/main/java/io/jboot/support/redis/jedis/JbootJedisClusterImpl.java index c43fc4bb..f68fb6c9 100644 --- a/src/main/java/io/jboot/support/redis/jedis/JbootJedisClusterImpl.java +++ b/src/main/java/io/jboot/support/redis/jedis/JbootJedisClusterImpl.java @@ -52,8 +52,8 @@ public class JbootJedisClusterImpl extends JbootRedisBase { if (timeout != null) { this.timeout = timeout; } - if(maxAttempts == null) { - maxAttempts = this.maxAttempts; + if (maxAttempts == null) { + maxAttempts = this.maxAttempts; } GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); @@ -1227,6 +1227,10 @@ public class JbootJedisClusterImpl extends JbootRedisBase { return new RedisScanResult<>(scanResult.getStringCursor(), scanResult.getResult()); } + @Override + public Object eval(String script, int keyCount, String... params) { + return jedisCluster.eval(script, keyCount, params); + } public JedisCluster getJedisCluster() { return jedisCluster; diff --git a/src/main/java/io/jboot/support/redis/jedis/JbootJedisImpl.java b/src/main/java/io/jboot/support/redis/jedis/JbootJedisImpl.java index 9cf5a4a1..a7181b98 100644 --- a/src/main/java/io/jboot/support/redis/jedis/JbootJedisImpl.java +++ b/src/main/java/io/jboot/support/redis/jedis/JbootJedisImpl.java @@ -1563,6 +1563,15 @@ public class JbootJedisImpl extends JbootRedisBase { } } + @Override + public Object eval(String script, int keyCount, String... params) { + Jedis jedis = getJedis(); + try { + return jedis.eval(script, keyCount, params); + } finally { + returnResource(jedis); + } + } public Jedis getJedis() { try { diff --git a/src/main/java/io/jboot/support/redis/lettuce/JbootLettuceImpl.java b/src/main/java/io/jboot/support/redis/lettuce/JbootLettuceImpl.java index 830ad87a..653c336f 100644 --- a/src/main/java/io/jboot/support/redis/lettuce/JbootLettuceImpl.java +++ b/src/main/java/io/jboot/support/redis/lettuce/JbootLettuceImpl.java @@ -525,4 +525,9 @@ public class JbootLettuceImpl implements JbootRedis { public List valueListFromBytesList(Collection data) { return null; } + + @Override + public Object eval(String script, int keyCount, String... params) { + return null; + } } diff --git a/src/main/java/io/jboot/support/redis/redisson/JbootRedissonImpl.java b/src/main/java/io/jboot/support/redis/redisson/JbootRedissonImpl.java index 7c20d105..d7625904 100644 --- a/src/main/java/io/jboot/support/redis/redisson/JbootRedissonImpl.java +++ b/src/main/java/io/jboot/support/redis/redisson/JbootRedissonImpl.java @@ -514,4 +514,9 @@ public class JbootRedissonImpl implements JbootRedis { public List valueListFromBytesList(Collection data) { return null; } + + @Override + public Object eval(String script, int keyCount, String... params) { + return null; + } } diff --git a/src/test/java/io/jboot/test/redis/RedisTester.java b/src/test/java/io/jboot/test/redis/RedisTester.java index a5913738..e46940cc 100644 --- a/src/test/java/io/jboot/test/redis/RedisTester.java +++ b/src/test/java/io/jboot/test/redis/RedisTester.java @@ -4,12 +4,38 @@ import io.jboot.app.JbootApplication; import io.jboot.support.redis.JbootRedis; import io.jboot.support.redis.JbootRedisManager; import io.jboot.support.redis.RedisScanResult; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; import java.util.ArrayList; import java.util.List; public class RedisTester { + @Before + public void config() { + JbootApplication.setBootArg("jboot.redis.host", "127.0.0.1"); + JbootApplication.setBootArg("jboot.redis.port", "6379"); + } + + @Test + public void testGetAndSet() { + JbootRedis redis = JbootRedisManager.me().getRedis(); + String key = "JbootRedisValue"; + Assert.assertEquals("OK", redis.set(key, "10")); + Assert.assertEquals("10", redis.get(key)); + redis.del(key); + } + + @Test + public void testEval() { + JbootRedis redis = JbootRedisManager.me().getRedis(); + String response = (String) redis.eval("return KEYS[1]", 1, "key1"); + Assert.assertEquals("key1", response); + } + + public static void main(String[] args) { JbootApplication.setBootArg("jboot.redis.host", "127.0.0.1"); JbootApplication.setBootArg("jboot.redis.database", "3"); -- Gitee From 8827bb1d920897e1df9bd53b838a619290ca82f7 Mon Sep 17 00:00:00 2001 From: orangej Date: Wed, 21 Jul 2021 21:28:56 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E9=80=9A?= =?UTF-8?q?=E8=BF=87redis=E5=AE=9E=E7=8E=B0=E9=9B=86=E7=BE=A4=E7=9A=84?= =?UTF-8?q?=E9=99=90=E6=AC=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jboot/components/limiter/LimitScope.java | 15 ++++++ .../limiter/annotation/EnableLimit.java | 7 +++ .../interceptor/BaseLimiterInterceptor.java | 12 +++++ .../interceptor/LimiterInterceptor.java | 10 +++- .../limiter/redis/RedisRateLimitUtil.java | 50 +++++++++++++++++++ .../jboot/test/aop/inject/AopController.java | 6 +++ .../java/io/jboot/test/redis/RedisTester.java | 8 +++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/jboot/components/limiter/LimitScope.java create mode 100644 src/main/java/io/jboot/components/limiter/redis/RedisRateLimitUtil.java diff --git a/src/main/java/io/jboot/components/limiter/LimitScope.java b/src/main/java/io/jboot/components/limiter/LimitScope.java new file mode 100644 index 00000000..7e4dd25f --- /dev/null +++ b/src/main/java/io/jboot/components/limiter/LimitScope.java @@ -0,0 +1,15 @@ +package io.jboot.components.limiter; + +public enum LimitScope { + + /** + * 整个集群限次,多实例共享 + */ + CLUSTER, + + /** + * 每个实例单独限次 + */ + NODE; + +} diff --git a/src/main/java/io/jboot/components/limiter/annotation/EnableLimit.java b/src/main/java/io/jboot/components/limiter/annotation/EnableLimit.java index 582cf8a0..248069a1 100644 --- a/src/main/java/io/jboot/components/limiter/annotation/EnableLimit.java +++ b/src/main/java/io/jboot/components/limiter/annotation/EnableLimit.java @@ -15,6 +15,7 @@ */ package io.jboot.components.limiter.annotation; +import io.jboot.components.limiter.LimitScope; import io.jboot.components.limiter.LimitType; import java.lang.annotation.*; @@ -38,6 +39,12 @@ public @interface EnableLimit { */ String type() default LimitType.TOKEN_BUCKET; + + /** + * 作用域,默认为单节点本地限次 + */ + LimitScope scope() default LimitScope.NODE; + /** * 频率 * diff --git a/src/main/java/io/jboot/components/limiter/interceptor/BaseLimiterInterceptor.java b/src/main/java/io/jboot/components/limiter/interceptor/BaseLimiterInterceptor.java index 39fb0877..2ab9177d 100644 --- a/src/main/java/io/jboot/components/limiter/interceptor/BaseLimiterInterceptor.java +++ b/src/main/java/io/jboot/components/limiter/interceptor/BaseLimiterInterceptor.java @@ -19,6 +19,7 @@ package io.jboot.components.limiter.interceptor; import com.google.common.util.concurrent.RateLimiter; import com.jfinal.aop.Invocation; import io.jboot.components.limiter.LimiterManager; +import io.jboot.components.limiter.redis.RedisRateLimitUtil; import io.jboot.utils.ClassUtil; import io.jboot.utils.StrUtil; @@ -60,6 +61,17 @@ public abstract class BaseLimiterInterceptor { } } + protected void doInterceptForTokenBucketWithCluster(int rate, String resource, String fallback, Invocation inv) { + //允许通行 + if (RedisRateLimitUtil.tryAcquire(resource, rate)) { + inv.invoke(); + } + //不允许通行 + else { + doExecFallback(resource, fallback, inv); + } + } + protected void doExecFallback(String resource, String fallback, Invocation inv) { LimiterManager.me().processFallback(resource, fallback, inv); } diff --git a/src/main/java/io/jboot/components/limiter/interceptor/LimiterInterceptor.java b/src/main/java/io/jboot/components/limiter/interceptor/LimiterInterceptor.java index c400a53d..58defb32 100644 --- a/src/main/java/io/jboot/components/limiter/interceptor/LimiterInterceptor.java +++ b/src/main/java/io/jboot/components/limiter/interceptor/LimiterInterceptor.java @@ -18,6 +18,7 @@ package io.jboot.components.limiter.interceptor; import com.jfinal.aop.Interceptor; import com.jfinal.aop.Invocation; +import io.jboot.components.limiter.LimitScope; import io.jboot.components.limiter.LimitType; import io.jboot.components.limiter.annotation.EnableLimit; import io.jboot.utils.AnnotationUtil; @@ -40,10 +41,17 @@ public class LimiterInterceptor extends BaseLimiterInterceptor implements Interc String type = AnnotationUtil.get(enableLimit.type()); switch (type) { case LimitType.CONCURRENCY: + if (LimitScope.CLUSTER == enableLimit.scope()) { + throw new IllegalArgumentException("Concurrency limit for cluster not implement!"); + } doInterceptForConcurrency(enableLimit.rate(), resource, enableLimit.fallback(), inv); break; case LimitType.TOKEN_BUCKET: - doInterceptForTokenBucket(enableLimit.rate(), resource, enableLimit.fallback(), inv); + if (LimitScope.CLUSTER == enableLimit.scope()) { + doInterceptForTokenBucketWithCluster(enableLimit.rate(), resource, enableLimit.fallback(), inv); + } else { + doInterceptForTokenBucket(enableLimit.rate(), resource, enableLimit.fallback(), inv); + } break; } } diff --git a/src/main/java/io/jboot/components/limiter/redis/RedisRateLimitUtil.java b/src/main/java/io/jboot/components/limiter/redis/RedisRateLimitUtil.java new file mode 100644 index 00000000..2152ce7d --- /dev/null +++ b/src/main/java/io/jboot/components/limiter/redis/RedisRateLimitUtil.java @@ -0,0 +1,50 @@ +package io.jboot.components.limiter.redis; + +import io.jboot.support.redis.JbootRedis; +import io.jboot.support.redis.JbootRedisManager; + +/** + * 通过lua脚本来进行限次 + */ +public class RedisRateLimitUtil { + + private static final String RATE_LIMIT_SCRIPT = "local c" + + "\nc = redis.call('get',KEYS[1])" + + // 调用量已经超过最大值,直接返回 + "\nif c and tonumber(c) > tonumber(ARGV[1]) then" + + "\nreturn tonumber(c);" + + "\nend" + + // 自增 + "\nc = redis.call('incr',KEYS[1])" + + "\nif tonumber(c) == 1 then" + + // 从第一次调用开始限流,设置对应键值的过期 + "\nredis.call('expire',KEYS[1],ARGV[2])" + + "\nend" + + "\nreturn c;"; + + private static JbootRedis redis; + + /** + * 限制时长默认为1秒 + */ + public static boolean tryAcquire(String resource, int rate) { + return tryAcquire(resource, rate, 1); + } + + /** + * 尝试是否能正常执行 + * + * @param resource 资源名 + * @param rate 限制次数 + * @param periodSeconds 限制时长,单位为秒 + * @return true 可以执行 + * false 限次,禁止 + */ + public static boolean tryAcquire(String resource, int rate, int periodSeconds) { + if (redis == null) { + redis = JbootRedisManager.me().getRedis(); + } + Long count = (Long) redis.eval(RATE_LIMIT_SCRIPT, 1, resource, String.valueOf(rate), String.valueOf(periodSeconds)); + return count <= rate; + } +} diff --git a/src/test/java/io/jboot/test/aop/inject/AopController.java b/src/test/java/io/jboot/test/aop/inject/AopController.java index 5026f256..4a234bae 100644 --- a/src/test/java/io/jboot/test/aop/inject/AopController.java +++ b/src/test/java/io/jboot/test/aop/inject/AopController.java @@ -2,6 +2,7 @@ package io.jboot.test.aop.inject; import com.jfinal.aop.Inject; import io.jboot.aop.annotation.ConfigValue; +import io.jboot.components.limiter.LimitScope; import io.jboot.components.limiter.annotation.EnableLimit; import io.jboot.test.aop.staticconstruct.StaticConstructManager; import io.jboot.web.controller.JbootController; @@ -36,6 +37,11 @@ public class AopController extends JbootController { renderText("host:" + host + " port:" + port + " xxx:" + xxx); } + @EnableLimit(rate = 1, fallback = "aaa", scope = LimitScope.CLUSTER) + public void bbb() { + renderText("host:" + host + " port:" + port + " xxx:" + xxx); + } + public void aaa(){ renderText("aaa"); } diff --git a/src/test/java/io/jboot/test/redis/RedisTester.java b/src/test/java/io/jboot/test/redis/RedisTester.java index e46940cc..45bb80db 100644 --- a/src/test/java/io/jboot/test/redis/RedisTester.java +++ b/src/test/java/io/jboot/test/redis/RedisTester.java @@ -1,6 +1,7 @@ package io.jboot.test.redis; import io.jboot.app.JbootApplication; +import io.jboot.components.limiter.redis.RedisRateLimitUtil; import io.jboot.support.redis.JbootRedis; import io.jboot.support.redis.JbootRedisManager; import io.jboot.support.redis.RedisScanResult; @@ -35,6 +36,13 @@ public class RedisTester { Assert.assertEquals("key1", response); } + @Test + public void testRateLimit() { + String resource = "limited-resource"; + Assert.assertTrue(RedisRateLimitUtil.tryAcquire(resource, 2, 1)); + Assert.assertTrue(RedisRateLimitUtil.tryAcquire(resource, 2, 1)); + Assert.assertFalse(RedisRateLimitUtil.tryAcquire(resource, 2, 1)); + } public static void main(String[] args) { JbootApplication.setBootArg("jboot.redis.host", "127.0.0.1"); -- Gitee