精华内容
下载资源
问答
  • 限流

    千次阅读 2019-09-27 00:50:08
    分布式限流 很多时候我需要有一个全局的限速,例如用户注册时,让用户输入手机验证码,为了防止短信接口不被恶意频繁调用,一般会限制用户每分钟获取验证码频率,例如一分钟不能超过5次。 此时,我们可以通过Redis...

    1、控制单位时间内的请求数

    atomicLong#incrementAndGet()

    分布式限流

    很多时候我需要有一个全局的限速,例如用户注册时,让用户输入手机验证码,为了防止短信接口不被恶意频繁调用,一般会限制用户每分钟获取验证码频率,例如一分钟不能超过5次。

    此时,我们可以通过Redis的来实现,伪代码如下:

    phoneNum = "186xxxxxx";
    key = "verifyCode:limit:"+phoneNum 
    // SET key value EX 60 NX
    isExists = redis.set(key, 1, "EX 60", "NX");
    if( isExists !=null || redis.incr(key) <=5) {
        //通过
    } else {
        //限速
    }
    

    上述,就是通过Redis实现了限速功能,例如一些网站限制一个IP地址不能在一秒钟内访问超过n次也可以采用类似的思路来实现。

    2、并发线程数

    Semaphore


    从上图可以看出上游的A、B服务直接依赖了下游的基础服务C、D和E,对于A,B服务都依赖的基础服务D这种场景,服务A和B其实处于某种竞争关系,当我们考量服务A的并发阈值时,不可忽略的是服务B对A的影响,所以,大多情况并发阈值的设置需要保守一点,如果服务A的并发阈值设置过大,当流量高峰期来临,有可能直接拖垮基础服务D并影响服务B,即雪崩效应来了。从表面上看并发量限流似乎很有用,但也不可否认,它仍然可以造成流量尖刺,即每台服务器上该服务的并发量从0上升到阈值是没有任何“阻力”的,这是因为并发量考虑的只是服务能力边界的问题。

    3、漏斗算法

    均匀的速率

    4、令牌桶算法

    可接受突然大的流量

     

    Sentinel

    对于provider

    根据qps限流

    Service Provider 用于向外界提供服务,处理各个消费者的调用请求。为了保护 Provider 不被激增的流量拖垮影响稳定性,可以给 Provider 配置 QPS 模式的限流,这样当每秒的请求量超过设定的阈值时会自动拒绝多的请求。限流粒度可以是 服务接口 和 服务方法 两种粒度。若希望整个服务接口的 QPS 不超过一定数值,则可以为对应服务接口资源(resourceName 为接口全限定名)配置 QPS 阈值;若希望服务的某个方法的 QPS 不超过一定数值,则可以为对应服务方法资源(resourceName 为接口全限定名:方法签名)配置 QPS 阈值

     

    对于consumer限流

    根据并发数

    Service Consumer 作为客户端去调用远程服务。每一个服务都可能会依赖几个下游服务,若某个服务 A 依赖的下游服务 B 出现了不稳定的情况,服务 A 请求 服务 B 的响应时间变长,从而服务 A 调用服务 B 的线程就会产生堆积,最终可能耗尽服务 A 的线程数。我们通过用并发线程数来控制对下游服务 B 的访问,来保证下游服务不可靠的时候,不会拖垮服务自身。基于这种场景,推荐给 Consumer 配置线程数模式的限流,来保证自身不被不稳定服务所影响。采用基于线程数的限流模式后,我们不需要再显式地去进行线程池隔离,Sentinel 会控制资源的线程数,超出的请求直接拒绝,直到堆积的线程处理完成,可以达到信号量隔离的效果。

    我们看一下这种模式的效果。假设当前服务 A 依赖两个远程服务方法 sayHello(java.lang.String) 和 doAnother()。前者远程调用的响应时间 为 1s-1.5s 之间,后者 RT 非常小(30 ms 左右)。服务 A 端设两个远程方法 thread count 为 5。然后每隔 50 ms 左右向线程池投入两个任务,作为消费者分别远程调用对应方法,持续 10 次。可以看到 sayHello 方法被限流 5 次,因为后面调用的时候前面的远程调用还未返回(RT 高);而 doAnother() 调用则不受影响。线程数目超出时快速失败能够有效地防止自己被慢调用所影响。

     通过降级

    服务降级

    当服务依赖于多个下游服务,而某个下游服务调用非常慢时,会严重影响当前服务的调用。这里我们可以利用 Sentinel 熔断降级的功能,为调用端配置基于平均 RT 的降级规则。这样当调用链路中某个服务调用的平均 RT 升高,在一定的次数内超过配置的 RT 阈值,Sentinel 就会对此调用资源进行降级操作,接下来的调用都会立刻拒绝,直到过了一段设定的时间后才恢复,从而保护服务不被调用端短板所影响。同时可以配合 fallback 功能使用,在被降级的时候提供相应的处理逻辑。


    Fallback

    从 0.1.1 版本开始,Sentinel Dubbo Adapter 还支持配置全局的 fallback 函数,可以在 Dubbo 服务被限流/降级/负载保护的时候进行相应的 fallback 处理。用户只需要实现自定义的 DubboFallback 接口,并通过 DubboFallbackRegistry 注册即可。默认情况会直接将 BlockException 包装后抛出。同时,我们还可以配合 Dubbo 的 fallback 机制 来为降级的服务提供替代的实现。

     

     

     

     

     

     

    快30岁的的我,突然发现自己不再年轻,可能自己消耗的太多,懂得的太晚

     

    我是一名程序媛,很重感情的姑娘,我的童年记忆里我又觉得我是特别的苦,但是很快乐的姑娘

    小时候家里太穷,基本都是看着别人家的孩子在吃好吃的,我只有在那里看着的份,在我记事起,我爸爸腿受伤了,我延迟上学了一年,那年6岁我开始照顾我爸爸,我的童年,我的少年都是很苦的过来的,而我一直在抗争,在与命运抗争

    村里还是重男轻女的,但是我的父母比较的开明,我多次的跟我爸妈谈过话,我说我要上大学,我不想看到到头的生活,那时候我只有9岁,我就知道我要干什么,穷人家的孩子是要受尽各种的苦,才能考上高中,考上大学,我顺利的考上了高中,是年纪里唯一个考上高中的学生,可能我的学校太差,还有我的刻苦,我一直都是年级第一的

    高中的时候生病,考试成绩577分,那年第一年知分填志愿,我与一本无缘,我还是选择离开家乡,去了东北读书

     

    整个过程我觉得我是快乐的,我一直都是成绩很棒,一切我也充满希望,大学毕业,也进入大厂,做起了一个程序媛

    可能我的童年里,太多的与生活做斗争,不知道什么是感情,可能也因为自己的性格里有太多的不安全感,与谈了三年的恋爱告了别分了手,我一直都是在感情里慢半拍的人,分手了半年时间,回了趟大学,想起了点点滴滴,在大学的每一步我都在哭,在想想毕业后自己艰辛,我突然觉得我真的失去了我最爱的人,我是爱他的

     

    然后在回京以后,我路上一直哭,坐地铁自己低着头一直哭的稀里哗啦,我告诉我自己,我真的失去了太多,我没有把握住那个对我那么好,那么单纯付出的人

     

    然后毕业以后在伤痛里沉浸了3个月才像一个正常人一样,突然想开了好多,但是持续了3年,我也没再去真正的去谈恋爱

    自己一直没有走出来,在毕业后的第三年,我觉得我应该走出来了,遇到了公司的同事,他傻乎乎,对人很真诚,对我也足够的忍耐,一起学习,一起玩,最后商量一起来杭州,中间有很多的矛盾,来了杭州才发现,他这个人平时看着足够的忍耐,却脾气爆发的时候像是爆炸,每次都把我炸的自己蹲在地上哭,没有想象的杭州生活的美好,而是各种的矛盾,来了快一年,7个月都在争吵,他也不断的离家出走,最后两者都很受伤,以分手收场

     

    我再看看现在的我自己,老了好多,颓废了好多,觉得自己很惨,怎么就跟他来了这里,没有一个朋友和亲人

    我再看看我的经历,是不是我真的没有爱的能力,28岁的我,被这段感情折磨的已经没有了爱的动力,自己一个人的杭州,没有一点的生机和活力

    在看一直奋斗自己,一直过不了感情这个砍,看到了世界的残酷,人性的恶毒,并不是每个人都善良,对自己好才是王道

     

    我不知道自己何时才能走出来,将近30岁的年级,看着别人都走进了婚姻的殿堂,而我在这里还在舔着伤口

    我一直在想活着的意义,婚姻的本质,是不是自己一直都错了,还是注定要一辈子孤独终老

    此时的自己已经哭的一塌糊涂,毕业的时候班长叮嘱我说,敏敏,你很单纯,心软,在社会上我好怕你被骗,你以后小心点

    我终于知道为什么班长叮嘱我了,可能我真的是很单纯,看事情没那么复杂,可以很快乐的生活着,但是现在的自己,好难受

     

    突然觉得自己长大了,又突然觉得自己没了未来

     

    杭州一个人的城市,不是太容易的,自己也老了吧,不再想着交友,认识新的人,我好想回到去年,甚至大学的时候,我起码是快乐的,更想回到高中的时候,那时候的我不知道感情是这样子有如此大的杀伤力的

     

    生活放眼望去,似乎什么都到头了,甚至恐惧未来的自己

     

    展开全文
  • Spring Cloud Alibaba 05_使用微服务网关 Gateway 实现路由映射和API限流 注:Gateway 是 SpringCloud 官方提供的新一代网关组件,是基于Netty 的,与 Servlet 不兼容 所以在 gateway 中不能出现 Servlet 相关的...

    Spring Cloud Alibaba 05_使用微服务网关 Gateway 实现路由映射和API限流


    注:Gateway 是 SpringCloud 官方提供的新一代网关组件,是基于Netty 的,与 Servlet 不兼容

    所以在 gateway 中不能出现 Servlet 相关的组件,不能使用 spring web 依赖!


    手动配置路由映射:

    • 删除父工程中的 spring web 依赖,然后在 Provider 和 Comsumer 中重新添加:
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    • 创建 getway 子模块,将子模块 provider 的 parent 标签修改为父工程信息,并添加 gateway 的依赖:
    <parent>
    	<groupId>com.blu</groupId>
    	<artifactId>springcloudalibabademo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    </parent>
    
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-gateway</artifactId>
    	<version>2.2.5.RELEASE</version>
    </dependency>
    
    • gatewayapplication.yml 配置文件:
    server:
      port: 8010
    
    spring:
      application:
        name: gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true # 开启通过网关映射微服务
          routes:
            - id: provider_route  # 映射路由id
              uri: http://localhost:8081  # 真实的微服务uri
              # 映射的服务路径,此时 localhost:8010/provider/index 将映射成 localhost:8081/provider/list
              predicates:
                - Path=/provider/**
              # 地址映射时去除第一个前缀(provider),即: localhost:8081/list
              filters:
                - StripPrefix=1
    

    启动 provider 和 gateway

    直接访问 provider :http://localhost:8081/index

    通过网关访问 provider :http://localhost:8010/provider/index


    通过 nacos 实现自动配置的路由映射:

    gateway 中添加 nacos 依赖:

    <dependency>
    	<groupId>com.alibaba.cloud</groupId>
    	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    	<version>2.2.1.RELEASE</version>
    </dependency>
    

    去掉 application.yml 中手动配置的路由映射:

    server:
      port: 8010
    
    spring:
      application:
        name: gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true # 开启通过网关映射微服务
    

    重启 gateway,依然能够通过路由访问:http://localhost:8010/provider/index





    基于路由限流

    gateway 中去掉 nacos 依赖,重新手动配置 routes,添加 sentinel 与 gateway 整合的依赖:

    <dependency>
    	<groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        <version>1.8.0</version>
    </dependency>
    

    限流配置类:

    package com.blu.configuration;
    
    import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
    import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.codec.ServerCodecConfigurer;
    import org.springframework.web.reactive.function.BodyInserters;
    import org.springframework.web.reactive.function.server.ServerResponse;
    import org.springframework.web.reactive.result.view.ViewResolver;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.PostConstruct;
    import java.util.*;
    
    @Configuration
    public class GatewayConfiguration {
    
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;
    
        public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,ServerCodecConfigurer serverCodecConfigurer) {
            this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
            this.serverCodecConfigurer = serverCodecConfigurer;
        }
    
        //配置限流的异常处理
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
            return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
        }
    
        //配置初始化的限流参数
        @PostConstruct
        public void initGatewayRules(){
            Set<GatewayFlowRule> rules = new HashSet<>();
            rules.add(
                    //provider_route是配置文件中配置的路由route的id
                    new GatewayFlowRule("provider_route")
                            //表示1s允许1个访问,QPS=1
                            .setCount(1)
                            .setIntervalSec(1)
            );
            GatewayRuleManager.loadRules(rules);
        }
    
        //初始化限流过滤器
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public GlobalFilter sentinelGatewayFilter() {
            return new SentinelGatewayFilter();
        }
    
        //自定义限流异常页面
        @PostConstruct
        public void initBlockHandlers(){
            BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
                @Override
                public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                    Map map = new HashMap();
                    map.put("code",0);
                    map.put("msg","被限流了");
                    return ServerResponse.status(HttpStatus.OK)
                            .contentType(MediaType.APPLICATION_JSON)
                            .body(BodyInserters.fromObject(map));
                }
            };
            GatewayCallbackManager.setBlockHandler(blockRequestHandler);
        }
    
    
    }
    

    启动,直接访问 Provider 微服务(无限流):http://localhost:8081/index

    通过 gateway 访问 Provider (限流 1QPS):http://localhost:8010/provider/index





    基于API分组限流

    • 在 ProviderController 中添加以下四个方法:
    @GetMapping("api1/demo1")
    public String demo1(){
    	return "demo1";
    }
    
    @GetMapping("api1/demo2")
    public String demo2(){
    	return "demo2";
    }
    
    @GetMapping("api2/demo3")
    public String demo3(){
    	return "demo3";
    }
    
    @GetMapping("api2/demo4")
    public String demo4(){
    	return "demo4";
    }
    
    • 修改 GatewayConfiguration 配置类:
    package com.blu.configuration;
    
    import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
    import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
    import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.codec.ServerCodecConfigurer;
    import org.springframework.web.reactive.function.BodyInserters;
    import org.springframework.web.reactive.function.server.ServerResponse;
    import org.springframework.web.reactive.result.view.ViewResolver;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.PostConstruct;
    import java.util.*;
    
    @Configuration
    public class GatewayConfiguration {
    
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;
    
    
        public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                    ServerCodecConfigurer serverCodecConfigurer) {
            this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
            this.serverCodecConfigurer = serverCodecConfigurer;
        }
    
        //配置限流的异常处理
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
            return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
        }
    
        //配置初始化的限流参数
        @PostConstruct
        public void initGatewayRules(){
            Set<GatewayFlowRule> rules = new HashSet<>();
            //配置分组名称和QPS
            rules.add(new GatewayFlowRule("provider-api1").setCount(1).setIntervalSec(1));
            rules.add(new GatewayFlowRule("provider-api2").setCount(2).setIntervalSec(1));
            GatewayRuleManager.loadRules(rules);
        }
    
        //初始化限流过滤器
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public GlobalFilter sentinelGatewayFilter() {
            return new SentinelGatewayFilter();
        }
    
        //自定义限流异常页面
        @PostConstruct
        public void initBlockHandlers(){
            BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
                @Override
                public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                    Map map = new HashMap();
                    map.put("code",0);
                    map.put("msg","被限流了");
                    return ServerResponse.status(HttpStatus.OK)
                            .contentType(MediaType.APPLICATION_JSON)
                            .body(BodyInserters.fromObject(map));
                }
            };
            GatewayCallbackManager.setBlockHandler(blockRequestHandler);
        }
    
        //自定义API分组
        @PostConstruct
        private void initCustomizedApis(){
            Set<ApiDefinition> definitions = new HashSet<>();
            ApiDefinition api1 = new ApiDefinition("provider-api1")
                    .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                        add(new ApiPathPredicateItem().setPattern("/provider/api1/**")
                                .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                    }});
            ApiDefinition api2 = new ApiDefinition("provider-api2")
                    .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                        add(new ApiPathPredicateItem().setPattern("/provider/api2/demo3"));
                    }});
            definitions.add(api1);
            definitions.add(api2);
            GatewayApiDefinitionManager.loadApiDefinitions(definitions);
        }
    
    }
    

    启动访问:

    http://localhost:8010/provider/api1/demo1 (限流1QPS)

    http://localhost:8010/provider/api1/demo2 (限流1QPS)

    http://localhost:8010/provider/api2/demo3 (限流2QPS)

    http://localhost:8010/provider/api2/demo4 (不限流)



    基于 nacos 服务发现组件进行限流

    • 在 gateway 中重新添加 nacos 依赖,application.yml 中去除手动配置的路由映射
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>
    

    重启访问,效果一致:

    http://localhost:8010/provider/api1/demo1 (限流1QPS)

    http://localhost:8010/provider/api1/demo2 (限流1QPS)

    http://localhost:8010/provider/api2/demo3 (限流2QPS)

    http://localhost:8010/provider/api2/demo4 (不限流)

    展开全文
  • Redis 简单限流、漏斗限流

    千次阅读 2019-03-22 14:57:34
    简单限流 实现代码: 漏斗限流

    简单限流

    实现代码:

    漏斗限流

     

    展开全文
  • 单机限流和分布式应用限流

    千次阅读 2019-03-11 13:04:57
    单机限流 1. 令牌桶算法 2. 漏桶算法 3. 计数器限流算法 4.漏桶和令牌桶的比较 分布式限流 单机限流 在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个...

    目录

    单机限流

    1. 令牌桶算法

    2. 漏桶算法

    3. 计数器限流算法

    4.漏桶和令牌桶的比较

    分布式限流


    单机限流

    在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃。此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。在限流时,常见的两种算法是漏桶和令牌桶算法算法。

    单机限流算法主要有:令牌桶(Token Bucket)、漏桶(leaky bucket)和计数器算法是最常用的三种限流的算法。

    1. 令牌桶算法

    令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝

    public class RateLimiterDemo {
        private static RateLimiter limiter = RateLimiter.create(5);
     
        public static void exec() {
            limiter.acquire(1);
            try {
                // 处理核心逻辑
                TimeUnit.SECONDS.sleep(1);
                System.out.println("--" + System.currentTimeMillis() / 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    Guava RateLimiter 提供了令牌桶算法可用于平滑突发限流策略。
    该示例为每秒中产生5个令牌,每200毫秒会产生一个令牌。
    limiter.acquire() 表示消费一个令牌。当桶中有足够的令牌时,则直接返回0,否则阻塞,直到有可用的令牌数才返回,返回的值为阻塞的时间。

    2. 漏桶算法

    它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量,数据可以以任意速度流入到漏桶中。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。 漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶为空,则不需要流出水滴,如果漏桶(包缓存)溢出,那么水滴会被溢出丢弃

    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * 漏斗限流算法
     *
     * @author dadiyang
     * @date 2018/9/28
     */
    public class FunnelRateLimiter {
        private Map<String, Funnel> funnelMap = new ConcurrentHashMap<>();
        
        public static void main(String[] args) throws InterruptedException {
            FunnelRateLimiter limiter = new FunnelRateLimiter();
            int testAccessCount = 30;
            int capacity = 5;
            int allowQuota = 5;
            int perSecond = 30;
            int allowCount = 0;
            int denyCount = 0;
            for (int i = 0; i < testAccessCount; i++) {
                boolean isAllow = limiter.isActionAllowed("dadiyang", "doSomething", 5, 5, 30);
                if (isAllow) {
                    allowCount++;
                } else {
                    denyCount++;
                }
                System.out.println("访问权限:" + isAllow);
                Thread.sleep(1000);
            }
            System.out.println("报告:");
            System.out.println("漏斗容量:" + capacity);
            System.out.println("漏斗流动速率:" + allowQuota + "次/" + perSecond + "秒");
    
            System.out.println("测试次数=" + testAccessCount);
            System.out.println("允许次数=" + allowCount);
            System.out.println("拒绝次数=" + denyCount);
        }
    
        /**
         * 根据给定的漏斗参数检查是否允许访问
         *
         * @param username   用户名
         * @param action     操作
         * @param capacity   漏斗容量
         * @param allowQuota 每单个单位时间允许的流量
         * @param perSecond  单位时间(秒)
         * @return 是否允许访问
         */
        public boolean isActionAllowed(String username, String action, int capacity, int allowQuota, int perSecond) {
            String key = "funnel:" + action + ":" + username;
            if (!funnelMap.containsKey(key)) {
                funnelMap.put(key, new Funnel(capacity, allowQuota, perSecond));
            }
            Funnel funnel = funnelMap.get(key);
            return funnel.watering(1);
        }
    
        private static class Funnel {
            private int capacity;
            private float leakingRate;
            private int leftQuota;
            private long leakingTs;
    
            public Funnel(int capacity, int count, int perSecond) {
                this.capacity = capacity;
                // 因为计算使用毫秒为单位的
                perSecond *= 1000;
                this.leakingRate = (float) count / perSecond;
            }
    
            /**
             * 根据上次水流动的时间,腾出已流出的空间
             */
            private void makeSpace() {
                long now = System.currentTimeMillis();
                long time = now - leakingTs;
                int leaked = (int) (time * leakingRate);
                if (leaked < 1) {
                    return;
                }
                leftQuota += leaked;
                // 如果剩余大于容量,则剩余等于容量
                if (leftQuota > capacity) {
                    leftQuota = capacity;
                }
                leakingTs = now;
            }
    
            /**
             * 漏斗漏水
             *
             * @param quota 流量
             * @return 是否有足够的水可以流出(是否允许访问)
             */
            public boolean watering(int quota) {
                makeSpace();
                int left = leftQuota - quota;
                if (left >= 0) {
                    leftQuota = left;
                    return true;
                }
                return false;
            }
        }
    }
    
    

    3. 计数器限流算法

    数器限流算法也是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。

    public class CountRateLimiterDemo {
     
        private static Semaphore semphore = new Semaphore(5);
     
        public static void exec() {
            if(semphore.getQueueLength()>100){
                System.out.println("当前等待排队的任务数大于100,请稍候再试...");
            }
            try {
                semphore.acquire();
                // 处理核心逻辑
                TimeUnit.SECONDS.sleep(1);
                System.out.println("--" + System.currentTimeMillis() / 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semphore.release();
            }
        }
    }

    使用Semaphore信号量来控制并发执行的次数,如果超过域值信号量,则进入阻塞队列中排队等待获取信号量进行执行。如果阻塞队列中排队的请求过多超出系统处理能力,则可以在拒绝请求。

     

    4.漏桶和令牌桶的比较

    令牌桶可以在运行时控制和调整数据处理的速率,处理某时的突发流量。放令牌的频率增加可以提升整体数据处理的速度,而通过每次获取令牌的个数增加或者放慢令牌的发放速度和降低整体数据处理速度。而漏桶不行,因为它的流出速率是固定的,程序处理速度也是固定的。更多算法相关:算法聚合

     

    分布式限流

    实现原理其实很简单。既然要达到分布式全局限流的效果,那自然需要一个第三方组件来记录请求的次数。

    其中 Redis 就非常适合这样的场景。

    • 每次请求时将方法名进行md5加密后作为Key 写入到 Redis 中,超时时间设置为 2 秒,Redis 将该 Key 的值进行自增。
    • 当达到阈值时返回错误。
    • 写入 Redis 的操作用 Lua 脚本来完成,利用 Redis 的单线程机制可以保证每个 Redis 请求的原子性

    Lua脚本准备

    local val = redis.call('incr', KEYS[1])
    local ttl = redis.call('ttl', KEYS[1])
    
    redis.log(redis.LOG_NOTICE, "incr "..KEYS[1].." "..val);
    if val == 1 then
        redis.call('expire', KEYS[1], tonumber(ARGV[1]))
    else
        if ttl == -1 then
            redis.call('expire', KEYS[1], tonumber(ARGV[1]))
        end
    end
    
    if val > tonumber(ARGV[2]) then
        return 0
    end
    
    return 1
    

    RateLimiter.java

    @Component
    public class RateLimiter {
        @Autowired
        private RedisClient redisClient;
    
        @Value("${redis.limit.expire}")
        private int expire;
    
        @Value("${redis.limit.request.count}")
        private int reqCount;
    
        @Value("${redis.limit.script.name}")
        private String scriptName;
    
        public Long limit(String key) {
            return redisClient.eval(key, scriptName, 1, expire, reqCount);
        }
    }

    RateLimitAspect.java 

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import com.lzhsite.core.exception.OverLimitException;
    import com.lzhsite.core.utils.MD5Util;
    import com.lzhsite.technology.redis.limit.RateLimiter;
    
    /**
     * Created by hao.g on 18/5/17.
     */
    @Aspect
    @Component
    public class RateLimitAspect {
        @Autowired
        private RateLimiter rateLimiter;
    
        @Before("@annotation(com.lzhsite.technology.redis.limit.RateLimit)")
        public void before(JoinPoint pjp) throws Throwable {
            Signature sig = pjp.getSignature();
            if (!(sig instanceof MethodSignature)) {
                throw new IllegalArgumentException("该注解只能用在方法上");
            }
            MethodSignature msig = (MethodSignature) sig;
            String methodName = pjp.getTarget().getClass().getName() + "." + msig.getName();
            String limitKey = MD5Util.md5(methodName);
    
            if (rateLimiter.limit(limitKey) != 1){
                throw new OverLimitException("触发限流控制");
            }
        }
    }

     RateLimit.java

    @Target({ElementType.METHOD})  
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RateLimit {
        String value() default "";
    }
    
    @RestController
    public class ShopOrderController {
        private static final Logger LOGGER = LoggerFactory.getLogger(ShopOrderController.class);
    
        @Autowired
        private ShopOrderService shopOrderService;
    
        @RequestMapping("/seckill")
        @RateLimit
        public String index(Long stockId) {
            try{
                if (shopOrderService.createOrder(stockId)){
                    LOGGER.info(ConstantUtil.SNATCH_DEAL_SUCCESS);
                    return ConstantUtil.SNATCH_DEAL_SUCCESS;
                }
            } catch (Exception e){
                LOGGER.error(e.getMessage());
                return e.getMessage();
            }
    
            return ConstantUtil.SYSTEM_EXCEPTION;
        }
    }
    

    当然这只是利用 Redis 做了一个粗暴的计数器,如果想实现类似于上文中的令牌桶算法可以基于 Lua 自行实现。

    完整代码

    https://gitee.com/lzhcode/maven-parent/blob/78734ac309aba8f5499e0dd2eefc45c41baf0ebe/lzh-technology/src/main/java/com/lzhsite/aop/RateLimitAspect.java

     

    参考文章

    https://blog.csdn.net/sunlihuo/article/details/79700225

    https://blog.csdn.net/ghaohao/article/details/80361089

    展开全文
  • 限流的意义 限流一般是指在一个时间窗口内对某些操作请求的数量进行限制,比如一个论坛限制用户每秒钟只能发一个帖子,每秒钟只能回复5个帖子。限流可以保证系统的稳定,限制恶意请求,防止因为流量暴增导致系统瘫痪...
  • SpringCloud-Alibaba-Sentinel-服务降级-热点限流-服务熔断

    万次阅读 多人点赞 2020-12-01 21:34:22
    比如: 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含...
  • java实现系统限流及IP限流

    千次阅读 2018-10-11 22:34:14
    Java 对IP请求进行限流. 高并发系统下, 有三把利器 缓存 降级 限流. 缓存: 将常用数据缓存起来, 减少数据库或者磁盘IO 降级: 保护核心系统, 降低非核心业务请求响应 限流: 在某一个时间窗口内...
  • 熔断限流/降级限流

    千次阅读 2019-01-22 11:36:36
    熔断是直接不处理或返回个默认值,降级比熔断更缓和一点 ... 实现原理:网关层在nginx中实现限流或给controller加切片  业务层给service加切片 熔断降级插件:Hystrix,谷歌RateLimite...
  • spring cloud gateway 之限流

    万次阅读 热门讨论 2018-12-18 21:52:04
    在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。 常见的限流方式,比如Hystrix适用线程池隔离,超过线程池的负载,走熔断的逻辑...
  • 什么是限流及如何限流

    千次阅读 2019-01-02 17:30:39
    此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。在限流时,常见的两种算法是漏桶和令牌桶算法算法。 限流算法 令牌桶(Token Bucket)、漏桶(leaky ...
  • 例如网站首页采取二级缓存操作,减少对数据库和redis的压力,但是遇到恶意请求还是不能有效的缓解压力,限流就能起到保护措施的作用了 2.nginx限流 nginx提供了两种限流方案: 控制请求速率 控制并发连接数 3.控制速率...
  • Sentinel 无需代码一行配置搞定限流

    万次阅读 2020-10-22 09:28:58
    Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面。在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数;或者按照某种规则进行限流,如限制ip的单位时间...
  • 高并发限流解决方案 高并发限流解决方案限流算法(令牌桶、漏桶、计数器)、应用层解决限流(Nginx) 限流算法 常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。 计数器 它是限流算法中最简单最...
  • 限流算法 令牌桶算法 漏桶算法 应用级限流 限制总并发数/连接/请求数 限制接口的总并发/请求数 限流接口每秒的请求数 平滑限流接口的请求数 平滑突发限流(SmoothBursty) 平滑预热限流(SmoothWarmingUp)...
  • 限流

    2019-07-28 21:22:17
    服务治理——限流(30%) neural
  • 1.简单限流 2.漏斗限流 2.1 Redis-Cell 限流在分布式领域是一个经常被提起的话题,当系统的处理能力有限的时候,需要阻止计划外的请求继续对系统施压。除了流量控制,限流还有一个应用的目的是用于控制用户的行为...
  • 限流01】限流算法理论篇

    千次阅读 2020-06-30 00:26:08
    常用手段有:鉴权、限流、降级、熔断等。 其中,限流是指对某个接口的调用频率进行限制,防止接口调用频率过快导致线程资源被耗尽从而导致整个系统响应变慢。限流在很多业务中都有应用,比如:秒杀、双11。当用户...
  • 本篇内容根据《spring cloud alibaba 微服务原理与实战》中内容摘取,希望和大家分享限流的思想,本篇不涉及代码层面的实现。 限流的目的 目的:通过限制并发访问数或者限制一个时间窗口内允许处理的请求数量来保护...
  • 系统限流实践 - 分布式限流

    千次阅读 2016-07-11 14:35:01
    开篇上篇学习了应用限流(传送门),接下来学习一下分布式限流的方法分布式限流分布式系统也会有限流的需求。分布式服务关键需要把限流实现为原子化,解决方案可以使用Redis+Lua或者Nginx+Lua来实现。Redis+Lua实现...
  • Redis 实现限流的三种方式

    万次阅读 多人点赞 2019-05-30 23:37:24
    面对越来越多的高并发场景,限流显示的尤为重要。 当然,限流有许多种实现的方式,Redis具有很强大的功能,我用Redis实践了三种的实现方式,可以较为简单的实现其方式。Redis不仅仅是可以做限流,还可以做数据统计...
  • 头条号突然被限流了是什么原因?我不在乎这个!有些人会对他们写的东西感兴趣,并会继续努力工作。没人看到,自己的水平太差了,限制在极限上,不要指望在这里发财或出名!(不管怎样,我不知道我是否有限)只要快乐!...
  • JUC之限流利器 Semaphore

    万次阅读 2020-06-06 09:28:45
    如果大家对java架构相关感兴趣,可以...常用于限制可以访问某些资源的线程数量,例如通过 Semaphore 限流。 主要方法摘要 // 非公平构造函数 创建具有给定的许可数和非公平的公平设置的 Semaphore。 Semaphore(int perm
  • 之前我们了解了 Sentinel 集成 SpringBoot实现限流,也探讨了Sentinel的限流基本原理,那么接下去我们来学习一下Sentinel整合Dubbo及 Nacos 实现动态数据源的限流以及分布式限流。  先来看一下我的工程目录: ...
  • 今天来说说限流的相关内容,包括常见的限流算法、单机限流场景、分布式限流场景以及一些常见限流组件。当然在介绍限流算法和具体场景之前我们先得明确什么是限流,为什么要限流?。任何技术都要搞清它...
  • 抖音限流怎么办。 1,如果大家是因为作品违规引起的限流是有办法解决的,可以通过平台进行申诉,但是如果申诉失败就不要再申诉,因为再次申诉也是没用的。 2,如果是因为刷赞刷流量等也非常容易引起封号危机,这种...
  • 目录 1 引入依赖 2 限流实现 3自定义拦截器,在拦截器中实现限流 4 实现WebMvcConfigurer 添加自定义拦截器 ... 限流限流算法已经介绍了常见的限流算法。guava 的 RateLimiter 使用的是令牌桶算法。本次实战,...
  • Nginx限流

    2019-09-23 14:11:22
    这个时候接口进行限流是非常有必要的,而限流是Nginx最有用的特性之一,而且也是最容易被错误配置的特性之一。本篇文章主要讲讲Nginx如何对接口进行限流。 Nginx限流主要分为两种方式: 限制访问频率 限制并发连接...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 36,793
精华内容 14,717
关键字:

限流