高并发系统的三把利剑(缓存,降级,限流)

下咽顿除烟火气,入齿便作冰雪声。 文天祥 西瓜吟

 

场景 : 618抢购。 很多系统宕机,很多服务500(404)。

 

发生这个场景的问题所在:

1: 独立api频繁被调用

2:电商网站某个模块入口被瞬间挤爆

3: 调用第三方网站api,我方请求过多,第三方网站无法及时响应,服务器排队太多宕机。

 

针对不同的场景,使用不同的限流算法来避免网站不可用。

 

 

高并发系统的三把利剑

1:缓存

1.1 缓存目的 : 提升系统访问速度和增大系统能处理的容量。提升性能,缓解数据库压力

1.2 缓存的方式: redis , memcached

1.3 缓存带来的问题

【1】、缓存穿透:缓存穿透是说收到一个请求,但是该请求缓存中不存在,只能去数据库中查询,然后放进缓存。
【2】、缓存击穿:上面提到的某个数据没有,然后好多请求查询数据库,可以归为缓存击穿的范畴:对于热点数据,当缓存失效的一瞬间,所有的请求都被下放到数据库去请求更新缓存,数据库被压垮。
【3】、缓存雪崩:缓存雪崩是指当我们给所有的缓存设置了同样的过期时间,当某一时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被抛向了数据库,数据库就崩掉了。
【4】、缓存刷新:既清空缓存 ,一般在insert、update、delete操作后就需要刷新缓存,如果不执行就会出现脏数据。但当缓存请求的系统蹦掉后,返回给缓存的值为null。   

 

2:降级 Spring Cloud Hystrix

2.1 人工降级

修改配置中心的配置,代码里面根据相关配置开关去执行不同的代码逻辑块。

2.2 自动降级

熔断策略

熔断本质上是一个过载保护机制

 

举例:

微服务A -》微服务B -》微服务C,如果微服务A需要发布新版本,会对B,C产生影响。

房子的排污管道从上至下,如果你家楼下的管道堵住了,污水会灌到你们家。这时候你先避免自己

家收到影响,然后通知楼下去疏通。

 

 

高并发系统的三把利剑(缓存,降级,限流)_第1张图片 熔断系统性能

 

3:限流

海底捞高峰时间,如果采取熔断,关门10分钟,会导致用户体验不好。

画图看看现有项目部署下的多层面限流实现

高并发系统的三把利剑(缓存,降级,限流)_第2张图片

限流策略

3.1 api限流(黑白名单等皆可)

3.1.1 计数器(固定窗口) : 若干定长时间内,ip地址/用户/流量等访问次数.

String remoteAddr = request.getRemoteAddr();
        Integer count = map.get(remoteAddr);
​
        //统计ip访问次数,或者总共的访问次数
        if(null == count){
            map.put(remoteAddr,1);
        }else{
            map.put(remoteAddr,count+1);
        }
​
        //可以记录该ip的访问时间,限制其 1s 内只能访问一次等
        long timeMillis = System.currentTimeMillis();
​
        if(count > 5){
            return ; //不继续处理请求
        }
​
        System.out.println(remoteAddr +" : "+ map.get(remoteAddr));

 

高并发系统的三把利剑(缓存,降级,限流)_第3张图片

问题: 流量请求不是一个 **固定周期内的恒定值**,可能会所有请求在固定周期内最后一秒,或者第一秒涌入,这样固定周期内其他时间空闲了下来导致资源无法高效利用,或者固定周期内一些请求无法处理。滑动窗口可以解决这个问题。

滑动窗口

高并发系统的三把利剑(缓存,降级,限流)_第4张图片

 

https://media.pearsoncmg.com/aw/ecs_kurose_compnetwork_7/cw/content/interactiveanimations/selective-repeat-protocol/index.html

 

3.1.2 漏桶算法

通过一个缓冲区将不平滑的流量“整形”成平滑的,也就是说将高于平均值的流量补充到低于平均值的流量,以此最大化计算处理资源的利用率。

应用场景: 如果你的系统需要调用第三方发短信,支付等接口。对方接口gps为2000,但是你的系统同一时间接收到5000个请求,这样可以设置漏桶,系统恒定输出2000,流量入口可以暂存。

 

高并发系统的三把利剑(缓存,降级,限流)_第5张图片

3.1.3 令牌桶 。 不会误削峰

以恒定的速度 (Rate:令牌生成速率 / 每秒)往桶里面放入令牌,如果这时候有请求需要处理,则需要先从桶里面获取一个令牌,如果这时候桶里面的令牌被消耗完毕,则返回其他结果(停止服务等)。

高并发系统的三把利剑(缓存,降级,限流)_第6张图片

应用场景:应对秒杀的场景,秒杀1000个商品,在1s之内,永远只接受10个请求去处理。

Semaphore : 限制现成个数(控制并发数量)

控制速率(处理突发流量): Burst 令牌桶大小 /

 

Ratelimiter实现令牌桶:

实现上并不是有个定时器在那里放令牌,然后消耗令牌,而是将空闲时间利用令牌的思想进行建模,实质上是计算并保留下次可执行请求的时间,并根据这个时间与请求到来的时间作比较,然后决定是否执行

 

3.2 应用级限流tomcat   网上拷贝的

配置tomcat中的自定义线程池,配置最大连接数,请求处理队列等参数来限流。

conf/server.xml文件中,在Connector之前配置一个Executor (线程池)




配置Connector


 

3.2.3 分布式限流(流量入口)

限制接口: 获取令牌的方式,采用redis。RedissonClient getRateLimiter()。

//结合redis,实现分布式的qpi接口限流
    public static void test() throws InterruptedException, ExecutionException {
​
        Config config = new Config();
//        config.useClusterServers()
//                .setScanInterval(2000)
//                .addNodeAddress("127.0.0.1:7000", "127.0.0.1:7001")
//                .addNodeAddress("127.0.0.1:7002");
        config.useSingleServer().setAddress("redis://192.168.8.154:6379").setConnectionMinimumIdleSize(10);
​
​
        RedissonClient redisson = Redisson.create(config);
​
        /**
         * 基于Redis的分布式限流器可以用来在分布式环境下现在请求方的调用频率。
         * 既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。
         * 该算法不保证公平性。
         */
        RRateLimiter myLimiter = redisson.getRateLimiter("my");
​
​
        /**
         * Total rate for all RateLimiter instances
         * 作用在所有的RRateLimiter实例
         * OVERALL
         *
         * Total rate for all RateLimiter instances working with the same Redisson instance
         * 作用在同一个Redisson实例创建的 RRateLimiter上面。
         * PER_CLIENT
         *
         * return : 设置是否成功。 对同一个redis服务端,只需要设置一次。如果redis重启需要重新设置
         */
        boolean bl = myLimiter.trySetRate(RateType.OVERALL,5,5, RateIntervalUnit.SECONDS);
​
​
        System.out.println(bl);
​
        for(int i = 0 ; i < 200 ; i++){
            System.out.println(myLimiter.tryAcquire());
            Thread.sleep(100);
        }
    }

 

前置化限流: nginx+lua (Openresty)  网上拷贝的:

location /buy {
   access_by_lua_block {
       local limit_req = require "resty.limit.req"
       -- 这里设置rate=2/s,漏桶桶容量设置为0,(也就是来多少水就留多少水) 
       -- 因为resty.limit.req代码中控制粒度为毫秒级别,所以可以做到毫秒级别的平滑处理
       local lim, err = limit_req.new("my_limit_req_store", 2, 60)
       if not lim then
           ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
           return ngx.exit(500)
       end
​
       local key = ngx.var.binary_remote_addr
       local delay, err = lim:incoming(key, true)
       if not delay then
           if err == "rejected" then
               return ngx.exit(503)
           end
           ngx.log(ngx.ERR, "failed to limit req: ", err)
           return ngx.exit(500)
       end
       
       -- 此方法返回,当前请求需要delay秒后才会被处理,和他前面对请求数
       -- 所以此处对桶中请求进行延时处理,让其排队等待,就是应用了漏桶算法
       -- 此处也是与令牌桶的主要区别既
       if delay >= 0.001 then
           ngx.sleep(delay)
       end
   }
​
   proxy_pass http://192.168.10.111:6666;
   proxy_set_header Host $host;
   proxy_redirect off;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_connect_timeout 60;
   proxy_read_timeout 600;
   proxy_send_timeout 600;
}

分布式api限流实现代码:   链接: https://pan.baidu.com/s/1-1vY7KgIooQ8cOVS67UOWQ 提取码: wrqh 

你可能感兴趣的:(高并发系统的三把利剑(缓存,降级,限流))