- 浏览: 2654360 次
- 来自: 杭州
文章分类
- 全部博客 (1188)
- webwork (4)
- 网摘 (18)
- java (103)
- hibernate (1)
- Linux (85)
- 职业发展 (1)
- activeMQ (2)
- netty (14)
- svn (1)
- webx3 (12)
- mysql (81)
- css (1)
- HTML (6)
- apache (3)
- 测试 (2)
- javascript (1)
- 储存 (1)
- jvm (5)
- code (13)
- 多线程 (12)
- Spring (18)
- webxs (2)
- python (119)
- duitang (0)
- mongo (3)
- nosql (4)
- tomcat (4)
- memcached (20)
- 算法 (28)
- django (28)
- shell (1)
- 工作总结 (5)
- solr (42)
- beansdb (6)
- nginx (3)
- 性能 (30)
- 数据推荐 (1)
- maven (8)
- tonado (1)
- uwsgi (5)
- hessian (4)
- ibatis (3)
- Security (2)
- HTPP (1)
- gevent (6)
- 读书笔记 (1)
- Maxent (2)
- mogo (0)
- thread (3)
- 架构 (5)
- NIO (5)
- 正则 (1)
- lucene (5)
- feed (4)
- redis (17)
- TCP (6)
- test (0)
- python,code (1)
- PIL (3)
- guava (2)
- jython (4)
- httpclient (2)
- cache (3)
- signal (1)
- dubbo (7)
- HTTP (4)
- json (3)
- java socket (1)
- io (2)
- socket (22)
- hash (2)
- Cassandra (1)
- 分布式文件系统 (5)
- Dynamo (2)
- gc (8)
- scp (1)
- rsync (1)
- mecached (0)
- mongoDB (29)
- Thrift (1)
- scribe (2)
- 服务化 (3)
- 问题 (83)
- mat (1)
- classloader (2)
- javaBean (1)
- 文档集合 (27)
- 消息队列 (3)
- nginx,文档集合 (1)
- dboss (12)
- libevent (1)
- 读书 (0)
- 数学 (3)
- 流程 (0)
- HBase (34)
- 自动化测试 (1)
- ubuntu (2)
- 并发 (1)
- sping (1)
- 图形 (1)
- freemarker (1)
- jdbc (3)
- dbcp (0)
- sharding (1)
- 性能测试 (1)
- 设计模式 (2)
- unicode (1)
- OceanBase (3)
- jmagick (1)
- gunicorn (1)
- url (1)
- form (1)
- 安全 (2)
- nlp (8)
- libmemcached (1)
- 规则引擎 (1)
- awk (2)
- 服务器 (1)
- snmpd (1)
- btrace (1)
- 代码 (1)
- cygwin (1)
- mahout (3)
- 电子书 (1)
- 机器学习 (5)
- 数据挖掘 (1)
- nltk (6)
- pool (1)
- log4j (2)
- 总结 (11)
- c++ (1)
- java源代码 (1)
- ocr (1)
- 基础算法 (3)
- SA (1)
- 笔记 (1)
- ml (4)
- zokeeper (0)
- jms (1)
- zookeeper (5)
- zkclient (1)
- hadoop (13)
- mq (2)
- git (9)
- 问题,io (1)
- storm (11)
- zk (1)
- 性能优化 (2)
- example (1)
- tmux (1)
- 环境 (2)
- kyro (1)
- 日志系统 (3)
- hdfs (2)
- python_socket (2)
- date (2)
- elasticsearch (1)
- jetty (1)
- 树 (1)
- 汽车 (1)
- mdrill (1)
- 车 (1)
- 日志 (1)
- web (1)
- 编译原理 (1)
- 信息检索 (1)
- 性能,linux (1)
- spam (1)
- 序列化 (1)
- fabric (2)
- guice (1)
- disruptor (1)
- executor (1)
- logback (2)
- 开源 (1)
- 设计 (1)
- 监控 (3)
- english (1)
- 问题记录 (1)
- Bitmap (1)
- 云计算 (1)
- 问题排查 (1)
- highchat (1)
- mac (3)
- docker (1)
- jdk (1)
- 表达式 (1)
- 网络 (1)
- 时间管理 (1)
- 时间序列 (1)
- OLAP (1)
- Big Table (0)
- sql (1)
- kafka (1)
- md5 (1)
- springboot (1)
- spring security (1)
- Spring Boot (3)
- mybatis (1)
- java8 (1)
- 分布式事务 (1)
- 限流 (1)
- Shadowsocks (0)
- 2018 (1)
- 服务治理 (1)
- 设计原则 (1)
- log (0)
- perftools (1)
最新评论
-
siphlina:
课程——基于Python数据分析与机器学习案例实战教程分享网盘 ...
Python机器学习库 -
san_yun:
leibnitz 写道hi,我想知道,无论在92还是94版本, ...
hbase的行锁与多版本并发控制(MVCC) -
leibnitz:
hi,我想知道,无论在92还是94版本,更新时(如Puts)都 ...
hbase的行锁与多版本并发控制(MVCC) -
107x:
不错,谢谢!
Latent Semantic Analysis(LSA/ LSI)算法简介 -
107x:
不错,谢谢!
Python机器学习库
原文:https://mp.weixin.qq.com/s/xlcizvNkxfEoFa03GjiC9w
为什么需要限流
我们都知道,构建高并发的系统有三大利器:缓存、降级、限流。通过使用缓存,可以让用户在获取数据链路的过程变的更短、获取数据的速度变得更快,从而提升系统的吞吐量,通过使用降级手短,可以把非核心业务的资源用来保证核心业务的正常服务。然而有些场景并不能用缓存和降级来解决,比如稀缺资源的秒杀、抢购、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发或请求量,即限流。通过限流,可以保证系统处理的流量始终在系统自身能力范围之内,最终做到有损服务而非不服务。
常见的几种限流算法简介
目前常用的限流算法有以下几种,每种算法都各有优缺点:
一、简单计数法
简单计数法是限流算法里面最简单粗暴的一种算法,其算法的核心思想就是维护一个计数器,这个计数器有一个时间窗口,当达到这个时间窗口时计数器会归零。每当一个新请求到来的时候,都让计数器自增,当计数器自增达到设置的上限时,不再提供服务。
简单计数法正如其名,实现起来非常简单,这是它的最大的优点,但太简单粗暴也会导致一些 小的问题,比如无法预防短时间突然的流量高峰。我们举个例子来说,有个api,我们希望这个api每秒钟最多处理的请求数量是1000,即限制它的qps为1000,那么使用简单计数法有什么问题呢?参考下面的图片,在第1秒的前999毫秒内都没有任何请求,但第1秒的最后1毫秒,突然来了1000个请求,这个时候是没有问题的。紧接着,计数器达到了时间窗口,计数归零了,第2秒的第1毫秒,又来了1000个请求,相当于两毫秒内允许api处理了2000个请求,和我们约定的qps为1000有很大的差距。所以,鉴于以上的问题,简单计数可以应用于一些请求相对均匀,对限制要求不是很准确的场景。
二、滑动窗口算法(rolling window)
滑动窗口算法把时间划分为一个一个的窗口“格子”,记录每个格子的请求数量,每当时间经过了一个格子时向后滑动时间窗格并统计所有时间窗格的请求数量这样的方式来计数。具体的算法思想我们使用两张图来说明。沿用上面的例子,有个api,我们希望这个api每秒钟最多处理的请求数量是1000,即限制它的qps为1000。那么我们把1秒钟分成10个时间窗口格子,那么每个格子就是100ms,每当请求到来时,把当前时间对应格子上的请求数量加1,并计算当前时间格子和之前9个格子的请求数量之和是否超过上限。如下图:可以看到,当前的请求在第10个格子上,新请求过来时,我们去计算当前时间格子的请求数量和前面9个格子的请求数量之和,check是否大于1000,大于的话,则拒绝新的请求。
正是因为有之前格子计数的“积累”,所以当同样面临简单计数算法的时间边界情况时,请求在第二秒的初始也不会得到允许,如下图所示。
滑动窗口算法保证了在限流的任意时间段之内,请求数量都不会超过设置的上限,所以滑动窗口算法适用于对限流的准确性有一定要求的场景。
三、漏桶算法(leaky bucket)
漏桶的算法十分简单。只需要满足以下的规则:
1. 有一个固定容量的桶,有水流进来,也有水流出去。对于流出去的水,必须按照一个设定的速率。
2. 当桶里的水已经流空的时候,不需要再流出水。
3. 对于流入水的速率不做限制,如果流入的水速率大于流出的水,则当桶里的水达到了桶的容量时,多余的水被直接丢弃。
对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。将算法中的水换成实际应用中的请求,就是漏桶的算法。由于漏桶算法天生就限制了请求的速度,当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法也不会出现简单计数那样的临界问题,非常适合于一些流量整形(Traffic Shaping)和流量控制(TrafficPolicing)等严格要求请求速率的场景。
四、令牌桶算法(token bucket)
令牌桶算法也非常简单,有一个容量固定的桶,以一个恒定的速率产生令牌,如果桶内的令牌满了则多余的令牌会被丢弃。每当请求进来时,先去桶内拿一个令牌,桶内的令牌拿完了,则必须等待桶内产生令牌才能允许后续的请求(或者直接拒绝)。由于桶内可以堆积一定的令牌(一般为桶的容量),所以令牌桶算法比漏桶算法有个优点就是令牌桶算法可以允许一定量的流量高峰。
令牌桶算法虽然也容许一定量的流量突发,但是和简单计数的边界流量高峰还是有区别的。简单计数法是在每次计数器时间边界的瞬间都可能允许有两倍流量,而令牌桶算法由于有桶里令牌的积累,能允许一定量的流量突发,之后再有更多的请求,都必须等待令牌生成,等待令牌生成期间的所有请求要么阻塞到拿到令牌要么就失败,令牌桶在极端情况下也会有两倍流量的可能,使用令牌桶算法限流的时候需要考虑到这种情况,当业务上非常依赖限流准确性的时候不可使用令牌桶算法,我们通过图示来说明:
当一段时间内没有请求(或者请求速度小于令牌产生速度)时,桶里的令牌逐渐又积累起来,又会允许0秒时刻那样的流量突发:
只要理解了两点,一,产生令牌的速度恒定。二,没有令牌不得访问。就能理解令牌桶算法的工作原理。
“令牌桶算法”和“漏桶算法”的主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的上限,因此它适合于具有突发特性的流量。考虑到限流只是对服务/资源的一种保护,而非严格的业务需求,所以51采用了令牌桶算法来实现限流。具体的业务需要使用什么限流算法还要具体分析,当业务量非常大,系统可用性很高的时候使用简单计数也未尝不可。
应用级限流及分布式限流
应用级限流是指每个服务自己保证自己的资源以一个合理的限度使用,因为每个服务总有一个极限的请求并发数(QPS/TPS阈值),如果超过了这个上限,服务就会变得无响应或者响应很慢,从而不能很好的提供服务,通过限流,使服务的处理能力在上限之内,则服务就可以继续提供服务了。应用级限流对于保护单个服务节点是非常有效的,但是应用级限流不能保护服务集群背后的资源,比如控制对DB资源的访问,这种情况就需要使用分布式限流,保证一个服务集群总的请求量在设定的上限之内。
分布式限流的实现思路一般有两个:
第一种思路是把限流的算法应用到Redis/Memcached上,这样所有集群内的机器使用的都是一个单点的计数器,从而来实现分布式限流。例如使用Redis + Lua脚本实现,Redis集群作为计数的存储设备,Luau脚本来实现具体的算法。这里多说一句Redis + Lua这种思路,Redis虽然是单线程的,无需担心命令执行的线程安全问题,但是由于限流算法的复杂性,实现一次限流判断,需要多次和Redis之间交互,一次限流判断过程中的多次Redis交互有可能构成“先检查后执行(Check-Then-Act)”操作,这样的操作会导致Redis后面的行为可能基于一次失效的数据,从而导致限流算法发生问题,根据墨菲定律,当并发数量达到一定程度的时候,这样的问题一定会发生。而且多次的Redis交互之间也占用了额外的网络IO请求时间,所以解决这个问题需要使用Lua脚本,把限流的算法用Lua脚本实现,然后就可以一次执行Lua脚本就得到限流结果。Lua脚本在Redis执行的过程中不会被其他的操作打断,没有了“先检查后执行”问题,但也要注意Lua脚本不可过于复杂,如果因Lua脚本写的不正确有可能导致整个Redis集群夯住,从而引发悲剧事故。还有一点需要注意,Lua脚本无法从Redis获取系统时间(注2)。前面介绍的一些限流常见算法中,除了简单计数法,其他的算法的一般实现都需要获得一个系统时间,可以先通过Redis的Time命令获取时间,然后传入Lua脚本中这样的方式来实现。
下面附一个简单计数算法的Lua实现:
-- KEYS[1] key: 限流的redis key,外部计算传入-- ARGV[1] limit: 最大访问流量/数量限制-- ARGV[2] dur: 限流的总时长,单位毫秒-- ARGV[3] permits: 请求的流量,如果限制数量,则为1local key = KEYS[1]local limit = tonumber(ARGV[1])local dur = ARGV[2]local permits = tonumber(ARGV[3])local current = tonumber(redis.pcall('GET', key) or '0')if current + permits > limit then return 0else if (current == 0) then redis.pcall('SET', key, 0) redis.pcall('PEXPIRE', key, dur) end redis.pcall('INCRBY', key, permits) return 1end
实现分布式限流的另外一种思路就是通过把整个集群的限流数量均匀分摊到集群内的每一个机器上面,通过每个节点各自进行应用级别的限流来模拟整个集群的限流。这种思路的实现需要满足两个条件,一是集群内的每个节点承担的流量是均衡的,节点之间如果流量不均衡,则会导致流量高的节点很容易就触发分给自己的上限,因为每个节点的上限是通过集群上限除以节点数量得出的。二是当集群内的节点数量发生变化的时候,要实时去更新每个节点的流量上限,否则一旦有机器的增加,可能会出现导致无法保护后端资源的问题发生。
比较一下应用级限流和分布式限流的优缺点,可以发现这两种限流方式各有优缺点。应用级限流对应用节点自身保护,好处是当应用的能力不足以提供服务时,可以随时进行扩容,而无需考虑扩容带来的影响。而分布式限流优点是可以做到对应用后端的资源进行保护,无论是采用单点的存储方式还是使用节点模拟来实现分布式限流,都需要额外的操作来保证限流的正确性。具体使用哪种限流方式,还是要看具体的业务场景。
51的限流实践
51信用卡实行的是微服务架构实践,每个DC内的微服务调用使用的是RoundRobin的负载均衡策略,所以可以认为每个服务的流量都是均衡的。另外每个服务在启动的时候会把服务的信息注册到Consul集群(一个高可用、支持跨DC的服务发现与KV存储系统),通过Consul可以获取每个服务部署的节点数量。基于以上的两点,所以51的分布式限流采用的使用节点来模拟集群的方式,另外也对应用级的限流做了支持,可以使用服务治理后台来实时调整,对单个服务所做的调整会实时下发到这个服务的每个实例。对单个节点我们直接使用了谷歌Guava中的RateLimiter类来承担限流功能,RateLimiter是谷歌对令牌桶限流算法的一个比较成熟的轻量级实现,其原理是利用系统时间来维护一个“下次令牌产生时间”,每次请求时,通过计算当前时间和“下次令牌产生时间”的差距可以计算出来当前桶里面有多少个可用令牌,当桶里还有可用令牌时,请求可以继续,并把使用同步的方法把令牌桶里面的令牌数量减一,当桶里没有令牌时,Guava也提供了两种处理方案:阻塞直到产生令牌或等待指定的超时时间后拒绝请求。另外Guava还提供了一个名为setRate()方法,来允许你随时安全地调整限制的速度,根据前面分布式限流与应用级限流中提到的“当集群内的节点数量发生变化的时候,要实时去更新每个节点的流量上限”,实在是太贴心了。
限流的配置包含三个维度,针对api的调用限制、针对RPC的调用限制、还有针对服务内某个具体的method的调用限制,分别对应配置文件的service、client、method节点,当配置的对象是api时,还可以更详细的配置当前的api对某个具体的服务限制调用多少(即下面的by-service),配置的yml结构是这样设计的:
这是接口限流部分对应的后台管理界面:
当服务出现符合配置的条件时,使用pattern作为Key,去本地缓存的中获取key对应的RateLimiter对象,没有则新创建一个,如果当期配置的是分布式限流,则需要去服务注册发现中心获取服务的节点数量,和内存中缓存的节点数量不一致时矫正单台节点的限流数量。为了提高系统的可用性,并没有每次请求都去服务注册发现中心获取,这个过程在另外的线程中定时执行,使用的时候其实用的是一份本地的可用节点数量的快照。有了RateLimiter对象,就可以直接使用Guava为我们提供的方法了,这部分就不再赘述。
参考文章:
1. http://www.kissyu.org/2016/08/13/限流算法总结/
2. https://redis.io/commands/eval
3. http://xiaobaoqiu.github.io/blog/2015/07/02/ratelimiter/
4. 《亿级流量网站架构核心技术》
为什么需要限流
我们都知道,构建高并发的系统有三大利器:缓存、降级、限流。通过使用缓存,可以让用户在获取数据链路的过程变的更短、获取数据的速度变得更快,从而提升系统的吞吐量,通过使用降级手短,可以把非核心业务的资源用来保证核心业务的正常服务。然而有些场景并不能用缓存和降级来解决,比如稀缺资源的秒杀、抢购、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发或请求量,即限流。通过限流,可以保证系统处理的流量始终在系统自身能力范围之内,最终做到有损服务而非不服务。
常见的几种限流算法简介
目前常用的限流算法有以下几种,每种算法都各有优缺点:
一、简单计数法
简单计数法是限流算法里面最简单粗暴的一种算法,其算法的核心思想就是维护一个计数器,这个计数器有一个时间窗口,当达到这个时间窗口时计数器会归零。每当一个新请求到来的时候,都让计数器自增,当计数器自增达到设置的上限时,不再提供服务。
简单计数法正如其名,实现起来非常简单,这是它的最大的优点,但太简单粗暴也会导致一些 小的问题,比如无法预防短时间突然的流量高峰。我们举个例子来说,有个api,我们希望这个api每秒钟最多处理的请求数量是1000,即限制它的qps为1000,那么使用简单计数法有什么问题呢?参考下面的图片,在第1秒的前999毫秒内都没有任何请求,但第1秒的最后1毫秒,突然来了1000个请求,这个时候是没有问题的。紧接着,计数器达到了时间窗口,计数归零了,第2秒的第1毫秒,又来了1000个请求,相当于两毫秒内允许api处理了2000个请求,和我们约定的qps为1000有很大的差距。所以,鉴于以上的问题,简单计数可以应用于一些请求相对均匀,对限制要求不是很准确的场景。
二、滑动窗口算法(rolling window)
滑动窗口算法把时间划分为一个一个的窗口“格子”,记录每个格子的请求数量,每当时间经过了一个格子时向后滑动时间窗格并统计所有时间窗格的请求数量这样的方式来计数。具体的算法思想我们使用两张图来说明。沿用上面的例子,有个api,我们希望这个api每秒钟最多处理的请求数量是1000,即限制它的qps为1000。那么我们把1秒钟分成10个时间窗口格子,那么每个格子就是100ms,每当请求到来时,把当前时间对应格子上的请求数量加1,并计算当前时间格子和之前9个格子的请求数量之和是否超过上限。如下图:可以看到,当前的请求在第10个格子上,新请求过来时,我们去计算当前时间格子的请求数量和前面9个格子的请求数量之和,check是否大于1000,大于的话,则拒绝新的请求。
正是因为有之前格子计数的“积累”,所以当同样面临简单计数算法的时间边界情况时,请求在第二秒的初始也不会得到允许,如下图所示。
滑动窗口算法保证了在限流的任意时间段之内,请求数量都不会超过设置的上限,所以滑动窗口算法适用于对限流的准确性有一定要求的场景。
三、漏桶算法(leaky bucket)
漏桶的算法十分简单。只需要满足以下的规则:
1. 有一个固定容量的桶,有水流进来,也有水流出去。对于流出去的水,必须按照一个设定的速率。
2. 当桶里的水已经流空的时候,不需要再流出水。
3. 对于流入水的速率不做限制,如果流入的水速率大于流出的水,则当桶里的水达到了桶的容量时,多余的水被直接丢弃。
对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。将算法中的水换成实际应用中的请求,就是漏桶的算法。由于漏桶算法天生就限制了请求的速度,当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法也不会出现简单计数那样的临界问题,非常适合于一些流量整形(Traffic Shaping)和流量控制(TrafficPolicing)等严格要求请求速率的场景。
四、令牌桶算法(token bucket)
令牌桶算法也非常简单,有一个容量固定的桶,以一个恒定的速率产生令牌,如果桶内的令牌满了则多余的令牌会被丢弃。每当请求进来时,先去桶内拿一个令牌,桶内的令牌拿完了,则必须等待桶内产生令牌才能允许后续的请求(或者直接拒绝)。由于桶内可以堆积一定的令牌(一般为桶的容量),所以令牌桶算法比漏桶算法有个优点就是令牌桶算法可以允许一定量的流量高峰。
令牌桶算法虽然也容许一定量的流量突发,但是和简单计数的边界流量高峰还是有区别的。简单计数法是在每次计数器时间边界的瞬间都可能允许有两倍流量,而令牌桶算法由于有桶里令牌的积累,能允许一定量的流量突发,之后再有更多的请求,都必须等待令牌生成,等待令牌生成期间的所有请求要么阻塞到拿到令牌要么就失败,令牌桶在极端情况下也会有两倍流量的可能,使用令牌桶算法限流的时候需要考虑到这种情况,当业务上非常依赖限流准确性的时候不可使用令牌桶算法,我们通过图示来说明:
当一段时间内没有请求(或者请求速度小于令牌产生速度)时,桶里的令牌逐渐又积累起来,又会允许0秒时刻那样的流量突发:
只要理解了两点,一,产生令牌的速度恒定。二,没有令牌不得访问。就能理解令牌桶算法的工作原理。
“令牌桶算法”和“漏桶算法”的主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的上限,因此它适合于具有突发特性的流量。考虑到限流只是对服务/资源的一种保护,而非严格的业务需求,所以51采用了令牌桶算法来实现限流。具体的业务需要使用什么限流算法还要具体分析,当业务量非常大,系统可用性很高的时候使用简单计数也未尝不可。
应用级限流及分布式限流
应用级限流是指每个服务自己保证自己的资源以一个合理的限度使用,因为每个服务总有一个极限的请求并发数(QPS/TPS阈值),如果超过了这个上限,服务就会变得无响应或者响应很慢,从而不能很好的提供服务,通过限流,使服务的处理能力在上限之内,则服务就可以继续提供服务了。应用级限流对于保护单个服务节点是非常有效的,但是应用级限流不能保护服务集群背后的资源,比如控制对DB资源的访问,这种情况就需要使用分布式限流,保证一个服务集群总的请求量在设定的上限之内。
分布式限流的实现思路一般有两个:
第一种思路是把限流的算法应用到Redis/Memcached上,这样所有集群内的机器使用的都是一个单点的计数器,从而来实现分布式限流。例如使用Redis + Lua脚本实现,Redis集群作为计数的存储设备,Luau脚本来实现具体的算法。这里多说一句Redis + Lua这种思路,Redis虽然是单线程的,无需担心命令执行的线程安全问题,但是由于限流算法的复杂性,实现一次限流判断,需要多次和Redis之间交互,一次限流判断过程中的多次Redis交互有可能构成“先检查后执行(Check-Then-Act)”操作,这样的操作会导致Redis后面的行为可能基于一次失效的数据,从而导致限流算法发生问题,根据墨菲定律,当并发数量达到一定程度的时候,这样的问题一定会发生。而且多次的Redis交互之间也占用了额外的网络IO请求时间,所以解决这个问题需要使用Lua脚本,把限流的算法用Lua脚本实现,然后就可以一次执行Lua脚本就得到限流结果。Lua脚本在Redis执行的过程中不会被其他的操作打断,没有了“先检查后执行”问题,但也要注意Lua脚本不可过于复杂,如果因Lua脚本写的不正确有可能导致整个Redis集群夯住,从而引发悲剧事故。还有一点需要注意,Lua脚本无法从Redis获取系统时间(注2)。前面介绍的一些限流常见算法中,除了简单计数法,其他的算法的一般实现都需要获得一个系统时间,可以先通过Redis的Time命令获取时间,然后传入Lua脚本中这样的方式来实现。
下面附一个简单计数算法的Lua实现:
-- KEYS[1] key: 限流的redis key,外部计算传入-- ARGV[1] limit: 最大访问流量/数量限制-- ARGV[2] dur: 限流的总时长,单位毫秒-- ARGV[3] permits: 请求的流量,如果限制数量,则为1local key = KEYS[1]local limit = tonumber(ARGV[1])local dur = ARGV[2]local permits = tonumber(ARGV[3])local current = tonumber(redis.pcall('GET', key) or '0')if current + permits > limit then return 0else if (current == 0) then redis.pcall('SET', key, 0) redis.pcall('PEXPIRE', key, dur) end redis.pcall('INCRBY', key, permits) return 1end
实现分布式限流的另外一种思路就是通过把整个集群的限流数量均匀分摊到集群内的每一个机器上面,通过每个节点各自进行应用级别的限流来模拟整个集群的限流。这种思路的实现需要满足两个条件,一是集群内的每个节点承担的流量是均衡的,节点之间如果流量不均衡,则会导致流量高的节点很容易就触发分给自己的上限,因为每个节点的上限是通过集群上限除以节点数量得出的。二是当集群内的节点数量发生变化的时候,要实时去更新每个节点的流量上限,否则一旦有机器的增加,可能会出现导致无法保护后端资源的问题发生。
比较一下应用级限流和分布式限流的优缺点,可以发现这两种限流方式各有优缺点。应用级限流对应用节点自身保护,好处是当应用的能力不足以提供服务时,可以随时进行扩容,而无需考虑扩容带来的影响。而分布式限流优点是可以做到对应用后端的资源进行保护,无论是采用单点的存储方式还是使用节点模拟来实现分布式限流,都需要额外的操作来保证限流的正确性。具体使用哪种限流方式,还是要看具体的业务场景。
51的限流实践
51信用卡实行的是微服务架构实践,每个DC内的微服务调用使用的是RoundRobin的负载均衡策略,所以可以认为每个服务的流量都是均衡的。另外每个服务在启动的时候会把服务的信息注册到Consul集群(一个高可用、支持跨DC的服务发现与KV存储系统),通过Consul可以获取每个服务部署的节点数量。基于以上的两点,所以51的分布式限流采用的使用节点来模拟集群的方式,另外也对应用级的限流做了支持,可以使用服务治理后台来实时调整,对单个服务所做的调整会实时下发到这个服务的每个实例。对单个节点我们直接使用了谷歌Guava中的RateLimiter类来承担限流功能,RateLimiter是谷歌对令牌桶限流算法的一个比较成熟的轻量级实现,其原理是利用系统时间来维护一个“下次令牌产生时间”,每次请求时,通过计算当前时间和“下次令牌产生时间”的差距可以计算出来当前桶里面有多少个可用令牌,当桶里还有可用令牌时,请求可以继续,并把使用同步的方法把令牌桶里面的令牌数量减一,当桶里没有令牌时,Guava也提供了两种处理方案:阻塞直到产生令牌或等待指定的超时时间后拒绝请求。另外Guava还提供了一个名为setRate()方法,来允许你随时安全地调整限制的速度,根据前面分布式限流与应用级限流中提到的“当集群内的节点数量发生变化的时候,要实时去更新每个节点的流量上限”,实在是太贴心了。
限流的配置包含三个维度,针对api的调用限制、针对RPC的调用限制、还有针对服务内某个具体的method的调用限制,分别对应配置文件的service、client、method节点,当配置的对象是api时,还可以更详细的配置当前的api对某个具体的服务限制调用多少(即下面的by-service),配置的yml结构是这样设计的:
这是接口限流部分对应的后台管理界面:
当服务出现符合配置的条件时,使用pattern作为Key,去本地缓存的中获取key对应的RateLimiter对象,没有则新创建一个,如果当期配置的是分布式限流,则需要去服务注册发现中心获取服务的节点数量,和内存中缓存的节点数量不一致时矫正单台节点的限流数量。为了提高系统的可用性,并没有每次请求都去服务注册发现中心获取,这个过程在另外的线程中定时执行,使用的时候其实用的是一份本地的可用节点数量的快照。有了RateLimiter对象,就可以直接使用Guava为我们提供的方法了,这部分就不再赘述。
参考文章:
1. http://www.kissyu.org/2016/08/13/限流算法总结/
2. https://redis.io/commands/eval
3. http://xiaobaoqiu.github.io/blog/2015/07/02/ratelimiter/
4. 《亿级流量网站架构核心技术》
相关推荐
《51信用卡管家APP产品需求文档》 51信用卡管家是一款专注于个人财务管理的应用,它集成了账单管理、还款服务、借贷以及投资理财等多种功能,旨在帮助用户更好地管理信用卡、优化财务状况。本文档旨在详细阐述该...
《51信用卡Android架构演进实践》 51信用卡在Android架构的发展历程中,面对业务的迅速扩张,原有的单一工程开发模式逐渐无法满足需求。为了应对这一挑战,他们早在两年前就开始构建一套中型项目的开发模式,将公用...
在51信用卡的实践中,他们经历了从最初的纯Java框架到基于关键字驱动和数据驱动开发框架的演变。早期框架包括三层结构:测试脚本、Java Bean以及工具层,工具层包含了针对不同技术如MySQL、HTTP和Redis的Util类,...
《新浪&51信用卡:2018年信用卡行业报告》是针对中国信用卡市场在2018年度的一份综合研究报告。这份报告由新浪与51信用卡两家知名机构联合发布,旨在深度剖析当年信用卡行业的整体状况、发展趋势以及面临的挑战。 ...
在探讨微服务架构下的监控平台架构实践时,51信用卡公司的实践案例为我们提供了宝贵的参考。微服务架构作为一种流行的软件架构方式,旨在通过将复杂的系统拆分成小的、独立的服务来提高系统的可维护性和扩展性。但...
在互联网行业的快速迭代中,51信用卡的成功上市不仅代表着金融科技创新的力量,也揭示了一支高效技术团队的成长历程。本文将从技术团队建设、技术创新、业务发展与风控策略等多个角度,深度剖析51信用卡背后的科技...
"Environmental, Social and Governance Report"是关于51信用卡在环保、社会和治理方面的实践与绩效的报告。这通常涉及公司的可持续性策略,如节能减排、员工福利、社区参与、反腐败政策等,体现了公司在履行社会...
51信用卡是一家提供个人信用评估和信贷服务的科技金融公司,风控系统是其中的核心组成部分,它通过一系列的风险评估规则和技术手段对信贷业务中的风险进行管理与控制。张泽鹏是51信用卡的架构师,负责相关金融信贷...
5. **免息还款期**:信用卡的免息还款期通常在21天到51天之间,具体取决于账单日和最后还款日。 6. **临时信用额度**:当有临时性资金需求时,持卡人可以申请提高临时信用额度。 7. **最后还款日**:账单日后的一...
《51信用卡在微服务架构下的监控平台架构实践》-杨帆.pdf 《AI大数据时代电商攻防:AI对抗AI》-苏志刚.pdf 《Apache Pulsar--实时数据处理中消息 计算和存储的统一》--翟佳.pdf 《Buck在大规模iOS开发中的应用实践》...
本文将深入探讨如何使用51单片机与RC522射频卡模块进行通信,以实现射频卡的读写功能。 首先,51单片机是Intel公司8051系列的CISC(复杂指令集计算)微处理器的典型代表,它包含了CPU、内存、定时器/计数器、串行和...
【51单片机C语言实验及实践教程_32.电子密码锁设计】这篇教程主要涉及了基于51单片机的电子密码锁的设计与实现。以下是详细的知识点解析: 1. **51单片机**:51系列单片机是Intel公司开发的一类8位微控制器,因其...
《MCS-51单片机的实践与应用》是一本深入浅出的教程,旨在帮助初学者理解和掌握51单片机的使用。MCS-51,又称8051,是微控制器领域中广泛应用的一种单片机,由英特尔公司推出,因其强大的处理能力和广泛的硬件支持而...
本书详细介绍了V9.00版本的Keil C51编译器和Vision4的强大功能和具体使用方法,完整地介绍了最新版本C51编译器控制命令,给出了全部C51运行库函数及其应用范例,对Keil C51软件包中各种应用工具都作了详细介绍,阐述...
《51单片机C语言实验及实践》是一份丰富的学习资源,专为那些希望深入理解和应用51系列单片机的C语言编程者而设计。这个资源包含了超过三十个精心设计的实验案例,旨在帮助学习者从理论到实践全方位掌握51单片机的...
总的来说,51单片机软件模拟SPI通信读写SD卡模块涉及了硬件接口设计、通信协议理解、软件编程技巧等多个方面,对于学习嵌入式系统和单片机应用开发的工程师来说,这是一个很好的实践项目。通过这样的实践,可以提升...