Limiting high-availability architecture of the downgrade

 We often say that the N 9, is a description of the SLA.

SLA stands for ServiceLevel Agreement, translated into service level agreements, also known as service level agreements, it shows the level of public cloud service delivery and quality.

External commitments such as Ali cloud is a service within the cluster service availability period of not less than 99.99%, if less than this standard, a cloud service company needs to compensate the loss of customers.

 

1.1 4 9 do a good enough yet

 

Internet companies, SLA website or API is a service availability guarantee.

9 more year-round service on behalf of the longer time available services more reliable, four service availability 9, it sounds already high, but the actual business scenarios, this might not be enough.

We do a simple calculation, assuming a link depend core 20 service, and there is no strong dependence configure any downgrade, and the availability of this service reached 20 4 9, that is 99.99%,

That link is only the availability of a core of 20 th 99.99 = 99.8%,

  • If there are 10 billion times the request has failed requests 3,000,000 times

  • Ideally, still has 17-hour service is not available every year

     

     

This is an ideal estimate, in the actual production environment, the service released a variety of reasons such as downtime, this situation would surely be worse,

For some sensitive business operations, such as finance, service or stable demanding industries, such as order or payment services, this situation is unacceptable.

 

Avalanche 1.2 micro services

 

In addition to the pursuit of service availability, a micro-service architecture around the past avalanche problem is the service.

On a call link, micro-service architecture between the various services constitute a whole loose, indeed affect the whole body,

Service is a multi-stage avalanche conduction process, starting with a service provider is not available, due to the large number of timeouts waiting, caller then lead the service unavailable and conduction across the link, and then lead to system collapse.

 

 

 

 

 

Second, limiting how do downgrade

 

As our analysis above, in the large-scale micro-services architecture scene, avoid avalanche service, to reduce downtime and improve service availability should as far as possible.

Improve service availability, we can start from many directions, such as caching, pooling, asynchronous technology, load balancing, and queue downgrade fuse and other means.

  • And queuing buffer means to increase capacity of the system

  • And limiting concern is degraded upon arrival response system bottlenecks, more attention to stability

 

缓存和异步等提高系统的战力,限流降级关注的是防御。

限流和降级,具体实施方法可以归纳为八字箴言,分别是限流,降级,熔断和隔离。

 

2.1 限流和降级

限流顾名思义,提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。

限流需要结合压测等,了解系统的最高水位,也是在实际开发中应用最多的一种稳定性保障手段。

 

降级则是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。

从降级配置方式上,降级一般可以分为主动降级和自动降级。

主动降级是提前配置,自动降级则是系统发生故障时,如超时或者频繁失败,自动降级。

其中,自动降级,又可以分为以下策略:

  • 超时降级

  • 失败次数降级

  • 故障降级

 

在系统设计中,降级一般是结合系统配置中心,通过配置中心进行推送,下面是一个典型的降级通知设计

2.2 熔断隔离

 

如果某个目标服务调用慢或者有大量超时,此时熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。

熔断一般需要设置不同的恢复策略,如果目标服务情况好转则恢复调用。

 

服务隔离与前面的三个略有区别,我们的系统通常提供了不止一个服务,但是这些服务在运行时是部署在一个实例,或者一台物理机上面的,

如果不对服务资源做隔离,一旦一个服务出现了问题,整个系统的稳定性都会受到影响!

服务隔离的目的就是避免服务之间相互影响。

 

 

 

一般来说,隔离要关注两方面,一个是在哪里进行隔离,另外一个是隔离哪些资源。

  • 何处隔离

一次服务调用,涉及到的是服务提供方和调用方,我们所指的资源,也是两方的服务器等资源,服务隔离通常可以从提供方和调用方两个方面入手。

  • 隔离什么

广义的服务隔离,不仅包括服务器资源,还包括数据库分库,缓存,索引等,这里我们只关注服务层面的隔离。

 

2.3 降级和熔断的区别

服务降级和熔断在概念上比较相近,通过两个场景,谈谈我自己的理解。

  • 熔断,一般是停止服务

典型的就是股市的熔断,如果大盘不受控制,直接休市,不提供服务,是保护大盘的一种方式。

  • 降级,通常是有备用方案

从北京到济南,下雨导致航班延误,我可以乘坐高铁,如果高铁票买不到,也可以乘坐汽车或者开车过去。

  • 两者的区别

降级一般是主动的,有预见性的,熔断通常是被动的,

服务A降级以后,一般会有服务B来代替,而熔断通常是针对核心链路的处理。

在实际开发中,熔断的下一步通常就是降级。

 

三、常用限流算法设计

 

刚才讲了限流的概念,那么怎样判断系统到达设置的流量阈值了?

这就需要一些限流策略来支持,不同的限流算法有不同的特点,平滑程度也不同。

 

3.1 计数器法

计数器法是限流算法里最简单也是最容易实现的一种算法。

假设一个接口限制一分钟内的访问次数不能超过100个,维护一个计数器,每次有新的请求过来,计数器加一,这时候判断,如果计数器的值小于限流值,并且与上一次请求的时间间隔还在一分钟内,

允许请求通过,否则拒绝请求,如果超出了时间间隔,要将计数器清零。

 
public class CounterLimiter {
​
    //初始时间
    private static long startTime = System.currentTimeMillis();
​
    //初始计数值
    private static final AtomicInteger ZERO = new AtomicInteger(0);
​
    //时间窗口限制
    private static final long interval = 10000;
​
    //限制通过请求
    private static int limit = 100;
​
    //请求计数
    private AtomicInteger requestCount = ZERO;
​
    //获取限流
    public boolean tryAcquire() {
​
        long now = System.currentTimeMillis();
​
        //在时间窗口内
        if (now < startTime + interval) {
​
            //判断是否超过最大请求
            if (requestCount.get() < limit) {
                requestCount.incrementAndGet();
                return true;
            }
            return false;
​
        } else {
​
            //超时重置
            startTime = now;
            requestCount = ZERO;
            return true;
        }
​
    }
}

  


计数器限流可以比较容易的应用在分布式环境中,用一个单点的存储来保存计数值,比如用Redis,并且设置自动过期时间,这时候就可以统计整个集群的流量,并且进行限流。

计数器方式的缺点是不能处理临界问题,或者说限流策略不够平滑。

假设在限流临界点的前后,分别发送100个请求,实际上在计数器置0前后的极短时间里,处理了200个请求,这是一个瞬时的高峰,可能会超过系统的限制。

计数器限流允许出现 2*permitsPerSecond 的突发流量,可以使用滑动窗口算法去优化,具体不展开。

 

 

3.2 漏桶算法

假设我们有一个固定容量的桶,桶底部可以漏水(忽略气压等,不是物理问题),并且这个漏水的速率可控的,那么我们可以通过这个桶来控制请求速度,也就是漏水的速度。

我们不关心流进来的水,也就是外部请求有多少,桶满了之后,多余的水会溢出。

漏桶算法的示意图如下:

 

 

将算法中的水换成实际应用中的请求,可以看到漏桶算法从入口限制了请求的速度。使用漏桶算法,我们可以保证接口会以一个常速速率来处理请求,所以漏桶算法不会出现临界问题。

这里简单实现一下,也可以使用Guava的SmoothWarmingUp类,可以更好的控制漏桶算法,

 

public class LeakyLimiter {
​
    //桶的容量
    private int capacity;
​
    //漏水速度
    private int ratePerMillSecond;
​
    //水量
    private double water;
​
    //上次漏水时间
    private long lastLeakTime;
​
    public LeakyLimiter(int capacity, int ratePerMillSecond) {
​
        this.capacity = capacity;
        this.ratePerMillSecond = ratePerMillSecond;
        this.water = 0;
    }
​
​
    //获取限流
    public boolean tryAcquire() {
​
        //执行漏水,更新剩余水量
        refresh();
​
        //尝试加水,水满则拒绝
        if (water + 1 > capacity) {
            return false;
        }
​
        water = water + 1;
        return true;
​
    }
​
    private void refresh() {
        //当前时间
        long currentTime = System.currentTimeMillis();
​
        if (currentTime > lastLeakTime) {
​
            //距上次漏水的时间间隔
            long millisSinceLastLeak = currentTime - lastLeakTime;
            long leaks = millisSinceLastLeak * ratePerMillSecond;
​
            //允许漏水
            if (leaks > 0) {
                //已经漏光
                if (water <= leaks) {
                    water = 0;
                } else {
                    water = water - leaks;
                }
                this.lastLeakTime = currentTime;
            }
        }
    }
}

  

 

3.3 令牌桶算法

 

漏桶是控制水流入的速度,令牌桶则是控制留出,通过控制token,调节流量。

假设一个大小恒定的桶,桶里存放着令牌(token)。桶一开始是空的,现在以一个固定的速率往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。

如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小,

每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。

 

 

 

 

public class TokenBucketLimiter {
​
    private long capacity;
    private long windowTimeInSeconds;
    long lastRefillTimeStamp;
    long refillCountPerSecond;
    long availableTokens;
​
    public TokenBucketLimiter(long capacity, long windowTimeInSeconds) {
        this.capacity = capacity;
        this.windowTimeInSeconds = windowTimeInSeconds;
        lastRefillTimeStamp = System.currentTimeMillis();
        refillCountPerSecond = capacity / windowTimeInSeconds;
        availableTokens = 0;
    }
​
    public long getAvailableTokens() {
        return this.availableTokens;
    }
​
    public boolean tryAcquire() {
​
        //更新令牌桶
        refill();
​
        if (availableTokens > 0) {
            --availableTokens;
            return true;
        } else {
            return false;
        }
    }
​
​
    private void refill() {
        long now = System.currentTimeMillis();
​
        if (now > lastRefillTimeStamp) {
​
            long elapsedTime = now - lastRefillTimeStamp;
​
            int tokensToBeAdded = (int) ((elapsedTime / 1000) * refillCountPerSecond);
​
            if (tokensToBeAdded > 0) {
                availableTokens = Math.min(capacity, availableTokens + tokensToBeAdded);
                lastRefillTimeStamp = now;
            }
        }
    }
​
}

  

 

 

这两种算法的主要区别在于漏桶算法能够强行限制数据的传输速率,而令牌桶算法在能够限制数据的平均传输速率外,还允许某种程度的突发传输。

在令牌桶算法中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。

 

3.4 漏桶和令牌桶的比较

漏桶和令牌桶算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

主要区别在于令牌桶允许一定程度的突发,漏桶主要目的是平滑流入速率,考虑一个临界场景,令牌桶内积累了100个token,可以在一瞬间通过,但是因为下一秒产生token的速度是固定的,

所以令牌桶允许出现瞬间出现permitsPerSecond的流量,但是不会出现2*permitsPerSecond的流量,漏桶的速度则始终是平滑的。

 

3.5 使用RateLimiter实现限流

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法实现流量限制,使用方便。

RateLimiter使用的是令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌,看下方法的说明:

 

修饰符和类型

方法和描述

修饰符和类型

方法和描述

double

acquire()
从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求

double

acquire(int permits)
从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求

static RateLimiter

create(double permitsPerSecond)
根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)

static RateLimiter

create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)

double

getRate()
返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数

void

setRate(double permitsPerSecond)
更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。

boolean

tryAcquire()
从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话

boolean

tryAcquire(int permits)
从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话

boolean

tryAcquire(int permits, long timeout, TimeUnit unit)
从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)

boolean

tryAcquire(long timeout, TimeUnit unit)
从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)

  

RateLimter提供的API可以直接应用,其中acquire会阻塞,类似JUC的信号量Semphore,tryAcquire方法则是非阻塞的:

public class RateLimiterTest {
​
    public static void main(String[] args) throws InterruptedException {
​
        //允许10个,permitsPerSecond
        RateLimiter limiter = RateLimiter.create(10);
​
        for(int i=1;i<20;i++){
            if (limiter.tryAcquire(1)){
                System.out.println("第"+i+"次请求成功");
            }else{
                System.out.println("第"+i+"次请求拒绝");
            }
        }
    }
}
​

  

 四、总结

本文从服务可用性开始,分析了在业务高峰期通过限流降级保障服务高可用的重要性。

接下来分别探讨了限流,降级,熔断,隔离的概念和应用,并且介绍了常用的限流策略,图片引用网络和维基百科

 

参考资料

阿里云服务器 ECS服务等级协议

接口限流算法总结

Guava Docs

How-it-Works

https://en.wikipedia.org/wiki/Token_bucket

 

Guess you like

Origin www.cnblogs.com/binyue/p/11596763.html