概述
redis+lua 可以做什么?
- 限流
- 分布式锁
- 信号量
- 事务
相比 Redis 事务来说,Lua 脚本有以下优点
- 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求,而脚本只需一次即可,减少网络传输;
- 原子操作:Redis 将整个脚本作为一个原子执行,无需担心并发,也就无需事务;
- 复用:脚本会永久保存 Redis 中,其他客户端可继续使用。
代码实现
依赖
<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
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
/**
* @author 当我遇上你
* @公众号 当我遇上你
* @since 2020-05-21
*/
@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
脚本
--获取KEY
local key1 = KEYS[1]
--获取参数
local avg1 = ARGV[1]
local avg2 = ARGV[2]
--打印日志到reids
--注意,这里的打印日志级别,需要和redis.conf配置文件中的日志设置级别一致才行
redis.log(redis.LOG_WARNING,"key1=" ..key1)
redis.log(redis.LOG_WARNING,"avg=" ..avg1, avg2)
--将参数String转为数字类型
--限流时间窗
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 脚本
/**
* @author 当我遇上你
* @公众号 当我遇上你
* @since 2020-05-21
*/
@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;
}
}
测试限流效果
/**
* @author 当我遇上你
* @公众号 当我遇上你
* @since 2020-05-21
*/
@SpringBootTest
class RateLimitingServiceTest {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private DefaultRedisScript<Long> redisScript;
@Test
void luaTest() {
List<String> keys = Arrays.asList("aaa");
// 10秒内小于或等于3次时返回1,否则返回0
for (int i = 0; i < 4; i++) {
Object execute = redisTemplate.execute(redisScript, keys, 10, 3);
System.out.println(execute);
}
}
}
结果输出
1
1
1
0
可见限流生效
最后
本文到此结束,感谢阅读。原创不易, 如果您觉得不错,请关注公众号【当我遇上你】支持一下。