zoukankan      html  css  js  c++  java
  • 服务器端限流保护

    之前开发一个视频聊天室服务,碰到许多服务器端高并发性能的问题,所以进行了一些这方面的学习。服务器端在收到客户端过多的请求时,往往会因为过高的cpu或者内存消耗而宕掉。这就有一个原则是服务端要能自我保护,宁可提供受损的服务,也不能不提供服务。在开发高并发系统时有三把利器来保护系统:缓存降级限流

    缓存的目的是提升系统访问速度和增大系统能处理的容量。项目中有很多地方用到了缓存。其中一个场景是,客户端会上传一个信令表明身份,而这个信令有专门的服务来维护,我们就需要拿着这个信令调用那个服务的http接口来验证,当并发较多时,http请求就有可能拥堵。考虑到这个信令的存活时间较长,就可以在redis中做一下缓存,定期清理。这样就减少了http请求次数。

    降级是当服务出问题或者影响到核心流程的性能时,需要暂时屏蔽掉一些服务,待高峰或者问题解决后再打开。这个方法基本上每一个服务都用到了,高峰时或者cpu、内存占用较高时,减少日志记录、主动拒绝一些请求等等。

    限流的目的是通过对并发访问/请求进行限制或者一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务或者排队等待。

    令牌桶算法

    令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。简单描述如下:

    • 假设限制2r/s,则按照500ms的固定速率往桶中添加令牌;
    • 同种最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
    • 当n个请求到来时,从同种删除n个令牌,接着处理请求;
    • 如果桶中的令牌不足n个,则拒绝后面的部分

    令牌桶

    漏桶算法

    漏桶作为计量工具时,可以用于流量整形和流量控制。简单描述如下:

    • 一个固定容量的漏桶,按照常量固定速率流出水滴;
    • 如果桶是空的,则不需要流出水滴;
    • 可以以任意的速率流入水滴;
    • 如果流入水滴超出了桶的容量,则流入的水滴溢出,被丢弃。

    漏桶

    对比这两个算法

    • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶里令牌是否足够,当令牌数是零时拒绝新的请求。
    • 漏桶是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数量累计到漏桶容量时,则新流入的请求被拒绝。
    • 令牌桶限制的是单位时间内的平均流入速率,但是允许突发请求
    • 漏桶限制的是流出速度,也就是处理速度,碰到突发请求,也是按固定速率处理这些请求,平滑突发流入速率

    我们这次优化只用到了漏桶算法。其实实现很简单,就是用到一个队列。队列的一端不断添加请求,另一端按照固定的速率,例如每秒1个,来从队列中读取请求处理。如果队列满了,可以选择一种策略来处理新来的请求,一是直接丢弃新请求,二是覆盖旧的请求。这个队列也可以改为用栈来实现,具体情况具体分析。

    计数器限流

    主要用来限制总并发数,对全局总请求数或者单位时间内的总请求数进行限流。这个方法比较简单。

    应用级限流

    • 限制总并发/连接/请求数。例如Tomcat、Nginx、Redis可以对线程数、连接数做限制。
    • 限制总资源数。如果有的资源是稀缺自然,而且可能有多个系统都会使用它,那么需要限制应用。最常用的是池化技术,线程池或者连接池。
    • 限流某个接口的总并发/请求数。在Java中可以用AtomicLong进行计数限制。
    • 限流某个接口的时间窗请求数。这和上面的相似,可以用一个计数器进行限制。
    • 平滑限流某个接口的请求数。上面的两种方法都不能很好的应对突发请求,但有时候会需要对突发请求做一些平滑整形。这时候可以用令牌桶或者漏桶算法。

    分布式限流

    分布式限流最关键的是讲限流服务做成原子化,而解决方案可以使用redis+lua或者nginx+lua。我对nginx不熟,而且redis性能很好,所以偏向于使用redis。

    local key = KEYS[1] --限流KEY(一秒一个)
    local limit = tonumber(ARGV[1])        --限流大小
    local current = tonumber(redis.call('get', key) or "0")
    if current + 1 > limit then --如果超出限流大小
       return 0
    else if current == 1 then --设置2秒过期
        redis.call("INCRBY", key, "1")
        redis.call("EXPIRE", key, "2")
    else  --请求数+1
       redis.call("INCRBY", key,"1")
       return 1
    end
    

    参考

  • 相关阅读:
    微信公众号的开通
    支付宝同步请求检查appid,以及公钥,私钥是否正确
    检查支付宝异步通知是否正确
    支付宝开发者中心添加应用
    js中undefined和null的区别
    判断前端<input>标签是否选中(批量删除时,批量选择复选框)
    P1396 营救+二分+BFS
    P1396 营救+生成树
    P1396 营救+最短路
    P1195 口袋的天空
  • 原文地址:https://www.cnblogs.com/xianzhedeyu/p/5868024.html
Copyright © 2011-2022 走看看