令牌桶概念令牌桶是一种用来控制请求速率的算法。可以限制一定时间内可以提交的请求数量,避免超过系统的处理能力。令牌桶算法是基于一个抽象的“令牌桶”,其中可以存储一定数量的令牌。在每个时间单位内,新的令牌以一定的速率添加到桶中。如果一个请求需要被处理,它需要从桶中消耗一定数量的令牌。如果桶中没有足够的令牌,请求将被拒绝。令牌桶算法的优点是可以根据当前系统负载动态调整请求处理速率,可以控制请求速率和延迟。优点和缺点可能会导致请求延迟。如果请求率很高,可能会导致桶中的令牌用完,导致无法处理新的请求。可以避免大量请求涌入导致资源耗尽,并根据实际情况动态调整请求处理速率。核心参数分析:桶容量:表示桶中最多可以存放多少个令牌。令牌添加率:在每个时间单位(1s)内可以添加到令牌桶中的令牌数量。Thenumberoftokensrequiredforeachrequest:表示每次请求需要消耗的token数量,一般默认为1。token添加率的实现如下:维护一个时间戳记录上次添加token的时间,从而计算出处理请求时的token添加率。综上所述,可以进行代码设计:publicclassRedisRateLimiterReq{/***限流唯一标识*/@NotBlankprivateStringid;/***代币添加率*/@Min(1)privateintreplenishRate;/***桶容量*/@Min(0)privateintburstCapacity=1;/***每次请求需要的token个数*/@Min(1)privateintrequestedTokens=1;}基于Redis+lua分发的Token桶限流rediskey设计:Key[1]:记录剩余容量bucketKey[2]:记录bucket最后一次刷新时间,计算当前需要填充的token个数。第一次:需要填入新的token个数=(当前时间-0)*rate其他:需要新填入的token个数=(当前时间-Key[2])*rate总结一下:当前桶中可用令牌数=桶的剩余容量+需要新填充的令牌数.rate:tokenaddingrate:每个时间单位(1s)内可以添加到令牌桶中的令牌数量。requested:Thenumberoftokensrequiredforeachrequest:表示每次请求需要消耗的token数量,一般默认为1。核心公式:fill_time:填充时间:capacity/rate,例如10/2,即每秒填充5个令牌。ttl:rediskey[1]和key[2]的过期时间,填充时间*2;为什么是2次:这样可以保证令牌桶中的令牌能够得到充分利用,避免过早过期。例如,如果填充时间值为10秒,则过期时间值应设置为20秒。这样,在令牌桶的生命周期内,用户有足够的时间使用令牌桶中的令牌。LUA脚本redis.replicate_commands()--记录桶的剩余容量localtokens_key=KEYS[1]--记录桶上次刷新的时间,从而计算需要填充的token个数atpresent--第一次:需要新填入的token数量=(当前时间-0)*rate--之后:需要新填入的token数量=(当前时间-Key[2])*ratelocaltimestamp_key=KEYS[2]--总结:**当前桶中可用令牌数=桶剩余容量+需要新填充的令牌数**redis.log(redis.LOG_WARNING,"tokens_key"..tokens_key)localrate=tonumber(ARGV[1])localcapacity=tonumber(ARGV[2])localnow=redis.call('TIME')[1]localrequested=tonumber(ARGV[4])localfill_time=capacity/rate--rediskey[1]、key[2]Expirationtime--token过期时间:fillingtime*2--返回小于参数x的最大整数--这个可以e保证令牌桶中的令牌能被充分利用,避免过早失效。--例如,如果填充时间值为10秒,则过期时间值应设置为20秒。这样,在令牌桶的生命周期内,用户有足够的时间使用令牌桶中的令牌。localttl=math.floor(fill_time*2)redis.log(redis.LOG_WARNING,"rate"..ARGV[1])redis.log(redis.LOG_WARNING,"capacity"..ARGV[2])redis.log(redis.LOG_WARNING,"now"..now)redis.log(redis.LOG_WARNING,"requested"..ARGV[4])redis.log(redis.LOG_WARNING,"filltime"..fill_time)redis.log(redis.LOG_WARNING,"ttl"..ttl)locallast_tokens=tonumber(redis.call("get",tokens_key))iflast_tokens==nilthenlast_tokens=capacityendredis.log(redis.LOG_WARNING,"last_tokens"..last_tokens)locallast_refreshed=tonumber(redis.call("get",timestamp_key))iflast_refreshed==nilthenlast_refreshed=0endredis.log(redis.LOG_WARNING,"last_refreshed"..last_refreshed)localdelta=math.max(0,now-last_refreshed)localfilled_tokens=math.min(capacity,last_tokens+(delta*rate))localallowed=filled_tokens>=requestedlocalnew_tokens=filled_tokenslocalallowed_num=0ifallowedthennew_tokens=filled_tokens-requestedallowed_num=1end--redis.log(redis.LOG_WARNING,"delta"..delta)--redis.log(redis.LOG_WARNING,"filled_tokens"..filled_tokens)--redis.log(redis.LOG_WARNING,"allowed_num"..allowed_num)--redis.log(redis.LOG_WARNING,"new_tokens"..new_tokens)ifttl>0thenredis.call("setex",tokens_key,ttl,new_tokens)redis.call("setex",timestamp_key,ttl,now)end--return{allowed_num,new_tokens,capacity,filled_tokens,requested,new_tokens}return{allowed_num,new_tokens}redis.replicate_commands()是Redis客户端的一个方法,用于启用命令复制命令Replication引用在多个Redis实例之间复制命令以确保数据一致性。例如,如果您在Redis集群中执行写入命令,该命令将被复制到集群中的其他实例。这样可以保证集群中的所有实例存储相同的数据,提供高可用性和数据安全性。
