SpringBoot通过Redis调用Lua脚本实践笔记
概述
redis+lua可以做什么?
相比Redis事务来说,Lua脚本有以下优点
- 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求,而脚本只需一次即可,减少网络传输;
- 原子操作:Redis 将整个脚本作为一个原子执行,无需担心并发,也就无需事务;
- 复用:脚本会永久保存 Redis 中,其他客户端可继续使用。
代码实现
依赖
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.8.0</version> </dependency>
|
redis配置
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13
| spring: redis: database: 0 host: localhost port: 6379 password: timeout: 6000ms lettuce: pool: max-active: 1000 max-wait: -1ms max-idle: 10 min-idle: 5
|
配置RedisTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Configuration public class RedisConfig {
@Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) { RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; } }
|
Lua脚本
在 resources/scripts/
路径下创建 test.lua
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| local key1 = KEYS[1]
local avg1 = ARGV[1] local avg2 = ARGV[2]
redis.log(redis.LOG_WARNING,"key1=" ..key1) redis.log(redis.LOG_WARNING,"avg=" ..avg1, avg2)
local expire = tonumber(ARGV[1]) redis.log(redis.LOG_WARNING,"时间窗=" ..expire)
local limit = tonumber(ARGV[2]) redis.log(redis.LOG_WARNING,"限流频次=" ..limit)
local current = tonumber(redis.call('get', key1) or "0") redis.log(redis.LOG_WARNING,"当前并发=" ..current)
if current + 1 > limit then return 0 else redis.call("INCRBY", key1, "1") redis.call("expire", key1, expire) return 1 end
|
加载lua脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Configuration public class LuaConfiguration {
@Bean public DefaultRedisScript<Long> redisScript() { DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/test.lua"))); return redisScript; } }
|
测试限流效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
@SpringBootTest class RateLimitingServiceTest {
@Autowired private RedisTemplate redisTemplate;
@Autowired private DefaultRedisScript<Long> redisScript;
@Test void luaTest() { List<String> keys = Arrays.asList("aaa"); for (int i = 0; i < 4; i++) { Object execute = redisTemplate.execute(redisScript, keys, 10, 3); System.out.println(execute); } } }
|
结果输出
可见限流生效
最后
本文到此结束,感谢阅读。原创不易, 如果您觉得不错,请关注公众号【当我遇上你】支持一下。