当前位置: 首页 > news >正文

网站建设公司好吗建设网站的功能及目的是什么

网站建设公司好吗,建设网站的功能及目的是什么,怎么做投资网站不违法,小企业管理软件排名前言 Redis除了能用作缓存外#xff0c;还有很多其他用途#xff0c;比如分布式锁#xff0c;分布式限流#xff0c;分布式唯一主键等#xff0c;本文将和大家分享下基于Redis分布式限流的各种实现方案。 一、为什么需要限流 用最简单的话来说#xff1a;外部请求是不可…前言 Redis除了能用作缓存外还有很多其他用途比如分布式锁分布式限流分布式唯一主键等本文将和大家分享下基于Redis分布式限流的各种实现方案。 一、为什么需要限流 用最简单的话来说外部请求是不可控的而我们系统的负载是有限的如果没有限流机制一旦外部请求超过系统承载的压力就会出现系统宕机等严重问题。加入限流正是为了保证系统负载在可以承受的范围内。 比如春节的秒杀环节。我们在上线前预估了能应对的秒杀 qps 是 1kw/s但是实际可能达到了1亿/s这种情况下这多出来的9kw请求很可能压垮我们的数据库进而影响到接下来所有的用户正常访问。 补充 微服务保证稳定性的几个利器缓存、熔断、降级、限流。 缓存的目的是为了降低系统的访问延迟提高系统能力给用户更好的体验熔断的目的是为了在发现某个服务故障熔断对下游依赖的请求减少不必要的损耗降级的目的是为了在系统在某个环节故障比如某个下游故障不影响整体核心链路比如返回作者列表关注服务故障了获取不了关注真实的关注情况这种情况可以考虑降级关注按钮全部显示为未关注限流的目的是为了保证系统处理的请求量在可以承受的范围内防止突发流量压垮系统保证系统稳定性。 二、单机限流和分布式限流 1、单机限流的瓶颈 单机限流有个主要的缺陷就是不够精确我们可能有1000个实例但是下游存储只有一套即多对一的关系如果单纯的以单机限流作为衡量指标很可能把下游打挂。如果以下游数据库的总请求量均衡到每台机器上由于每个机器请求数据库量级可能不同导致部分机器被限流严重而部分机器压根没什么请求造成误伤。 即不好根据下游数据库服务的负载压力来评估上游应用服务器每台的负载压力是多少。 采用根据总负载来均摊负载的方式显然并不精确并且不能充分发挥上游服务器的处理性能极大的限制了系统的负载能力。 比如数据库负载为limit300由于采用上游采用负载均摊的方式每台应用服务器限制为limit100如果来个固定IP或固定用户的请求一直落在服务器Server1上那么系统的实际并发就变成了100显然这样处理并不科学也降低了单台应用服务器的处理能力。 单机限流也有好处可以根据服务器配置的不同进行不同权重的限流配置。 比如:4核CPU16G内存的服务器上限流为10002核 8G内存的服务器上限流为500. 2、分布式限流 为了解决单机限流的瓶颈需要引入分布式限流即我们不应为每个单机设置限流阈值而是根据下游的负载情况设置一个全局的阈值。目前主流的做法主要是通过 redis 或者 zookeeper 来实现。zookeeper 使用、运维都比较复杂所以大部分是使用 redis lua 脚本来实现。 分布式限流器和单机限流的实现类似只是计数存储换成了一些分布式存储而不是在单机内存中。 我们可以使用 redis lua 脚本实现令牌桶的算法因为 lua 的执行可以做到事务要么全部成功要么全部失败。所以可以很简单地实现分布式的令牌桶逻辑并且可以实现精确的限流。但是这种实现的缺陷是如果请求 qps很高所有的请求都要和redis交互redis 不一定能承载得住。 3、小结 单机限流更适合对单机精确限流比如针对单个mysql数据库的请求限流某低配的应用服务器只能处理100并发的限流。分布式限流更适合分布式场景的总体流控比如知道请求链路中下游的负载能力出现瓶颈那么上游就需要根据下游瓶颈进行整体流量控制如数据库的负载是limit500这时就可以采用分布式限流器来控制落在应用服务器上整体的请求数。 三、4种主流的限流算法 1、固定时间窗口 固定窗口算法又叫计数器算法是一种简单方便的限流算法。主要通过一个支持原子操作的计数器来累计 1 秒内的请求次数当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒计数器重置为 0 开始重新计数。 优点时间窗口固定实现简单性能较高缺点无法应对两个时间窗口临界时间内的突发流量如上图所示我们虽然通过限流器限制了单个时间窗口内只能有2个请求即QPS2但是在两个1秒时间窗口中间1秒的时间窗口内却发生了4次请求。 2、滑动时间窗口 我们已经知道固定窗口算法的实现方式以及它所存在的问题而滑动窗口算法是对固定窗口算法的改进。既然固定窗口算法在遇到时间窗口的临界突变时会有问题那么我们在遇到下一个时间窗口前也调整时间窗口不就可以了吗 下面是滑动窗口的示意图: 上图的示例中每 500ms 滑动一次窗口可以发现窗口滑动的间隔越短时间窗口的临界突变问题发生的概率也就越小不过只要有时间窗口的存在还是有可能发生时间窗口的临界突变问题。 那么有没有什么办法更精确的统计时间窗口内的请求数呢 答案是有的就是记录下所有的请求时间点新请求到来时以请求时间作为时间窗口的结尾时间统计时间窗口范围内的请求数量是否超过指定阈值由此来确定是否达到限流这种方式没有了时间窗口突变的问题限流比较准确但是因为要记录下每次请求的时间点所以占用的内存较多。 该方法又叫做滑动日志算法。不过其算法的本质还是滑动时间窗口那套区别在于滑动时间窗口是固定时间间隔滑动时间窗口而滑动日志算法由于保存了每个请求的时间戳可以根据最新请求的时间戳计算出当前时间窗口内的请求数。 优点优化了固定时间窗口的临界问题限流更加精准缺点实现较为复杂会占用较多内存每个请求都需要重新统计最新时间窗口内的请求数性能较低。 3、漏桶 漏桶算法中将限流器比作一个漏斗每一个请求到来就会向桶中添加一定的水量桶底有一个孔以恒定速度不断的漏出水当一个请求过来需要向加水时如果漏桶剩余容积不足以容纳添加的水量就会触发拒绝策略。 假设限流要求是每分钟允许30个请求可以将漏桶的容积看作30每次请求加水量为1漏桶中水的流出速度为30/时间周期1分钟。 当出现最大30个并发时漏桶会被瞬间注满水后续请求都会被拒绝。只有随着水量以固定速度流出后漏斗中有剩余空间容纳新的水量系统才能接受的新的请求。 注意 1、漏桶算法模型中并没有队列的概念每个请求到来时向漏斗中加水并不是加入队列等待被消费所以漏斗并不能像消息队列那样削峰填谷、缓解突发的请求压力限流器只负责判断当前请求是被允许还是需要拒绝。只要容器中能继续加水请求就被允许否则拒绝请求。 2、漏桶中水的流出速度并不等于请求的并发量往漏桶中加水的速度才是当前系统的实际并发量。水的流出可以等同于令牌桶算法中向桶中投放令牌水流出的速度越快漏桶中就越快腾出空余空间用来存放新的请求到来需要添加的水量。所以不要简单认为水的流出速度恒定就能控制当前系统的并发量保持均衡两者并不是一个概念。 3、桶的容积决定了限流器允许的最大并发。当漏桶中没有水时允许出现最大的并发流量。 很多地方对漏桶的说法都是可以缓冲请求对此我有不同的看法所有的限流器不管是采用何种方法实现都仅仅只是做请求是否被允许的判定器请求要么被允许通过要么被直接拒绝其本身并不提供缓冲请求的功能。漏桶模型中的桶的容量并不会像消息队列那样缓冲请求不会存放真实的请求信息等待被处理消费。 优点能够起到一定的平滑突发流量的作用。水的恒定流出速度可以等价于固定速度的投放令牌不会出现一下子投放大量令牌立刻被抢空导致突发流量的问题。 缺点 1、资源利用率低漏桶并不能高效地利用可用的资源。因为它只在固定的时间间隔放行请求所以在很多情况下流量非常低即使不存在资源争用也无法有效地消耗资源。 2、饥饿问题当短时间内有大量突发请求即使服务器没有任何负载由于漏桶中的水还没有流出请求会大量被拒绝。 3、实现复杂性能较低会占用较多内存 4、令牌桶 漏桶是看处理效率和生产效率来控制流速但是这个流速是静态的很可能无法充分利用机器的性能。比如服务器能处理的速率是100qps但是我们配置的恒定流速只有50qps这个时候服务器资源还非常地冗余。 令牌桶算法能比较灵活的调整以最大化利用资源系统每接受到一个请求时都要求有一个令牌如果拿到令牌就处理否则就拒绝处理完以后把令牌丢弃。 桶中能存放的最大令牌数决定了令牌桶算法的最大并发当桶中放满令牌时允许达到最大并发。 令牌桶限流算法的核心就在于如何控制令牌的发放策略。这样可以做的很动态例如利用系统的负载、业务高峰情况等高峰时且负载允许加快令牌发放。 从本质上来说令牌桶中以恒定速度生成令牌和漏桶算法中以恒定速度控制水的流出速度是等同的都可以实现相同的限流效果。 漏桶 vs 令牌桶 网上的大多数说法 令牌桶相比漏桶会更加灵活可以根据业务诉求配置灵活的发放策略。比如可以任意时间放入令牌或者依据机器负载放入多个令牌但是漏桶的整形效果会更好对下游服务会更加友好因为不容易出现突刺。 个人理解 两者本质上只是模型不同但都能实现相同的限流效果。令牌桶能调整令牌的发放策略漏桶也可以改进水流出的速率。只是我们一般的默认理解是令牌桶是定时投放一定数量的令牌而漏桶中的水是恒定速度流出这样的前提下漏桶相较之下更能起到平滑突发流量的作用不会像令牌桶那样出现令牌投放后立刻被抢空的情况而令牌桶则是能更好的适应突发流量。 优点可以适应流量突发N 个请求到来只需要从桶中获取 N 个令牌就可以继续处理。当然在漏桶算法中只要桶中剩余空间够大也能够应付突发的流量。 缺点实现复杂性能较低会占用较多内存 四、基于Redis的Lua分布式限流实战 基于Redis实现分布式限流一般都会采用Lua实现以保证操作的原子性。 1、固定时间窗口 fixedWindow-rateLimit.lua脚本内容 --KEYS[1]: 限流 key --ARGV[1]: 阈值 --ARGV[2]: 时间窗口计数器的过期时间 local rateLimitKey KEYS[1]; local rate tonumber(ARGV[1]); local rateInterval tonumber(ARGV[2]);local allowed 1; -- 每次调用计数器rateLimitKey的值都会加1 local currValue redis.call(incr, rateLimitKey);if (currValue 1) then -- 初次调用时通过给计数器rateLimitKey设置过期时间rateInterval达到固定时间窗口的目的redis.call(expire, rateLimitKey, rateInterval);allowed 1; else -- 当计数器的值固定时间窗口内 大于频度rate时返回0不允许访问if (currValue rate) thenallowed 0;end end return allowed固定时间窗口限流器实现FixedWindowRateLimiter public class FixedWindowRateLimiter implements RateLimiter{private final static String PREFIX fixedWindowRateLimiter;private final RScript rScript;public FixedWindowRateLimiter(RedissonClient client) {//获取RScript时一定要指定LongCodec.INSTANCE不然Lua中获取参数值是可能会失败this.rScript client.getScript(LongCodec.INSTANCE);}Overridepublic boolean isAllowed(Rule rule) {String keys String.format(%s.{%s},PREFIX,rule.getKey());String script LuaScript.getFixedWindowRateLimiterScript();long timeoutInMillis -1L;if (rule.getRateInterval() 0L) {//由于通过expire命令设置过期时长的单位是秒s这里需要将时间转为秒timeoutInMillis rule.getTimeUnit().toSeconds(rule.getRateInterval());}Long result rScript.eval(RScript.Mode.READ_WRITE, script, RScript.ReturnType.VALUE, Collections.singletonList(keys), rule.getRate(), timeoutInMillis);return result 1L;} }单元测试 Testvoid fixedWindowRateLimitServiceTest() throws InterruptedException, ExecutionException {//创建名称为laowan.fixedWindow的固定窗口限流器每60s允许30个请求Rule rule new Rule(laowan.fixedWindow,30,60,TimeUnit.SECONDS, Mode.FIXED_WINDOW);while (true){boolean result rateLimiterService.isAllowed(rule);Thread.sleep(1000L);if(result){log.info(请求正常);}else{log.info(请求太多频繁请稍后再试);}}} 2、滑动时间窗口 slidingWindow-rateLimit.lua内容 --KEYS[1]: 限流器的 key --ARGV[1]: 当前时间窗口的开始时间 --ARGV[2]: 请求的时间戳也作为score --ARGV[3]: 阈值 -- 1. 移除时间窗口之前的数据 redis.call(zremrangeByScore, KEYS[1], 0, ARGV[1]) -- 2. 统计当前元素数量 local res redis.call(zcard, KEYS[1]) -- 3. 是否超过阈值 if (res nil) or (res tonumber(ARGV[3])) then-- 4、保存每个请求的时间搓redis.call(zadd, KEYS[1], ARGV[2], ARGV[2])return 1 elsereturn 0 end滑动窗口限流器实现SlidingWindowRateLimiter public class SlidingWindowRateLimiter implements RateLimiter{private final RScript rScript;private final static String PREFIX slidingWindowRateLimiter;public SlidingWindowRateLimiter(RedissonClient client) {this.rScript client.getScript(LongCodec.INSTANCE);}Overridepublic boolean isAllowed(Rule rule) {String keys String.format(%s.{%s},PREFIX,rule.getKey());String script LuaScript.getSlidingWindowRateLimiterScript();long interval -1L;if (rule.getRateInterval() 0L) {interval rule.getTimeUnit().toMillis(rule.getRateInterval());}//新窗口的结束时间long now System.currentTimeMillis();//新窗口的起始时间long windowStartTime now - interval;Long result rScript.eval(RScript.Mode.READ_WRITE, script, RScript.ReturnType.INTEGER,Collections.singletonList(keys),windowStartTime,now, rule.getRate());return result 1L;} }3、漏斗 leakyBucket-rateLimit.lua内容 --参数说明: --KEYS[1]: 限流器的 key --ARGV[1]: 容量决定最大的并发量 --ARGV[2]: 漏水速率决定平均的并发量 --ARGV[3]: 一次请求的加水量 --ARGV[4]: 时间戳 local limitInfo redis.call(hmget, KEYS[1], capacity, passRate, addWater,water, lastTs) local capacity limitInfo[1] local passRate limitInfo[2] local addWater limitInfo[3] local water limitInfo[4] local lastTs limitInfo[5]--初始化漏斗 if capacity false thencapacity tonumber(ARGV[1])passRate tonumber(ARGV[2])--请求一次所要加的水量,一定不能大于容量值的addWatertonumber(ARGV[3])--当前储水量初始水位一般为0water tonumber(ARGV[1])lastTs tonumber(ARGV[4])redis.call(hmset, KEYS[1], capacity, capacity, passRate, passRate,addWater,addWater,water, water, lastTs, lastTs)return 1 elselocal nowTs tonumber(ARGV[4])--计算距离上一次请求到现在的漏水量 流水速度 * (nowTs - lastTs)local waterPass tonumber(ARGV[2] * (nowTs - lastTs))--计算当前剩余水量 上次水量 - 时间间隔中流失的水量water math.max(tonumber(0), tonumber(water - waterPass))--设置本次请求的时间lastTs nowTs--判断是否可以加水 容量 - 当前水量 增加水量判断剩余容量是否能够容纳增加的水量if capacity - water addWater then-- 加水water water addWater-- 更新增加后的当前水量和时间戳redis.call(hmset, KEYS[1], water, water, lastTs, lastTs)return 1end-- 请求失败return 0 end漏桶实现LeakyBucketRateLimiter public class LeakyBucketRateLimiter implements RateLimiter{private final static String PREFIX leakyBucketRateLimiter;private final RScript rScript;public LeakyBucketRateLimiter(RedissonClient client) {this.rScript client.getScript(LongCodec.INSTANCE);}Overridepublic boolean isAllowed(Rule rule) {String keys String.format(%s.{%s},PREFIX,rule.getKey());long rateInterval -1L;if (rule.getRateInterval() 0L) {rateInterval rule.getTimeUnit().toMillis(rule.getRateInterval());}//容量决定最大并发数long capacity rule.getRate();//水流的速度即容器中的水量在周期时间流完的速度double passRate (double)rule.getRate()/rateInterval;//每次请求增加的水量相当于每次请求获取的令牌数long addWater 1L;//请求时间戳使用毫秒long lastTs System.currentTimeMillis();String script LuaScript.getLeakyBucketRateLimiterScript();Long result rScript.eval(RScript.Mode.READ_WRITE, script, RScript.ReturnType.INTEGER,Collections.singletonList(keys),capacity,passRate,addWater, lastTs);return result 1L;} }4、令牌桶 令牌桶的lua脚本是参考Redisson的RRateLimiter中的lua脚本实现的并没有开启单独的线程去添加令牌而是每次收到新的请求时判断是否需要添加令牌。 tokenBucket-rateLimit.lua内容如下: -- 本质上和滑动窗口类似都是通过有序集合zset来保存缓存信息时间戳作为score通过时间戳维护时间窗口或令牌的有效性 -- 区别在于滑动窗口是保存每次请求的时间搓来计分而令牌桶保存的是许可证生成的时间戳。 --参数说明: --KEYS[1]: 限流器 key --KEYS[2]: 桶中剩余的许可证 key --KEYS[3]: 记录所有许可证发出的时间戳的 key--ARGV[1]: 本次请求申请的令牌数 --ARGV[2]: 请求时间搓 --ARGV[3]: 随机数 --ARGV[4]: 令牌桶容量 --ARGV[5]: 时间周期local limitInfo redis.call(hmget, KEYS[1], rate, interval); local rate limitInfo[1]; local interval limitInfo[2]; --初始化限流信息 if rate false then-- rate表示令牌桶的最大容量rate tonumber(ARGV[4]);-- 生成令牌的时间周期interval tonumber(ARGV[5]);redis.call(hmset, KEYS[1], rate, rate, interval, interval); end; -- assert(rate ~ false and interval ~ false , RateLimiter is not initialized)-- 限流器名称 local valueName KEYS[2]; local permitsName KEYS[3]; -- 单机时才需要 -- if type 1 then -- valueName KEYS[3]; -- permitsName KEYS[5]; -- end; assert(tonumber(rate) tonumber(ARGV[1]), Requested permits amount could not exceed defined rate);-- 获取限流器中的剩余许可数量 local currentValue redis.call(get, valueName); local res; if currentValue ~ false then -- 如果有令牌过期就会在下次请求时重新发放令牌重新发放的令牌数量 releasedlocal expiredValues redis.call(zrangebyscore, permitsName, 0, tonumber(ARGV[2]) - interval);local released 0;for i, v in ipairs(expiredValues) do-- permits表示单次请求发放的令牌个数local random, permits struct.unpack(Bc0I, v);released released permits;end;-- 更新剩余许可数量currentValueif released 0 then-- 清除过期的许可redis.call(zremrangebyscore, permitsName, 0, tonumber(ARGV[2]) - interval);-- 如果 剩余许可数 发放的许可数 桶的大小tonumber(rate)桶已经满了if tonumber(currentValue) released tonumber(rate) then-- 剩余许可 请求许可数 - 已发放的未过期的许可数currentValue tonumber(rate) - redis.call(zcard, permitsName);else-- 桶未满发放新的许可数量更新剩余可用许可剩余许可 当前剩余许可 新发放的许可currentValue tonumber(currentValue) released;end;redis.call(set, valueName, currentValue);end;-- 如果剩余许可不够需要在res中返回下个许可需要等待多长时间if tonumber(currentValue) tonumber(ARGV[1]) then-- zrange获取有序集合下标区间 0 至 0 的成员firstValue表示第一个许可生成的时间local firstValue redis.call(zrange, permitsName, 0, 0, withscores);-- 计算下次许可生成的时间-- res 3 interval - (tonumber(ARGV[2]) - tonumber(firstValue[2]));res 0;else-- 保存发放的许可生成许可保存到permitsName中,redis.call(zadd, permitsName, ARGV[2], struct.pack(Bc0I, string.len(ARGV[3]), ARGV[3], ARGV[1]));-- 减去valueName中的许可数量redis.call(decrby, valueName, ARGV[1]);res 1;end; else-- 初始化限流器struct.pack用于数据打包redis.call(set, valueName, rate);-- ARGV[2]表示分数即时间戳, ARGV[1]为申请的令牌数redis.call(zadd, permitsName, ARGV[2], struct.pack(Bc0I, string.len(ARGV[3]), ARGV[3], ARGV[1]));redis.call(decrby, valueName, ARGV[1]);res 1; end;-- 刷新过期时间 -- Pttl命令以毫秒为单位返回 key 的剩余过期时间 如果存在过期时间需要更新valueName和permitsName对象的过期时间 local ttl redis.call(pttl, KEYS[1]); if ttl 0 thenredis.call(pexpire, valueName, ttl);redis.call(pexpire, permitsName, ttl); end; return res;令牌桶限流器实现TokenBucketRateLimiter public class TokenBucketRateLimiter implements RateLimiter{private final static String PREFIX tokenBucketRateLimiter;private final RScript rScript;public TokenBucketRateLimiter(RedissonClient client) {this.rScript client.getScript(LongCodec.INSTANCE);}Overridepublic boolean isAllowed(Rule rule) {//keys参数组装String keyName String.format(%s.%s,PREFIX,rule.getKey());String valueName String.format({%s}.value,keyName);String permitsName String.format({%s}.permits,keyName);long rateInterval -1L;if (rule.getRateInterval() 0L) {rateInterval rule.getTimeUnit().toMillis(rule.getRateInterval());}byte[] random new byte[8];ThreadLocalRandom.current().nextBytes(random);String script LuaScript.getTokenBucketRateLimiterScript();Long result rScript.eval(RScript.Mode.READ_WRITE, script, RScript.ReturnType.INTEGER,Arrays.asList(keyName, valueName, permitsName),new Object[]{1, System.currentTimeMillis(), random,rule.getRate(),rateInterval});return result 1L;} }五、怎么兼容单机限流 上面采用Redis的Lua脚本实现了经典的4种限流算法并且都是分布式限流。那么基于Redis的限流器可以兼容单机限流吗 其中参考Redisson中的RRateLimiter实现我们很容易发现针对单机限流的实现只需要在每个限流器的Key中添加客户端标识信息即可比如IP信息。 这样每种限流器实现就既可以支持分布式限流也可以支持单机限流。 这里只提供思路感兴趣的朋友可以自己实现。 六、其他限流方案 1、Guava的RateLimiter Google开源工具包Guava提供了限流工具类RateLimiter该类基于令牌桶算法实现流量限制使用十分方便而且十分高效。 其提供了2种令牌桶算法实现平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现但是由于限流信息都保存在本地JVM中因此RateLimiter只能用于单机限流。 dependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion23.0/version /dependency使用样例 Service public class GuavaRateLimiterService {/*每秒控制5个许可*/RateLimiter rateLimiter RateLimiter.create(5.0);/*** 获取令牌** return*/public boolean tryAcquire() {return rateLimiter.tryAcquire();} }Autowiredprivate GuavaRateLimiterService rateLimiterService;ResponseBodyRequestMapping(/ratelimiter)public Result testRateLimiter(){if(rateLimiterService.tryAcquire()){return ResultUtil.success1(1001,成功获取许可);}return ResultUtil.success1(1002,未获取到许可);}思考 Google Guava 工具包中的 RateLimiter 的实现是令牌桶限流那么是怎么添加令牌的呢 如果按照指定间隔添加令牌那么需要开一个线程去定时添加如果有很多个接口很多个 RateLimiter 实例线程数会随之增加这显然不是一个好的办法。显然 Google 也考虑到了这个问题在 RateLimiter 中是在每次令牌获取时才进行计算令牌是否足够的。它通过存储的下一个令牌生成的时间和当前获取令牌的时间差再结合阈值去计算令牌是否足够同时再记录下一个令牌的生成时间以便下一次调用。 2、Redisson的RRateLimiter Redisson的RRateLimiter也是通过Lua实现的令牌桶限流并且支持兼容分布式和单机限流整个框架比较成熟也是我比较推荐的在项目中使用的方案。 添加依赖 dependencygroupIdorg.redisson/groupIdartifactIdredisson-spring-boot-starter/artifactIdversion3.20.1/version/dependency使用RRateLimiter限流 Autowired RedissonClient redissonClient;Testvoid limitTest() throws InterruptedException {RRateLimiter rateLimiter redissonClient.getRateLimiter(laowan.limiter);//RateType.OVERALL 全局RateType.PER_CLIENT每个客户端rateLimiter.trySetRate(RateType.OVERALL, 2, 10, RateIntervalUnit.SECONDS);while (true){//rateLimiter.acquire(1); // 申请1份许可直到成功Thread.sleep(1000L);boolean res rateLimiter.tryAcquire(1, 2, TimeUnit.SECONDS);if(res){log.info(获取许可);}else{log.info(失败);}}}3、Redis的redis-cell限流模块 Git地址redis-cell Redis 4.0提供了一个限流Redis模块名称为redis-cell并提供原子的限流指令。 该模块只有一条指令cl.throttle其参数和返回值比较复杂。 根据官网说明redis-cell的好处主要是两点 1、性能很好非常快 2、封装了底层实现避免自定义Lua出现幼稚写法。 但是由于作者声称redis-cell模块目前已经达到很好的性能和实现后续已经不再积极维护。 不知道是不是我在Redis7.X版本安装该模块始终失败的原因。 感兴趣的朋友可以自己研究下。 4、Nginx 限流 在Nginx中可以通过limit_req_zone配置限流策略。 limit_req_zone 用来限制单位时间内的请求数采用的漏桶算法 “leaky bucket”。 limit_req_zone $binary_remote_addr zonetest:10m rate10r/s; #定义限流策略$binary_remote_addr 指定按 ip 限流统计。zonetest:10m 表示生成一个大小为 10M名字为 one 的内存区域用来存储访问的频次信息。rate10r/s 表示允许同一个客户端的访问频次是每秒 10 次。 5、Gateway网关限流 Spring Cloud Gateway 的代码发现RateLimiter 接口只提供了一个实现类 RedisRateLimiter RedisRateLimiter可以用于分布式限流它的实现原理依然是基于令牌桶算法的不过实现逻辑是放在一段 lua 脚本中的我们可以在 src/main/resources/META-INF/scripts 目录下找到该脚本文件 request_rate_limiter.lua local tokens_key KEYS[1] local timestamp_key KEYS[2]local rate tonumber(ARGV[1]) local capacity tonumber(ARGV[2]) local now tonumber(ARGV[3]) local requested tonumber(ARGV[4])local fill_time capacity/rate local ttl math.floor(fill_time*2)local last_tokens tonumber(redis.call(get, tokens_key)) if last_tokens nil thenlast_tokens capacity endlocal last_refreshed tonumber(redis.call(get, timestamp_key)) if last_refreshed nil thenlast_refreshed 0 endlocal delta math.max(0, now-last_refreshed) local filled_tokens math.min(capacity, last_tokens(delta*rate)) local allowed filled_tokens requested local new_tokens filled_tokens local allowed_num 0 if allowed thennew_tokens filled_tokens - requestedallowed_num 1 endif ttl 0 thenredis.call(setex, tokens_key, ttl, new_tokens)redis.call(setex, timestamp_key, ttl, now) endreturn { allowed_num, new_tokens }这段代码和上面介绍令牌桶算法时用 Java 实现的那段经典代码几乎是一样的。这里使用 lua 脚本主要是利用了 Redis 的单线程特性以及执行 lua 脚本的原子性避免了并发访问时可能出现请求量超出上限的现象。想象目前令牌桶中还剩 1 个令牌此时有两个请求同时到来判断令牌是否足够也是同时的两个请求都认为还剩 1 个令牌于是两个请求都被允许了。 有两种方式来配置 Spring Cloud Gateway 自带的限流。第一种方式是通过配置文件比如下面所示的代码可以对某个 route 进行限流 spring:cloud:gateway:routes:- id: testuri: http://httpbin.org:80/getfilters:- name: RequestRateLimiterargs:key-resolver: #{hostAddrKeyResolver}redis-rate-limiter.replenishRate: 1redis-rate-limiter.burstCapacity: 3其中key-resolver 使用 SpEL 表达式 #{beanName} 从 Spring 容器中获取 hostAddrKeyResolver 对象burstCapacity 表示令牌桶的大小replenishRate 表示每秒往桶中填充多少个令牌也就是填充速度。 第二种方式是通过下面的代码来配置 Beanpublic RouteLocator myRoutes(RouteLocatorBuilder builder) {return builder.routes().route(p - p.path(/get).filters(filter - filter.requestRateLimiter().rateLimiter(RedisRateLimiter.class, rl - rl.setBurstCapacity(3).setReplenishRate(1)).and()).uri(http://httpbin.org:80)).build(); }6、阿里的sentinel限流 官网sentinel 随着微服务的流行服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。 Sentinel 的主要特性 可以看到Sentinel的功能十分强大并且完美兼容目前的微服务体系并且提供了Web控制台对限流规则实现可视化配置。 7、ratelimiter-spring-boot-starter Git地址ratelimiter-spring-boot-starter 介绍该方案主要是作者对基于Redis限流的封装方式比较优雅其基于注解的限流控制使得项目中对接口进行限流十分优雅便捷。想自己封装 限流-spring-boot-starter的同学可以好好借鉴下。 dependencygroupIdcom.github.taptap/groupIdartifactIdratelimiter-spring-boot-starter/artifactIdversion1.3/version /dependency8、信号量 Semaphore 信号量Semaphore也可以用来进行单机环境下的并发控制。 实际应用中可以用来限制访问某种资源的数量比如在Hystrix中就有基于Semaphore的资源隔离策略。 public class SemaphoreTest {private static ExecutorService threadPool Executors.newFixedThreadPool(100);private static Semaphore semaphore new Semaphore(10);public static void main(String[] args) {for (int i 0; i 100; i) {threadPool.execute(new Runnable() {Overridepublic void run() {try {semaphore.acquire();System.out.println(Request processing ...);semaphore.release();} catch (InterruptedException e) {e.printStack();}}});}threadPool.shutdown();} }9、Bucket4j 官方文档Bucket4j Bucket4j是一个基于令牌桶算法实现的强大的限流库它不仅支持单机限流还支持通过诸如 Hazelcast、Ignite、Coherence、Infinispan 或其他兼容 JCache API (JSR 107) 规范的分布式缓存实现分布式限流。 和 Guava 的限流器相比Bucket4j 的功能显然要更胜一筹毕竟 Guava 的目的只是用作通用工具类而不是用于限流的。使用 Bucket4j 基本上可以满足我们的大多数要求不仅支持单机限流和分布式限流而且可以很好的集成监控搭配 Prometheus 和 Grafana 简直完美。值得一提的是有很多开源项目譬如 JHipster API Gateway 就是使用 Bucket4j 来实现限流的。 Bucket4j 唯一不足的地方是它只支持请求频率限流不支持并发量限流另外还有一点虽然 Bucket4j 支持分布式限流但它是基于 Hazelcast 这样的分布式缓存系统实现的不能使用 Redis这在很多使用 Redis 作缓存的项目中就很不爽所以我们还需要在开源的世界里继续探索。 dependencygroupIdcom.bucket4j/groupIdartifactIdbucket4j-core/artifactIdversion8.1.1/version /dependency// define the limit 1 time per 10 minute Bandwidth limit Bandwidth.simple(1, Duration.ofMinutes(10)); // construct the bucket Bucket bucket Bucket.builder().addLimit(limit).build(); ...try {executor.execute(anyRunnable); } catch (RejectedExecutionException e) {// print stacktraces only if limit is not exceededif (bucket.tryConsume(1)) {ThreadInfo[] stackTraces ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);StacktraceUtils.print(stackTraces);}throw e; }10、Resilience4j 官网Resilience4j Resilience4j 是一款轻量级、易使用的高可用框架。用过 Spring Cloud 早期版本的同学肯定都听过 Netflix HystrixResilience4j 的设计灵感就来自于它。自从 Hystrix 停止维护之后官方也推荐大家使用 Resilience4j 来代替 Hystrix。 Resilience4j 的底层采用 Vavr这是一个非常轻量级的 Java 函数式库使得 Resilience4j 非常适合函数式编程。Resilience4j 以装饰器模式提供对函数式接口或 lambda 表达式的封装提供了一波高可用机制重试Retry、熔断Circuit Breaker、限流Rate Limiter、限时Timer Limiter、隔离Bulkhead、缓存Caceh 和 降级Fallback。我们重点关注这里的两个功能限流Rate Limiter 和 隔离BulkheadRate Limiter 是请求频率限流Bulkhead 是并发量限流。 Resilience4j 提供了两种限流的实现SemaphoreBasedRateLimiter 和 AtomicRateLimiter。SemaphoreBasedRateLimiter 基于信号量实现用户的每次请求都会申请一个信号量并记录申请的时间申请通过则允许请求申请失败则限流另外有一个内部线程会定期扫描过期的信号量并释放很显然这是令牌桶的算法。AtomicRateLimiter 和上面的经典实现类似不需要额外的线程在处理每次请求时根据距离上次请求的时间和生成令牌的速度自动填充。 Resilience4j 在功能特性上比 Bucket4j 强大不少而且还支持并发量限流。不过最大的遗憾是Resilience4j 不支持分布式限流。 dependencygroupIdio.github.resilience4j/groupIdartifactIdresilience4j-ratelimiter/artifactIdversion2.0.2/version /dependency通过代码配置 //限制每秒10次调用 RateLimiterConfig config RateLimiterConfig.custom().limitRefreshPeriod(Duration.ofSeconds(1)).limitForPeriod(10).timeoutDuration(Duration.ofMillis(25)).build(); // Create registry RateLimiterRegistry rateLimiterRegistry RateLimiterRegistry.of(config);// Use registry RateLimiter rateLimiterWithDefaultConfig rateLimiterRegistry.rateLimiter(name1); // Decorate your call to BackendService.doSomething() CheckedRunnable restrictedCall RateLimiter.decorateCheckedRunnable(rateLimiterWithDefaultConfig, backendService::doSomething); Try.run(restrictedCall).onFailure((RequestNotPermitted throwable) - LOG.info(Wait before call it again :)));通过属性文件配置 resilience4j.ratelimiter:instances:backendA:limitForPeriod: 10limitRefreshPeriod: 1stimeoutDuration: 0registerHealthIndicator: trueeventConsumerBufferSize: 100backendB:limitForPeriod: 6limitRefreshPeriod: 500mstimeoutDuration: 3sspring注解实现流控 RateLimiter(name backendA,fallbackMethod fallback ) public MonoString method(String param1) {return Mono.error(new NumberFormatException()); }private MonoString fallback(String param1, IllegalArgumentException e) {return Mono.just(test); }private MonoString fallback(String param1, RuntimeException e) {return Mono.just(test); }总结 本文主要介绍了基于Redis通过Lua脚本实现分布式限流的几种方案。 1、4种典型的限流算法固定时间窗口滑动时间窗口漏桶令牌牌。 2、通过Lua脚本实现4种典型的分流算法。 3、其他限流实现方案介绍Guava的RateLimiter、Redisson的RRateLimiter、Redis的redis-cell限流模块、Nginx 限流、 Spring Cloud Gateway网关限流、阿里的sentinel限流、信号量Semaphore单机并发限流、Bucket4j和Resilience4j限流。 系统设计 | 分布式限流器 5 种限流算法7 种限流方式挡住突发流量 redis (3) - 各种限流方式及源码实现 超详细的Guava RateLimiter限流原理解析 ratelimiter-spring-boot-starter Spring Cloud Gateway 限流实战终于有人写清楚了
http://www.dnsts.com.cn/news/177407.html

相关文章:

  • 深圳网站的网络公司西安网站制作公司官网
  • 成都手机网站建设标识设计师
  • 南昌市网站备案义乌广告设计与制作
  • 教务系统门户网站网站引导页怎么做.
  • 英文网站搜索广州近期流行的传染病
  • 网站中的ppt链接怎么做的简历设计网站
  • 温州建站平台视频转链接在线生成
  • 建设专业网站排名智慧软文发稿平台
  • 外贸网站模板免费下载定制一款软件需要多少钱
  • 自考都到哪个网站找题做网站开发的形式有哪些
  • 长沙网站 微信建设免费十大软件app
  • 汕头建站培训南山区宝安区福田区
  • 开网站做女装好还是童装好天津建设工程信息网如何投标报名
  • wordpress网站基础知识推广网站技巧
  • 保定建设网站及推广网页h5
  • 网站背景动图怎么做河南网站建设品牌
  • 长春网站建设外包网络推广优化品牌公司
  • 学校网站源码开源wordpress 如何添加模板文件
  • 福建省建设厅官方网站如何给网站做seo优化
  • 网站建设合同附件格式头条收录提交入口
  • 合肥企业网站建设公司响应式网站区别
  • 站长之家素材网网站建设方案 安全
  • 建设门户网站的重要性焦作做网站最专业的公司
  • 网站规则做业务在那几个网站上找客户端
  • 长春网长春网站建设络推广收费wordpress主题
  • 资讯门户类网站模板深圳广告设计策划公司
  • 网站建设 中企高程局网站建设方案
  • 北京网站托管站内信息 wordpress
  • 汉中网站建设有限公司wordpress 前端图片上传
  • 上海手机网站案例网站开发提供源代码