想通关分布式系统「限流问题」?来一篇源码实战

栏目: Lua · 发布时间: 1年前

来源: mp.weixin.qq.com

内容简介:在分布式领域,我们难免会遇到并发量突增,对后端服务造成高压力,严重甚至会导致系统宕机。为避免这种问题,我们通常会为接口添加限流、降级、熔断等能力,从而使接口更为健壮。Java领域常见的开源组件有Netflix的hystrix,阿里系开源的sentinel等,都是蛮不错的限流熔断框架。今天我们就基于Redis组件的特性,实现一个分布式限流组件,名字就定为shield-ratelimiter。首先解释下为何采用Redis作为限流组件的核心。

本文转载自:http://mp.weixin.qq.com/s?__biz=MzA3MTUzOTcxOQ==&mid=2452966978&idx=1&sn=d64328eeba9f264677ba6ca06e698143,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有。

在分布式领域,我们难免会遇到并发量突增,对后端服务造成高压力,严重甚至会导致系统宕机。为避免这种问题,我们通常会为接口添加限流、降级、熔断等能力,从而使接口更为健壮。Java领域常见的开源组件有Netflix的hystrix,阿里系开源的sentinel等,都是蛮不错的限流熔断框架。

今天我们就基于 Redis 组件的特性,实现一个分布式限流组件,名字就定为shield-ratelimiter。

原理

首先解释下为何采用Redis作为限流组件的核心。

通俗地讲,假设一个用户(用IP判断)每秒访问某服务接口的次数不能超过10次,那么我们可以在Redis中创建一个键,并设置键的过期时间为60秒。

当一个用户对此服务接口发起一次访问就把键值加1,在单位时间(此处为1s)内当键值增加到10的时候,就禁止访问服务接口。PS:在某种场景中添加访问时间间隔还是很有必要的。我们本次不考虑间隔时间,只关注单位时间内的访问次数。

需求

原理已经讲过了,说下需求。

1、基于Redis的incr及过期机制开发 2、调用方便,声明式 3、Spring支持

基于上述需求,我们决定基于注解方式进行核心功能开发,基于Spring-boot-starter作为基础环境,从而能够很好的适配Spring环境。

另外,在本次开发中,我们不通过简单的调用Redis的 java 类库API实现对Redis的incr操作。

原因在于,我们要保证整个限流的操作是原子性的,如果用Java代码去做操作及判断,会有并发问题。这里我决定采用 Lua 脚本进行核心逻辑的定义。

为何使用Lua

在正式开发前,我简单介绍下对Redis的操作中,为何推荐使用Lua脚本。

1、减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输; 2、原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务; 3、复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.

Redis添加了对Lua的支持,能够很好的满足原子性、事务性的支持,让我们免去了很多的异常逻辑处理。对于Lua的语法不是本文的主要内容,感兴趣的可以自行查找资料 。整编:微信公众号,搜云库技术团队,ID:souyunku

正式开发

到这里,我们正式开始手写限流组件的进程。

1. 工程定义

项目基于maven构建,主要依赖Spring-boot-starter,我们主要在springboot上进行开发,因此自定义的开发包可以直接依赖下面这个坐标,方便进行包管理。版本号自行选择稳定版。

2. Redis整合

由于我们是基于Redis进行的限流操作,因此需要整合Redis的类库,上面已经讲到,我们是基于Springboot进行的开发,因此这里可以直接整合RedisTemplate。

2.1 坐标引入

这里我们引入spring-boot-starter-redis的依赖。

2.2 注入CacheManager及RedisTemplate

新建一个Redis的配置类,命名为RedisCacheConfig,使用javaconfig形式注入CacheManager及RedisTemplate。为了操作方便,我们采用了Jackson进行序列化。代码如下

注意要使用 @Configuration 标注此类为一个配置类,当然你可以使用 @Component , 但是不推荐,原因在于 @Component 注解虽然也可以当作配置类,但是并不会为其生成CGLIB代理Class,而使用 @Configuration ,CGLIB会为其生成代理类,进行性能的提升。

2.3 调用方application.propertie需要增加Redis配置

我们的包开发完毕之后,调用方的application.properties需要进行相关配置如下:

如果有密码的话,配置password即可。

这里为单机配置,如果需要支持哨兵集群,则配置如下,Java代码不需要改动,只需要变动配置即可。 注意 两种配置不能共存!

3. 定义注解

为了调用方便,我们定义一个名为 RateLimiter 的注解,内容如下

该注解明确只用于方法,主要有三个属性。

1、key–表示限流模块名,指定该值用于区分不同应用,不同场景,推荐格式为:应用名:模块名:ip:接口名:方法名 2、limit–表示单位时间允许通过的请求数 3、expire–incr的值的过期时间,业务中表示限流的单位时间。

4. 解析注解

定义好注解后,需要开发注解使用的切面,这里我们直接使用aspectj进行切面的开发。先看代码

这里是注入了RedisTemplate,使用其API进行Lua脚本的调用。

init() 方法在应用启动时会初始化DefaultRedisScript,并加载Lua脚本,方便进行调用。

PS: Lua脚本放置在classpath下,通过ClassPathResource进行加载。

这里我们定义了一个切点,表示只要注解了 @RateLimiter 的方法,均可以触发限流操作。

这段代码的逻辑为,获取 @RateLimiter 注解配置的属性:key、limit、expire,并通过 redisTemplate.execute(RedisScriptscript,Listkeys,Objectargs) 方法传递给Lua脚本进行限流相关操作,逻辑很清晰。

这里我们定义如果脚本返回状态为0则为触发限流,1表示正常请求。

5. Lua脚本

这里是我们整个限流操作的核心,通过执行一个Lua脚本进行限流的操作。脚本内容如下

逻辑很通俗,我简单介绍下。

1、首先脚本获取Java代码中传递而来的要限流的模块的key,不同的模块key值一定不能相同,否则会覆盖!2、redis.call(‘incr’, key1)对传入的key做incr操作,如果key首次生成,设置超时时间ARGV[1];(初始值为1) 3、ttl是为防止某些key在未设置超时时间并长时间已经存在的情况下做的保护的判断;4、每次请求都会做+1操作,当限流的值val大于我们注解的阈值,则返回0表示已经超过请求限制,触发限流。否则为正常请求 。整编:微信公众号,搜云库技术团队,ID:souyunku

当过期后,又是新的一轮循环,整个过程是一个原子性的操作,能够保证单位时间不会超过我们预设的请求阈值。

到这里我们便可以在项目中进行测试。

测试

这里我贴一下核心代码,我们定义一个接口,并注解 @RateLimiter(key=ratedemo:1.0.0”,limit=5,expire=100) 表示模块 ratedemo:sendPayment:1.0.0 在100s内允许通过5个请求,这里的参数设置是为了方便看结果。实际中,我们通常会设置1s内允许通过的次数。

我们通过RestClient请求接口,日志返回如下:

根据日志能够看到,正常请求5次后,返回限流触发,说明我们的逻辑生效,对前端而言也是可以看到false标记,表明我们的Lua脚本限流逻辑是正确的,这里具体返回什么标记需要调用方进行明确的定义 。整编:微信公众号,搜云库技术团队,ID:souyunku

总结

我们通过Redis的incr及expire功能特性,开发定义了一套基于注解的分布式限流操作,核心逻辑基于Lua保证了原子性。达到了很好的限流的目的,生产上,可以基于该特点进行定制自己的限流组件,当然你可以参考本文的代码,相信你写的一定比我的demo更好!

代码详细地址:

https://github.com/TaXueWWL/shleld-ratelimter

敬请关注,微信公众号「搜云库技术团队」获取最新文章

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知我们,我们会立即删除并表示歉意。谢谢!

来源:http://dwz.win/dmj
整编:搜云库技术团队,欢迎广大技术人员投稿
投稿邮箱:admin@souyunku.com

以上所述就是小编给大家介绍的《想通关分布式系统「限流问题」?来一篇源码实战》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

关注码农网公众号

关注我们,获取更多IT资讯^_^


查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Principles of Object-Oriented JavaScript

Principles of Object-Oriented JavaScript

Nicholas C. Zakas / No Starch Press / 2014-2 / USD 24.95

If you've used a more traditional object-oriented language, such as C++ or Java, JavaScript probably doesn't seem object-oriented at all. It has no concept of classes, and you don't even need to defin......一起来看看 《Principles of Object-Oriented JavaScript》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具