分布式接口幂等性、分布式限流(Guava 、nginx和lua限流)

本文深入探讨了接口幂等性的概念及其重要性,通过版本号机制和Token确保Update、Insert操作的幂等性。接着,分析了分布式限流的多种维度,如时间、资源限制,并详细介绍了Guava RateLimiter、Nginx以及基于Redis+Lua的限流解决方案,包括各自的优缺点和实现细节。

一、接口幂等性

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性。

幂等性的核心思想:通过唯一的业务单号保障幂等性,非并发的情况下,查询业务单号有没有操作过,没有则执行操作,并发情况下,这个操作过程需要加锁。

1、Update操作的幂等性

1)根据唯一业务号去更新数据

通过版本号的方式,来控制update的操作的幂等性,用户查询出要修改的数据,系统将数据返回给页面,将数据版本号放入隐藏域,用户修改数据,点击提交,将版本号一同提交给后台,后台使用版本号作为更新条件

update set version = version +1 ,xxx=${xxx} where id =xxx and version = ${version};

2、使用Token机制,保证update、insert操作的幂等性

1)没有唯一业务号的update与insert操作

进入到注册页时,后台统一生成Token, 返回前台隐藏域中,用户在页面点击提交时,将Token一同传入后台,使用Token获取分布式锁,完成Insert操作,执行成功后,不释放锁,等待过期自动释放。

二、分布式限流

1、分布式限流的几种维度

时间 限流基于某段时间范围或者某个时间点,也就是我们常说的“时间窗口”,比如对每分钟、每秒钟的时间窗口做限定 资源 基于可用资源的限制,比如设定最大访问次数,或最高可用连接数

上面两个维度结合起来看,限流就是在某个时间窗口对资源访问做限制,比如设定每秒最多100个访问请求。但在真正的场景里,我们不止设置一种限流规则,而是会设置多个限流规则共同作用,主要的几种限流规则如下:

1)QPS和连接数控制

针对上图中的连接数和QPS(query per second)限流来说,我们可以设定IP维度的限流,也可以设置基于单个服务器的限流。在真实环境中通常会设置多个维度的限流规则,比如设定同一个IP每秒访问频率小于10,连接数小于5,再设定每台机器QPS最高1000,连接数最大保持200。

更进一步,我们可以把某个服务器组或整个机房的服务器当做一个整体,设置更high-level的限流规则,这些所有限流规则都会共同作用于流量控制。

2)传输速率

对于“传输速率”大家都不会陌生,比如资源的下载速度。有的网站在这方面的限流逻辑做的更细致,比如普通注册用户下载速度为100k/s,购买会员后是10M/s,这背后就是基于用户组或者用户标签的限流逻辑。

3)黑白名单

黑白名单是各个大型企业应用里很常见的限流和放行手段,而且黑白名单往往是动态变化的。举个例子,如果某个IP在一段时间的访问次数过于频繁,被系统识别为机器人用户或流量攻击,那么这个IP就会被加入到黑名单,从而限制其对系统资源的访问,这就是我们俗称的“封IP”。

我们平时见到的爬虫程序,比如说爬知乎上的美女图片,或者爬券商系统的股票分时信息,这类爬虫程序都必须实现更换IP的功能,以防被加入黑名单。有时我们还会发现公司的网络无法访问12306这类大型公共网站,这也是因为某些公司的出网IP是同一个地址,因此在访问量过高的情况下,这个IP地址就被对方系统识别,进而被添加到了黑名单。使用家庭宽带的同学们应该知道,大部分网络运营商都会将用户分配到不同出网IP段,或者时不时动态更换用户的IP地址。

白名单就更好理解了,相当于御赐金牌在身,可以自由穿梭在各种限流规则里,畅行无阻。比如某些电商公司会将超大卖家的账号加入白名单,因为这类卖家往往有自己的一套运维系统,需要对接公司的IT系统做大量的商品发布、补货等等操作。

4)分布式环境

所谓的分布式限流,其实道理很简单,一句话就可以解释清楚。分布式区别于单机限流的场景,它把整个分布式环境中所有服务器当做一个整体来考量。比如说针对IP的限流,我们限制了1个IP每秒最多10个访问,不管来自这个IP的请求落在了哪台机器上,只要是访问了集群中的服务节点,那么都会受到限流规则的制约。

从上面的例子不难看出,我们必须将限流信息保存在一个“中心化”的组件上,这样它就可以获取到集群中所有机器的访问状态,目前有两个比较主流的限流方案:

网关层限流

  • 将限流规则应用在所有流量的入口处

中间件限流

  • 将限流信息存储在分布式环境中某个中间件里(比如Redis缓存),每个组件都可以从这里获取到当前时刻的流量统计,从而决定是拒绝服务还是放行流量

2、限流方案常用算法讲解

1)令牌桶算法

Token Bucket令牌桶算法是目前应用最为广泛的限流算法,顾名思义,它有以下两个关键角色:

  • 令牌 获取到令牌的Request才会被处理,其他Requests要么排队要么被直接丢弃
  • 桶 用来装令牌的地方,所有Request都从这个桶里面获取令牌

分布式接口幂等性、分布式限流(Guava 、nginx和lua限流)

### Lua限流算法实现与库 在Lua中实现限流的方法通常依赖于外部存储(如Redis)来维护分布式环境下的状态信息。以下是一些常见的Lua限流实现方法库。 #### 1. 使用令牌桶算法 令牌桶算法是一种常用的限流算法,其核心思想是通过固定速率生成令牌并存入桶中,请求只有在获取到令牌时才能被处理。以下是一个基于LuaRedis的令牌桶算法实现: ```lua local redis = require "redis" local function token_bucket_limit(redis_client, key, rate, capacity) local current_time = tonumber(redis_client:time()[1]) local bucket = redis_client:hgetall(key) local tokens = tonumber(bucket["tokens"]) or capacity local last_refill_time = tonumber(bucket["last_refill_time"]) or current_time -- 计算从上次填充到现在可以生成多少令牌 local time_passed = current_time - last_refill_time tokens = math.min(capacity, tokens + time_passed * rate) -- 更新桶的状态 if tokens >= 1 then tokens = tokens - 1 redis_client:hset(key, "tokens", tokens) redis_client:hset(key, "last_refill_time", current_time) return true else return false end end -- 示例调用 local redis_client = redis.connect("127.0.0.1", 6379) local is_allowed = token_bucket_limit(redis_client, "rate_limit_key", 0.5, 10) -- 每秒生成0.5个令牌,桶容量为10 if is_allowed then print("Request allowed") else print("Request denied") end ``` 上述代码实现了基于Redis的令牌桶限流逻辑[^1]。 #### 2. 基于Nginx Lua模块的限流 Nginx Lua模块结合Redis可以实现高效的限流功能。`Nginx Lua Redis Rate Measuring` 是一个开源项目,它利用Lua脚本在Nginx内部实现了访问速率的测量及限制功能[^3]。以下是使用Nginx Lua模块的一个简单示例: ```lua local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) -- 1 second local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.say("failed to connect: ", err) return end local key = "rate_limit:" .. ngx.var.remote_addr local limit = 10 -- 每分钟最多10次请求 local interval = 60 -- 时间间隔(秒) local current = red:get(key) if not current then red:set(key, 1, "EX", interval) else current = tonumber(current) if current < limit then red:incrby(key, 1) else ngx.exit(503) -- 超过限流返回503错误 end end red:close() ``` 此代码片段展示了如何使用Nginx Lua模块Redis来限制每分钟的请求数量[^2]。 #### 3. 使用Lua库 `lua-resty-limit-traffic` `lua-resty-limit-traffic` 是一个专门用于Nginx Lua模块的限流库,支持多种限流算法(如漏桶算法、令牌桶算法)。以下是一个简单的示例: ```lua local limiter = require "resty.limit.traffic" local limit, err = limiter.new("my_rate_limit_store", 10, 1) -- 每秒最多10个请求 if not limit then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.traffic object: ", err) return ngx.exit(500) end local key = ngx.var.binary_remote_addr local delay, err = limit:incoming(key, true) if not delay then if err == "rejected" then return ngx.exit(503) end ngx.log(ngx.ERR, "failed to limit traffic: ", err) return ngx.exit(500) end ``` 该库提供了灵活的接口,允许开发者轻松实现复杂的限流策略[^3]。 #### 4. Guava平滑突发限流Lua实现 虽然Guava的平滑突发限流算法通常是用Java实现的,但可以用Lua模拟其行为。以下是一个简单的平滑突发限流示例: ```lua local function smooth_bursty_limit(rate, burst_tokens) local state = {tokens = burst_tokens, last_update = os.time()} local function acquire(tokens_to_acquire) local now = os.time() local elapsed = now - state.last_update state.tokens = math.min(burst_tokens, state.tokens + elapsed * rate) state.last_update = now if state.tokens >= tokens_to_acquire then state.tokens = state.tokens - tokens_to_acquire return true else return false end end return acquire end local acquire = smooth_bursty_limit(5, 10) -- 每秒生成5个令牌,初始桶容量为10 for i = 1, 10 do if acquire(1) then print("Acquired token") else print("Token acquisition failed") end end ``` 此代码实现了类似于Guava平滑突发限流的行为[^4]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值