shield-ratelimiter
基于Redis的分布式限流工具包
在分布式领域,我们难免会遇到并发量突增,对后端服务造成高压力,严重甚至会导致系统宕机。为避免这种问题,我们通常会为接口添加限流、降级、熔断等能力,从而使接口更为健壮。Java领域常见的开源组件有Netflix的hystrix,阿里系开源的sentinel等,都是蛮不错的限流熔断框架。
今天我们就基于Redis组件的特性,实现一个分布式限流组件,名字就定为shield-ratelimiter。
原理
首先解释下为何采用Redis作为限流组件的核心。
通俗地讲,假设一个用户(用IP判断)每秒访问某服务接口的次数不能超过10次,那么我们可以在Redis中创建一个键,并设置键的过期时间为60秒。
当一个用户对此服务接口发起一次访问就把键值加1,在单位时间(此处为1s)内当键值增加到10的时候,就禁止访问服务接口。PS:在某种场景中添加访问时间间隔还是很有必要的。我们本次不考虑间隔时间,只关注单位时间内的访问次数。
需求
原理已经讲过了,说下需求。
基于Redis的incr及过期机制开发
调用方便,声明式
Spring支持
基于上述需求,我们决定基于注解方式进行核心功能开发,基于Spring-boot-starter作为基础环境,从而能够很好的适配Spring环境。
另外,在本次开发中,我们不通过简单的调用Redis的java类库API实现对Redis的incr操作。
原因在于,我们要保证整个限流的操作是原子性的,如果用Java代码去做操作及判断,会有并发问题。这里我决定采用Lua脚本进行核心逻辑的定义。
为何使用Lua
在正式开发前,我简单介绍下对Redis的操作中,为何推荐使用Lua脚本。
减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;
复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.
Redis添加了对Lua的支持,能够很好的满足原子性、事务性的支持,让我们免去了很多的异常逻辑处理。对于Lua的语法不是本文的主要内容,感兴趣的可以自行查找资料。
正式开发
到这里,我们正式开始手写限流组件的进程。
1. 工程定义
项目基于maven构建,主要依赖Spring-boot-starter,我们主要在springboot上进行开发,因此自定义的开发包可以直接依赖下面这个坐标,方便进行包管理。版本号自行选择稳定版。
org.springframework.boot
spring-boot-starter
1.4.2.RELEASE
2.1 坐标引入
这里我们引入spring-boot-starter-redis的依赖。
org.springframework.boot
spring-boot-starter-redis
1.4.2.RELEASE
2.2 注入CacheManager及RedisTemplate
新建一个Redis的配置类,命名为RedisCacheConfig,使用javaconfig形式注入CacheManager及RedisTemplate。为了操作方便,我们采用了Jackson进行序列化。代码如下
@Configuration
@EnableCaching
public class RedisCacheConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisCacheConfig.class);
@Bean
public CacheManager cacheManager(RedisTemplate, ?> redisTemplate) {
CacheManager cacheManager = new RedisCacheManager(redisTemplate);