背景:在对外的接口中,为了限制每个用户的接口访问频率,或者是付费接口(多少钱多少次调用次数等),作为最苦逼的代码搬运工,我们需要掏出“代码大全”,话不多说,搞起
-
为啥要用Lua? 因为Redis执行Lua脚步是原子性的,所以我们可以在脚步中写我们的判断逻辑
-
先上一段Lua
//从redis读取key为KEYS[1]的值
local val = redis.call("GET", KEYS[1])
//如果不存在,设置key,value 过期时间
if not val
then
redis.call('SET', KEYS[1], ARGV[1])
redis.call('EXPIRE', KEYS[1], ARGV[2])
return true
//如果值小于阈值,则计数加一
elseif val < ARGV[3]
then
redis.call('INCR', KEYS[1])
return true
//超过阈值,返回false
else
return false
end
KEYS:表示传入的key数组,下标从1开始
ARGV:表示传入的参数
- 上代码
- xml 配置redis依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>
</dependency>
</dependencies>
- conf 配置redisTemplate
@Configuration
class RedisConf {
@Bean
fun redisTemplate(redisConnectionFactory: LettuceConnectionFactory): RedisTemplate<String, Serializable> {
val template = RedisTemplate<String, Serializable>();
template.keySerializer = StringRedisSerializer();
template.valueSerializer = GenericJackson2JsonRedisSerializer();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
- redis+lua 调用lua脚本
@RestController
@RequestMapping("/test")
class TestController {
@Autowired
private lateinit var redisTemplate: RedisTemplate<String, Serializable>
@GetMapping("/1/{userId}")
fun test(@PathVariable userId: String): Boolean {
val luaScript = buildScript()
val redisScript: RedisScript<Boolean> = DefaultRedisScript(luaScript, Boolean::class.java)
return redisTemplate.execute(redisScript, listOf("test:uri:$userId"), 1, 60, 5)
}
private fun buildScript(): String {
return """
local key = KEYS[1]
local val = redis.call("GET", key);
if not val
then
redis.call('SET', KEYS[1], ARGV[1])
redis.call('EXPIRE', KEYS[1], ARGV[2])
return true
elseif val < ARGV[3]
then
redis.call('INCR', KEYS[1])
return true
else
return false
end
""".trimIndent()
}
}