文章目录


  • 一、问题描述
  • 二、常用的限流算法
    • 1.漏桶算法
    • 2.令牌桶算法
  • 三、限流工具类RateLimiter
  • 四、适用场景
  • 五、服务治理---限流(令牌桶算法)
  • 六、高并发系统限流中的漏桶算法和令牌桶算法,通过流量整形和速率限制提升稳定性
  • 七、聊聊高并发系统之限流特技
    • **限流算法**
    • **应用级限流**
    • **分布式限流**
    • **接入层限流**
      • **ngx\_http\_limit\_conn\_module**
      • **ngx\_http\_limit\_req\_module**
      • **lua-resty-limit-traffic**
  • 系统监控和流控-java应用


参考:
原文链接
http://www.cnblogs.com/LBSer/p/4083131.html
https://blog.csdn.net/scorpio3k/article/details/53103239
http://jinnianshilongnian.iteye.com/blog/2305117
http://iamzhongyong.iteye.com/blog/1742829
https://blog.csdn.net/qianshangding0708/article/details/104026669
https://blog.csdn.net/qianshangding0708/article/details/104026669



一、问题描述

某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的接口也需要安装上“保险丝”,以防止非预期的请求对系统压力过大而引起的系统瘫痪,当流量过大时,可以采取拒绝或者引流等机制。


二、常用的限流算法

1.漏桶算法

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

2.令牌桶算法

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。如图2所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。


三、限流工具类RateLimiter

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法来完成限流,非常易于使用。RateLimiter类的接口描述请参考:RateLimiter接口描述,具体使用请参考:RateLimiter使用实践。 下面是主要源码:

public double acquire() {return acquire(1);}public double acquire(int permits) {checkPermits(permits);  //检查参数是否合法(是否大于0)long microsToWait;synchronized (mutex) { //应对并发情况需要同步microsToWait = reserveNextTicket(permits, readSafeMicros()); //获得需要等待的时间 }ticker.sleepMicrosUninterruptibly(microsToWait); //等待,当未达到限制时,microsToWait为0return 1.0 * microsToWait / TimeUnit.SECONDS.toMicros(1L);}private long reserveNextTicket(double requiredPermits, long nowMicros) {resync(nowMicros); //补充令牌long microsToNextFreeTicket = nextFreeTicketMicros - nowMicros;double storedPermitsToSpend = Math.min(requiredPermits, this.storedPermits); //获取这次请求消耗的令牌数目double freshPermits = requiredPermits - storedPermitsToSpend;long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)+ (long) (freshPermits * stableIntervalMicros); this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;this.storedPermits -= storedPermitsToSpend; // 减去消耗的令牌return microsToNextFreeTicket;}private void resync(long nowMicros) {// if nextFreeTicket is in the past, resync to nowif (nowMicros > nextFreeTicketMicros) {storedPermits = Math.min(maxPermits,storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);nextFreeTicketMicros = nowMicros;}}


四、适用场景

总结起来:如果要让自己的系统不被打垮,用令牌桶。如果保证别人的系统不被打垮,用漏桶算法

1.并不能说明令牌桶一定比漏洞好,她们使用场景不一样。令牌桶可以用来保护自己,主要用来对调用者频率进行限流,为的是让自己不被打垮。所以如果自己本身有处理能力的时候,如果流量突发(实际消费能力强于配置的流量限制),那么实际处理速率可以超过配置的限制。
2.而漏桶算法,这是用来保护他人,也就是保护他所调用的系统。主要场景是,当调用的第三方系统本身没有保护机制,或者有流量限制的时候,我们的调用速度不能超过他的限制,由于我们不能更改第三方系统,所以只有在主调方控制。这个时候,即使流量突发,也必须舍弃。因为消费能力是第三方决定的。


五、服务治理—限流(令牌桶算法)

1、最近在写一个分布式服务的框架,对于分布式服务的框架来说,除了远程调用,还要进行服务的治理,当进行促销的时候,所有的资源都用来完成重要的业务,就比如双11的时候,主要的业务就是让用户查询商品,以及购买支付,此时,金币查询、积分查询等业务就是次要的,因此要对这些服务进行服务的降级,典型的服务降级算法是采用令牌桶算法,因此在写框架的时候去研究了一下令牌桶算法

2、在实施QOS策略时,可以将用户的数据限制在特定的带宽,当用户的流量超过额定带宽时,超过的带宽将采取其它方式来处理。要衡量流量是否超过额定的带宽,网络设备并不是采用单纯的数字加减法来决定的,也就是说,比如带宽为100K,而用户发来的流量为110K,网络设备并不是靠110K减去100K等于10K,就认为用户超过流量10K。网络设备衡量流量是否超过额定带宽,需要使用令牌桶算法来计算。下面详细介绍令牌桶算法机制:

当网络设备衡量流量是否超过额定带宽时,需要查看令牌桶,而令牌桶中会放置一定数量的令牌,一个令牌允许接口发送或接收1bit数据(有时是1 Byte数据),当接口通过1bit数据后,同时也要从桶中移除一个令牌。当桶里没有令牌的时候,任何流量都被视为超过额定带宽,只有当桶中有令牌时,数据才可以通过接口。令牌桶中的令牌不仅仅可以被移除,同样也可以往里添加,所以为了保证接口随时有数据通过,就必须不停地往桶里加令牌,由此可见,往桶里加令牌的速度,就决定了数据通过接口的速度。因此,我们通过控制往令牌桶里加令牌的速度从而控制用户流量的带宽。而设置的这个用户传输数据的速率被称为承诺信息速率(CIR),通常以秒为单位。比如我们设置用户的带宽为1000 bit每秒,只要保证每秒钟往桶里添加1000个令牌即可。

3、举例:将CIR设置为8000 bit/s,那么就必须每秒将8000个令牌放入桶中,当接口有数据通过时,就从桶中移除相应的令牌,每通过1 bit,就从桶中移除1个令牌。当桶里没有令牌的时候,任何流量都被视为超出额定带宽,而超出的流量就要采取额外动作。每秒钟往桶里加的令牌,就决定了用户流量的速率,这个速率就是CIR,但是每秒钟需要往桶里加的令牌总数,并不是一次性加完的,一次性加进的令牌数量被称为Burst size(Bc),如果Bc只是CIR的一半,那么很明显每秒钟就需要往桶里加两次令牌,每次加的数量总是Bc的数量。还有就是加令牌的时间,Time interval(Tc),Tc表示多久该往桶里加一次令牌,而这个时间并不能手工设置,因为这个时间可以靠CIR和Bc的关系计算得到, Bc/ CIR= Tc。

4、令牌桶算法图例

a. 按特定的速率向令牌桶投放令牌
b. 根据预设的匹配规则先对报文进行分类,不符合匹配规则的报文不需要经过令牌桶的处理,直接发送;
c. 符合匹配规则的报文,则需要令牌桶进行处理。当桶中有足够的令牌则报文可以被继续发送下去,同时令牌桶中的令牌 量按报文的长度做相应的减少;
d. 当令牌桶中的令牌不足时,报文将不能被发送,只有等到桶中生成了新的令牌,报文才可以发送。这就可以限制报文的流量只能是小于等于令牌生成的速度,达到限制流量的目的。

5、Java参考代码:

package com.netease.datastream.util.flowcontrol;import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;/*** <pre>* Created by inter12 on 15-3-18.* </pre>*/
public class TokenBucket {// 默认桶大小个数 即最大瞬间流量是64Mprivate static final int DEFAULT_BUCKET_SIZE = 1024 * 1024 * 64;// 一个桶的单位是1字节private int everyTokenSize = 1;// 瞬间最大流量private int maxFlowRate;// 平均流量private int avgFlowRate;// 队列来缓存桶数量:最大的流量峰值就是 = everyTokenSize*DEFAULT_BUCKET_SIZE 64M = 1 * 1024 *// 1024 * 64private ArrayBlockingQueue<Byte> tokenQueue = new ArrayBlockingQueue<Byte>(DEFAULT_BUCKET_SIZE);private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();private volatile boolean isStart = false;private ReentrantLock lock = new ReentrantLock(true);private static final byte A_CHAR = 'a';public TokenBucket() {}public TokenBucket(int maxFlowRate, int avgFlowRate) {this.maxFlowRate = maxFlowRate;this.avgFlowRate = avgFlowRate;}public TokenBucket(int everyTokenSize, int maxFlowRate, int avgFlowRate) {this.everyTokenSize = everyTokenSize;this.maxFlowRate = maxFlowRate;this.avgFlowRate = avgFlowRate;}public void addTokens(Integer tokenNum) {// 若是桶已经满了,就不再家如新的令牌for (int i = 0; i < tokenNum; i++) {tokenQueue.offer(Byte.valueOf(A_CHAR));}}public TokenBucket build() {start();return this;}/*** 获取足够的令牌个数* * @return*/public boolean getTokens(byte[] dataSize) {//        Preconditions.checkNotNull(dataSize);
//        Preconditions.checkArgument(isStart,
//                "please invoke start method first !");int needTokenNum = dataSize.length / everyTokenSize + 1;// 传输内容大小对应的桶个数final ReentrantLock lock = this.lock;lock.lock();try {boolean result = needTokenNum <= tokenQueue.size(); // 是否存在足够的桶数量if (!result) {return false;}int tokenCount = 0;for (int i = 0; i < needTokenNum; i++) {Byte poll = tokenQueue.poll();if (poll != null) {tokenCount++;}}return tokenCount == needTokenNum;} finally {lock.unlock();}}public void start() {// 初始化桶队列大小if (maxFlowRate != 0) {tokenQueue = new ArrayBlockingQueue<Byte>(maxFlowRate);}// 初始化令牌生产者TokenProducer tokenProducer = new TokenProducer(avgFlowRate, this);scheduledExecutorService.scheduleAtFixedRate(tokenProducer, 0, 1,TimeUnit.SECONDS);isStart = true;}public void stop() {isStart = false;scheduledExecutorService.shutdown();}public boolean isStarted() {return isStart;}class TokenProducer implements Runnable {private int avgFlowRate;private TokenBucket tokenBucket;public TokenProducer(int avgFlowRate, TokenBucket tokenBucket) {this.avgFlowRate = avgFlowRate;this.tokenBucket = tokenBucket;}@Overridepublic void run() {tokenBucket.addTokens(avgFlowRate);}}public static TokenBucket newBuilder() {return new TokenBucket();}public TokenBucket everyTokenSize(int everyTokenSize) {this.everyTokenSize = everyTokenSize;return this;}public TokenBucket maxFlowRate(int maxFlowRate) {this.maxFlowRate = maxFlowRate;return this;}public TokenBucket avgFlowRate(int avgFlowRate) {this.avgFlowRate = avgFlowRate;return this;}private String stringCopy(String data, int copyNum) {StringBuilder sbuilder = new StringBuilder(data.length() * copyNum);for (int i = 0; i < copyNum; i++) {sbuilder.append(data);}return sbuilder.toString();}public static void main(String[] args) throws IOException,InterruptedException {tokenTest();}private static void arrayTest() {ArrayBlockingQueue<Integer> tokenQueue = new ArrayBlockingQueue<Integer>(10);tokenQueue.offer(1);tokenQueue.offer(1);tokenQueue.offer(1);System.out.println(tokenQueue.size());System.out.println(tokenQueue.remainingCapacity());}private static void tokenTest() throws InterruptedException, IOException {TokenBucket tokenBucket = TokenBucket.newBuilder().avgFlowRate(512).maxFlowRate(1024).build();BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:/ds_test")));String data = "xxxx";// 四个字节for (int i = 1; i <= 1000; i++) {Random random = new Random();int i1 = random.nextInt(100);boolean tokens = tokenBucket.getTokens(tokenBucket.stringCopy(data,i1).getBytes());TimeUnit.MILLISECONDS.sleep(100);if (tokens) {bufferedWriter.write("token pass --- index:" + i1);System.out.println("token pass --- index:" + i1);} else {bufferedWriter.write("token rejuect --- index" + i1);System.out.println("token rejuect --- index" + i1);}bufferedWriter.newLine();bufferedWriter.flush();}bufferedWriter.close();}}


六、高并发系统限流中的漏桶算法和令牌桶算法,通过流量整形和速率限制提升稳定性

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

一、漏桶和令牌桶算法的概念

漏桶算法(Leaky Bucket):主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。漏桶算法的示意图如下:


请求先进入到漏桶里,漏桶以一定的速度出水,当水请求过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

令牌桶算法(Token Bucket):是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。令牌桶算法示意图如下所示:


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

二、两种算法的区别

两者主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,所以它适合于具有突发特性的流量。

三、使用Guava的RateLimiter进行限流控制

Guava是google提供的java扩展类库,其中的限流工具类RateLimiter采用的就是令牌桶算法。RateLimiter 从概念上来讲,速率限制器会在可配置的速率下分配许可证,如果必要的话,每个acquire() 会阻塞当前线程直到许可证可用后获取该许可证,一旦获取到许可证,不需要再释放许可证。通俗的讲RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌。例如我们需要处理一个任务列表,但我们不希望每秒的任务提交超过两个,此时可以采用如下方式:


有一点很重要,那就是请求的许可数从来不会影响到请求本身的限制(调用acquire(1) 和调用acquire(1000) 将得到相同的限制效果,如果存在这样的调用的话),但会影响下一次请求的限制,也就是说,如果一个高开销的任务抵达一个空闲的RateLimiter,它会被马上许可,但是下一个请求会经历额外的限制,从而来偿付高开销任务。注意:RateLimiter 并不提供公平性的保证。

四、使用Semphore进行并发流控

Java 并发库的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。下面的Demo中申明了一个只有5个许可的Semaphore,而有20个线程要访问这个资源,通过acquire()和release()获取和释放访问许可:

最后:进行限流控制还可以有很多种方法,针对不同的场景各有优劣,例如通过AtomicLong计数器控制、使用MQ消息队列进行流量削峰【通过配置消费者线程池】等等。


七、聊聊高并发系统之限流特技

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。

限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。

一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。

先有缓存这个银弹,后有限流来应对618、双十一高并发流量,在处理高并发问题上可以说是如虎添翼,不用担心瞬间流量导致系统挂掉或雪崩,最终做到有损服务而不是不服务;限流需要评估好,不可乱用,否则会正常流量出现一些奇怪的问题而导致用户抱怨。

在实际应用时也不要太纠结算法问题,因为一些限流算法实现是一样的只是描述不一样;具体使用哪种限流技术还是要根据实际场景来选择,不要一味去找最佳模式,白猫黑猫能解决问题的就是好猫。

因在实际工作中遇到过许多人来问如何进行限流,因此本文会详细介绍各种限流手段。那么接下来我们从限流算法、应用级限流、分布式限流、接入层限流来详细学习下限流技术手段。

限流算法

常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。

令牌桶算法

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

  • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;

  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;

  • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;

  • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

漏桶算法

漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;

  • 如果桶是空的,则不需流出水滴;

  • 可以以任意速率流入水滴到漏桶;

  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

令牌桶和漏桶对比:

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;

  • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;

  • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;

  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;

  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;

  • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

另外有时候我们还使用计数器来进行限流,主要用来限制总并发数,比如数据库连接池、线程池、秒杀的并发数;只要全局总请求数或者一定时间段的总请求数设定的阀值则进行限流,是简单粗暴的总数量限流,而不是平均速率限流。

到此基本的算法就介绍完了,接下来我们首先看看应用级限流。

应用级限流

**限流总并发/****连接/**请求数

对于一个应用系统来说一定会有极限并发/请求数,即总有一个TPS/QPS阀值,如果超了阀值则系统就会不响应用户请求或响应的非常慢,因此我们最好进行过载保护,防止大量请求涌入击垮系统。

如果你使用过Tomcat,其Connector 其中一种配置有如下几个参数:

acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;

maxConnections: 瞬时最大连接数,超出的会排队等待;

maxThreads:Tomcat能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死。

详细的配置请参考官方文档。另外如Mysql(如max_connections)、Redis(如tcp-backlog)都会有类似的限制连接数的配置。

限流总资源数

如果有的资源是稀缺资源(如数据库连接、线程),而且可能有多个系统都会去使用它,那么需要限制应用;可以使用池化技术来限制总资源数:连接池、线程池。比如分配给每个应用的数据库连接是100,那么本应用最多可以使用100个资源,超出了可以等待或者抛异常。

**限流某个接口的总并发/**请求数

如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务;这个时候就需要限制这个接口的总并发/请求数总请求数了;因为粒度比较细,可以为每个接口都设置相应的阀值。可以使用Java中的AtomicLong进行限流:

try {
if(atomic.incrementAndGet() > 限流数) {
//拒绝请求
}
//处理请求
} finally {
atomic.decrementAndGet();
}

适合对业务无损的服务或者需要过载保护的服务进行限流,如抢购业务,超出了大小要么让用户排队,要么告诉用户没货了,对用户来说是可以接受的。而一些开放平台也会限制用户调用某个接口的试用请求量,也可以用这种计数器方式实现。这种方式也是简单粗暴的限流,没有平滑处理,需要根据实际情况选择使用;

限流某个接口的时间窗请求数

即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数/调用量。如一些基础服务会被很多其他系统调用,比如商品详情页服务会调用基础商品服务调用,但是怕因为更新量比较大将基础服务打挂,这时我们要对每秒/每分钟的调用量进行限速;一种实现方式如下所示:

LoadingCache<Long, AtomicLong> counter =
CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(new CacheLoader<Long, AtomicLong>() {
@Override
public AtomicLong load(Long seconds) throws Exception {
return new AtomicLong(0);
}
});
long limit = 1000;
while(true) {
_//_得到当前秒 long currentSeconds = System.currentTimeMillis() / 1000;
if(counter.get(currentSeconds).incrementAndGet() > limit) {
System.out.println(**"**限流了:" + currentSeconds);
continue;
}
_//_业务处理
}

我们使用Guava的Cache来存储计数器,过期时间设置为2秒(保证1秒内的计数器是有的),然后我们获取当前时间戳然后取秒数来作为KEY进行计数统计和限流,这种方式也是简单粗暴,刚才说的场景够用了。

平滑限流某个接口的请求数

之前的限流方式都不能很好地应对突发请求,即瞬间请求可能都被允许从而导致一些问题;因此在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如5r/s,则每隔200毫秒处理一个请求,平滑了速率)。这个时候有两种算法满足我们的场景:令牌桶和漏桶算法。Guava框架提供了令牌桶算法实现,可直接拿来使用。

Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

SmoothBursty

RateLimiter limiter = RateLimiter.create(5);
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());

将得到类似如下的输出:

0.0

0.198239

0.196083

0.200609

0.199599

0.19961

1、RateLimiter.create(5) 表示桶容量为5且每秒新增5个令牌,即每隔200毫秒新增一个令牌;

2、limiter.acquire()表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为0),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌(如上测试用例返回的为0.198239,差不多等待了200毫秒桶中才有令牌可用),这种实现将突发请求速率平均为了固定请求速率。

再看一个突发示例:

RateLimiter limiter = RateLimiter.create(5);
System.out.println(limiter.acquire(5));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));

将得到类似如下的输出:

0.0

0.98745

0.183553

0.199909

limiter.acquire(5)表示桶的容量为5且每秒新增5个令牌,令牌桶算法允许一定程度的突发,所以可以一次性消费5个令牌,但接下来的limiter.acquire(1)将等待差不多1秒桶中才能有令牌,且接下来的请求也整形为固定速率了。

RateLimiter limiter = RateLimiter.create(5);
System.out.println(limiter.acquire(10));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));

将得到类似如下的输出:

0.0

1.997428

0.192273

0.200616

同上边的例子类似,第一秒突发了10个请求,令牌桶算法也允许了这种突发(允许消费未来的令牌),但接下来的limiter.acquire(1)将等待差不多2秒桶中才能有令牌,且接下来的请求也整形为固定速率了。

接下来再看一个突发的例子:

RateLimiter limiter = RateLimiter.create(2);
System.out.println(limiter.acquire());
Thread.sleep(2000L);
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());

将得到类似如下的输出:

0.0

0.0

0.0

0.0

0.499876

0.495799

1、创建了一个桶容量为2且每秒新增2个令牌;

2、首先调用limiter.acquire()消费一个令牌,此时令牌桶可以满足(返回值为0);

3、然后线程暂停2秒,接下来的两个limiter.acquire()都能消费到令牌,第三个limiter.acquire()也同样消费到了令牌,到第四个时就需要等待500毫秒了。

此处可以看到我们设置的桶容量为2(即允许的突发量),这是因为SmoothBursty中有一个参数:最大突发秒数(maxBurstSeconds)默认值是1s,突发量/桶容量=速率*maxBurstSeconds,所以本示例桶容量/突发量为2,例子中前两个是消费了之前积攒的突发量,而第三个开始就是正常计算的了。令牌桶算法允许将一段时间内没有消费的令牌暂存到令牌桶中,留待未来使用,并允许未来请求的这种突发。

SmoothBursty通过平均速率和最后一次新增令牌的时间计算出下次新增令牌的时间的,另外需要一个桶暂存一段时间内没有使用的令牌(即可以突发的令牌数)。另外RateLimiter还提供了tryAcquire方法来进行无阻塞或可超时的令牌消费。

因为SmoothBursty允许一定程度的突发,会有人担心如果允许这种突发,假设突然间来了很大的流量,那么系统很可能扛不住这种突发。因此需要一种平滑速率的限流工具,从而系统冷启动后慢慢的趋于平均固定速率(即刚开始速率小一些,然后慢慢趋于我们设置的固定速率)。Guava也提供了SmoothWarmingUp来实现这种需求,其可以认为是漏桶算法,但是在某些特殊场景又不太一样。

SmoothWarmingUp创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)

permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。

示例如下:

RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);
for(int i = 1; i < 5;i++) {
System.out.println(limiter.acquire());
}
Thread.sleep(1000L);
for(int i = 1; i < 5;i++) {
System.out.println(limiter.acquire());
}

将得到类似如下的输出:

0.0

0.51767

0.357814

0.219992

0.199984

0.0

0.360826

0.220166

0.199723

0.199555

速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

到此应用级限流的一些方法就介绍完了。假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流。因此我们需要分布式限流和接入层限流来解决这个问题。

分布式限流

分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使使用redis+lua或者nginx+lua技术进行实现,通过这两种技术可以实现的高并发和高性能。

首先我们来使用redis+lua实现时间窗内某个接口的请求数限流,实现了该功能后可以改造为限流总并发/请求数和限制总资源数。Lua本身就是一种编程语言,也可以使用它实现复杂的令牌桶或漏桶算法。

redis+lua实现中的lua脚本:

local key = KEYS[1] --限流KEY(一秒一个)
local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call(“INCRBY”, key, “1”)) --请求数+1
if current > limit then --如果超出限流大小
return 0
elseif current == 1 then --只有第一次访问需要设置2秒的过期时间
redis.call(“expire”, key,“2”)
end
return 1

如上操作因是在一个lua脚本中,又因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 --请求数+1,并设置2秒过期
redis.call(“INCRBY”, key,“1”)
redis.call(“expire”, key,“2”)
return 1
end

如下是Java中判断是否需要限流的代码:

public static boolean acquire() throws Exception {
String luaScript = Files.toString(new File(“limit.lua”), Charset.defaultCharset());
Jedis jedis = new Jedis(“192.168.147.52”, 6379);
String key = “ip:” + System.currentTimeMillis()/ 1000; //此处将当前时间戳取秒数
Stringlimit = “3”; //限流大小
return (Long)jedis.eval(luaScript,Lists.newArrayList(key), Lists.newArrayList(limit)) == 1;
}

因为Redis的限制(Lua中有写操作不能使用带随机性质的读操作,如TIME)不能在Redis Lua中使用TIME获取时间戳,因此只好从应用获取然后传入,在某些极端情况下(机器时钟不准的情况下),限流会存在一些小问题。

使用Nginx+Lua实现的Lua脚本:

local locks = require “resty.lock”
local function acquire()
local lock =locks:new(“locks”)
local elapsed, err =lock:lock(“limit_key”) --互斥锁
local limit_counter =ngx.shared.limit_counter --计数器
local key = “ip:” …os.time()
local limit = 5 --限流大小
local current =limit_counter:get(key)

if current ~= nil and current + 1> limit then --如果超出限流大小  lock:unlock()  return 0  
end  
if current == nil then  limit\_counter:set(key, 1, 1) --第一次需要设置过期时间,设置key的值为1,过期时间为1秒  
else  limit\_counter:incr(key, 1) --第二次开始加1即可  
end  
lock:unlock()  
return 1  

end
ngx.print(acquire())

实现中我们需要使用lua-resty-lock互斥锁模块来解决原子性问题(在实际工程中使用时请考虑获取锁的超时问题),并使用ngx.shared.DICT共享字典来实现计数器。如果需要限流则返回0,否则返回1。使用时需要先定义两个共享字典(分别用来存放锁和计数器数据):

Java代码 收藏代码

  1. http {
  2.  ……  
    
  3.  lua\_shared\_dict locks 10m;  
    
  4.  lua\_shared\_dict limit\_counter 10m;  
    
  5. }

有人会纠结如果应用并发量非常大那么redis或者nginx是不是能抗得住;不过这个问题要从多方面考虑:你的流量是不是真的有这么大,是不是可以通过一致性哈希将分布式限流进行分片,是不是可以当并发量太大降级为应用级限流;对策非常多,可以根据实际情况调节;像在京东使用Redis+Lua来限流抢购流量,一般流量是没有问题的。

对于分布式限流目前遇到的场景是业务上的限流,而不是流量入口的限流;流量入口限流应该在接入层完成,而接入层笔者一般使用Nginx。

接入层限流

接入层通常指请求流量的入口,该层的主要目的有:负载均衡、非法请求过滤、请求聚合、缓存、降级、限流、A/B测试、服务质量监控等等,可以参考笔者写的《使用Nginx+Lua(OpenResty)开发高性能Web应用》。

对于Nginx接入层限流可以使用Nginx自带了两个模块:连接数限流模块ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module。还可以使用OpenResty提供的Lua限流模块lua-resty-limit-traffic进行更复杂的限流场景。

limit_conn用来对某个KEY对应的总的网络连接数进行限流,可以按照如IP、域名维度进行限流。limit_req用来对某个KEY对应的请求的平均速率进行限流,并有两种用法:平滑模式(delay)和允许突发模式(nodelay)。

ngx_http_limit_conn_module

limit_conn是对某个KEY对应的总的网络连接数进行限流。可以按照IP来限制IP维度的总连接数,或者按照服务域名来限制某个域名的总连接数。但是记住不是每一个请求连接都会被计数器统计,只有那些被Nginx处理的且已经读取了整个请求头的请求连接才会被计数器统计。

配置示例:

http {
limit_conn_zone$binary_remote_addr zone=addr:10m;
limit_conn_log_level error;
limit_conn_status 503;

server {

location /limit {
limit_conn addr 1;
}

limit_conn:要配置存放KEY和计数器的共享内存区域和指定KEY的最大连接数;此处指定的最大连接数是1,表示Nginx最多同时并发处理1个连接;

limit_conn_zone:用来配置限流KEY、及存放KEY对应信息的共享内存区域大小;此处的KEY是“binary_remote_addr”其表示IP地址,也可以使用如binary\_remote\_addr”其表示IP地址,也可以使用如binary_remote_addrIP使server_name作为KEY来限制域名级别的最大连接数;

limit_conn_status:配置被限流后返回的状态码,默认返回503;

limit_conn_log_level:配置记录被限流后的日志级别,默认error级别。

limit_conn的主要执行过程如下所示:

1、请求进入后首先判断当前limit_conn_zone中相应KEY的连接数是否超出了配置的最大连接数;

2.1、如果超过了配置的最大大小,则被限流,返回limit_conn_status定义的错误状态码;

2.2、否则相应KEY的连接数加1,并注册请求处理完成的回调函数;

3、进行请求处理;

4、在结束请求阶段会调用注册的回调函数对相应KEY的连接数减1。

limt_conn可以限流某个KEY的总并发/请求数,KEY可以根据需要变化。

按照IP****限制并发连接数配置示例:

首先定义IP维度的限流区域:

limit_conn_zone $binary_remote_addrzone=perip:10m;

接着在要限流的location中添加限流逻辑:

location /limit {
limit_conn perip 2;
echo “123”;
}

即允许每个IP最大并发连接数为2。

使用AB测试工具进行测试,并发数为5个,总的请求数为5个:

ab -n 5 -c 5 http://localhost/limit

将得到如下access.log输出:

[08/Jun/2016:20:10:51+0800] [1465373451.802] 200

[08/Jun/2016:20:10:51+0800] [1465373451.803] 200

[08/Jun/2016:20:10:51 +0800][1465373451.803] 503

[08/Jun/2016:20:10:51 +0800][1465373451.803] 503

[08/Jun/2016:20:10:51 +0800][1465373451.803] 503

此处我们把access log格式设置为log_format main ‘[KaTeX parse error: Can't use function '\]' in math mode at position 12: time\_local\̲]̲ \[msec] $status’;分别是“日期 日期秒/毫秒值 响应状态码”。

如果被限流了,则在error.log中会看到类似如下的内容:

2016/06/08 20:10:51 [error] 5662#0: *5limiting connections by zone “perip”, client: 127.0.0.1, server: _,request: “GET /limit HTTP/1.0”, host: “localhost”

按照域名限制并发连接数配置示例:

首先定义域名维度的限流区域:

limit_conn_zone $ server_name zone=perserver:10m;

接着在要限流的location中添加限流逻辑:

location /limit {
limit_conn perserver 2;
echo “123”;
}

即允许每个域名最大并发请求连接数为2;这样配置可以实现服务器最大连接数限制。

ngx_http_limit_req_module

limit_req是漏桶算法实现,用于对指定KEY对应的请求进行限流,比如按照IP维度限制请求速率。

配置示例:

http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
limit_conn_log_level error;
limit_conn_status 503;

server {

location /limit {
limit_req zone=one burst=5 nodelay;
}

limit_req:配置限流区域、桶容量(突发容量,默认0)、是否延迟模式(默认延迟);

limit_req_zone:配置限流KEY、及存放KEY对应信息的共享内存区域大小、固定请求速率;此处指定的KEY是“$binary_remote_addr”表示IP地址;固定请求速率使用rate参数配置,支持10r/s和60r/m,即每秒10个请求和每分钟60个请求,不过最终都会转换为每秒的固定请求速率(10r/s为每100毫秒处理一个请求;60r/m,即每1000毫秒处理一个请求)。

limit_conn_status:配置被限流后返回的状态码,默认返回503;

limit_conn_log_level:配置记录被限流后的日志级别,默认error级别。

limit_req的主要执行过程如下所示:

1、请求进入后首先判断最后一次请求时间相对于当前时间(第一次是0)是否需要限流,如果需要限流则执行步骤2,否则执行步骤3;

2.1、如果没有配置桶容量(burst),则桶容量为0;按照固定速率处理请求;如果请求被限流,则直接返回相应的错误码(默认503);

2.2、如果配置了桶容量(burst>0)且延迟模式(没有配置nodelay);如果桶满了,则新进入的请求被限流;如果没有满则请求会以固定平均速率被处理(按照固定速率并根据需要延迟处理请求,延迟使用休眠实现);

2.3、如果配置了桶容量(burst>0)且非延迟模式(配置了nodelay);不会按照固定速率处理请求,而是允许突发处理请求;如果桶满了,则请求被限流,直接返回相应的错误码;

3、如果没有被限流,则正常处理请求;

4、Nginx会在相应时机进行选择一些(3个节点)限流KEY进行过期处理,进行内存回收。

场景2.1测试

首先定义IP维度的限流区域:

limit_req_zone $binary_remote_addrzone=test:10m rate=500r/s;

限制为每秒500个请求,固定平均速率为2毫秒一个请求。

接着在要限流的location中添加限流逻辑:

location /limit {
limit_req zone=test;
echo “123”;
}

即桶容量为0(burst默认为0),且延迟模式。

使用AB测试工具进行测试,并发数为2个,总的请求数为10个:

ab -n 10 -c 2 http://localhost/limit

将得到如下access.log输出:

[08/Jun/2016:20:25:56+0800] [1465381556.410] 200

[08/Jun/2016:20:25:56 +0800][1465381556.410] 503

[08/Jun/2016:20:25:56 +0800][1465381556.411] 503

[08/Jun/2016:20:25:56+0800] [1465381556.411] 200

[08/Jun/2016:20:25:56 +0800][1465381556.412] 503

[08/Jun/2016:20:25:56 +0800][1465381556.412] 503

虽然每秒允许500个请求,但是因为桶容量为0,所以流入的请求要么被处理要么被限流,无法延迟处理;另外平均速率在2毫秒左右,比如1465381556.410和1465381556.411被处理了;有朋友会说这固定平均速率不是1毫秒嘛,其实这是因为实现算法没那么精准造成的。

如果被限流在error.log中会看到如下内容:

2016/06/08 20:25:56 [error] 6130#0: *1962limiting requests, excess: 1.000 by zone “test”, client: 127.0.0.1,server: _, request: “GET /limit HTTP/1.0”, host:“localhost”

如果被延迟了在error.log(日志级别要INFO级别)中会看到如下内容:

2016/06/10 09:05:23 [warn] 9766#0: *97021delaying request, excess: 0.368, by zone “test”, client: 127.0.0.1,server: _, request: “GET /limit HTTP/1.0”, host:“localhost”

场景2.2****测试

首先定义IP维度的限流区域:

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

为了方便测试设置速率为每秒2个请求,即固定平均速率是500毫秒一个请求。

接着在要限流的location中添加限流逻辑:

location /limit {
limit_req zone=test burst=3;
echo “123”;
}

固定平均速率为500毫秒一个请求,通容量为3,如果桶满了新的请求被限流,否则可以进入桶中排队并等待(实现延迟模式)。

为了看出限流效果我们写了一个req.sh脚本:

ab -c 6 -n 6 http://localhost/limit
sleep 0.3
ab -c 6 -n 6 http://localhost/limit

首先进行6个并发请求6次URL,然后休眠300毫秒,然后再进行6个并发请求6次URL;中间休眠目的是为了能跨越2秒看到效果,如果看不到如下的效果可以调节休眠时间。

将得到如下access.log输出:

[09/Jun/2016:08:46:43+0800] [1465433203.959] 200

[09/Jun/2016:08:46:43 +0800][1465433203.959] 503

[09/Jun/2016:08:46:43 +0800][1465433203.960] 503

[09/Jun/2016:08:46:44+0800] [1465433204.450] 200

[09/Jun/2016:08:46:44+0800] [1465433204.950] 200

[09/Jun/2016:08:46:45 +0800][1465433205.453] 200

[09/Jun/2016:08:46:45 +0800][1465433205.766] 503

[09/Jun/2016:08:46:45 +0800][1465433205.766] 503

[09/Jun/2016:08:46:45 +0800][1465433205.767] 503

[09/Jun/2016:08:46:45+0800] [1465433205.950] 200

[09/Jun/2016:08:46:46+0800] [1465433206.451] 200

[09/Jun/2016:08:46:46+0800] [1465433206.952] 200

桶容量为3,即桶中在时间窗口内最多流入3个请求,且按照2r/s的固定速率处理请求(即每隔500毫秒处理一个请求);桶计算时间窗口(1.5秒)=速率(2r/s)/桶容量(3),也就是说在这个时间窗口内桶最多暂存3个请求。因此我们要以当前时间往前推1.5秒和1秒来计算时间窗口内的总请求数;另外因为默认是延迟模式,所以时间窗内的请求要被暂存到桶中,并以固定平均速率处理请求:

第一轮:有4个请求处理成功了,按照漏桶桶容量应该最多3个才对;这是因为计算算法的问题,第一次计算因没有参考值,所以第一次计算后,后续的计算才能有参考值,因此第一次成功可以忽略;这个问题影响很小可以忽略;而且按照固定500毫秒的速率处理请求。

第二轮:因为第一轮请求是突发来的,差不多都在1465433203.959时间点,只是因为漏桶将速率进行了平滑变成了固定平均速率(每500毫秒一个请求);而第二轮计算时间应基于1465433203.959;而第二轮突发请求差不多都在1465433205.766时间点,因此计算桶容量的时间窗口应基于1465433203.959和1465433205.766来计算,计算结果为1465433205.766这个时间点漏桶为空了,可以流入桶中3个请求,其他请求被拒绝;又因为第一轮最后一次处理时间是1465433205.453,所以第二轮第一个请求被延迟到了1465433205.950。这里也要注意固定平均速率只是在配置的速率左右,存在计算精度问题,会有一些偏差。

如果桶容量改为1(burst=1),执行req.sh脚本可以看到如下输出:

09/Jun/2016:09:04:30+0800] [1465434270.362] 200

[09/Jun/2016:09:04:30 +0800][1465434270.371] 503

[09/Jun/2016:09:04:30 +0800] [1465434270.372]503

[09/Jun/2016:09:04:30 +0800][1465434270.372] 503

[09/Jun/2016:09:04:30 +0800][1465434270.372] 503

[09/Jun/2016:09:04:30+0800] [1465434270.864] 200

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.179] 503

[09/Jun/2016:09:04:31+0800] [1465434271.366] 200

桶容量为1,按照每1000毫秒一个请求的固定平均速率处理请求。

场景2.3****测试

首先定义IP维度的限流区域:

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

为了方便测试配置为每秒2个请求,固定平均速率是500毫秒一个请求。

接着在要限流的location中添加限流逻辑:

location /limit {
limit_req zone=test burst=3 nodelay;
echo “123”;
}

桶容量为3,如果桶满了直接拒绝新请求,且每秒2最多两个请求,桶按照固定500毫秒的速率以nodelay模式处理请求。

为了看到限流效果我们写了一个req.sh脚本:

ab -c 6 -n 6 http://localhost/limit
sleep 1
ab -c 6 -n 6 http://localhost/limit
sleep 0.3
ab -c 6 -n 6 http://localhost/limit
sleep 0.3
ab -c 6 -n 6 http://localhost/limit
sleep 0.3
ab -c 6 -n 6 http://localhost/limit
sleep 2
ab -c 6 -n 6 http://localhost/limit

将得到类似如下access.log输出:

[09/Jun/2016:14:30:11+0800] [1465453811.754] 200

[09/Jun/2016:14:30:11+0800] [1465453811.755] 200

[09/Jun/2016:14:30:11+0800] [1465453811.755] 200

[09/Jun/2016:14:30:11+0800] [1465453811.759] 200

[09/Jun/2016:14:30:11 +0800][1465453811.759] 503

[09/Jun/2016:14:30:11 +0800][1465453811.759] 503

[09/Jun/2016:14:30:12+0800] [1465453812.776] 200

[09/Jun/2016:14:30:12+0800] [1465453812.776] 200

[09/Jun/2016:14:30:12 +0800][1465453812.776] 503

[09/Jun/2016:14:30:12 +0800][1465453812.777] 503

[09/Jun/2016:14:30:12 +0800][1465453812.777] 503

[09/Jun/2016:14:30:12 +0800][1465453812.777] 503

[09/Jun/2016:14:30:13 +0800] [1465453813.095]503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.098] 503

[09/Jun/2016:14:30:13+0800] [1465453813.425] 200

[09/Jun/2016:14:30:13 +0800][1465453813.425] 503

[09/Jun/2016:14:30:13 +0800][1465453813.425] 503

[09/Jun/2016:14:30:13 +0800][1465453813.426] 503

[09/Jun/2016:14:30:13 +0800][1465453813.426] 503

[09/Jun/2016:14:30:13 +0800][1465453813.426] 503

[09/Jun/2016:14:30:13+0800] [1465453813.754] 200

[09/Jun/2016:14:30:13 +0800][1465453813.755] 503

[09/Jun/2016:14:30:13 +0800][1465453813.755] 503

[09/Jun/2016:14:30:13 +0800][1465453813.756] 503

[09/Jun/2016:14:30:13 +0800][1465453813.756] 503

[09/Jun/2016:14:30:13 +0800][1465453813.756] 503

[09/Jun/2016:14:30:15+0800] [1465453815.278] 200

[09/Jun/2016:14:30:15+0800] [1465453815.278] 200

[09/Jun/2016:14:30:15+0800] [1465453815.278] 200

[09/Jun/2016:14:30:15 +0800][1465453815.278] 503

[09/Jun/2016:14:30:15 +0800][1465453815.279] 503

[09/Jun/2016:14:30:15 +0800][1465453815.279] 503

[09/Jun/2016:14:30:17+0800] [1465453817.300] 200

[09/Jun/2016:14:30:17+0800] [1465453817.300] 200

[09/Jun/2016:14:30:17+0800] [1465453817.300] 200

[09/Jun/2016:14:30:17+0800] [1465453817.301] 200

[09/Jun/2016:14:30:17 +0800][1465453817.301] 503

[09/Jun/2016:14:30:17 +0800][1465453817.301] 503

桶容量为3(,即桶中在时间窗口内最多流入3个请求,且按照2r/s的固定速率处理请求(即每隔500毫秒处理一个请求);桶计算时间窗口(1.5秒)=速率(2r/s)/桶容量(3),也就是说在这个时间窗口内桶最多暂存3个请求。因此我们要以当前时间往前推1.5秒和1秒来计算时间窗口内的总请求数;另外因为配置了nodelay,是非延迟模式,所以允许时间窗内突发请求的;另外从本示例会看出两个问题:

第一轮和第七轮:有4个请求处理成功了;这是因为计算算法的问题,本示例是如果2秒内没有请求,然后接着突然来了很多请求,第一次计算的结果将是不正确的;这个问题影响很小可以忽略;

第五轮:1.0秒计算出来是3个请求;此处也是因计算精度的问题,也就是说limit_req实现的算法不是非常精准的,假设此处看成相对于2.75的话,1.0秒内只有1次请求,所以还是允许1次请求的。

如果限流出错了,可以配置错误页面:

proxy_intercept_errors on;
recursive_error_pages on;
error_page 503 //www.jd.com/error.aspx;

limit_conn_zone/limit_req_zone定义的内存不足,则后续的请求将一直被限流,所以需要根据需求设置好相应的内存大小。

此处的限流都是单Nginx的,假设我们接入层有多个nginx,此处就存在和应用级限流相同的问题;那如何处理呢?一种解决办法:建立一个负载均衡层将按照限流KEY进行一致性哈希算法将请求哈希到接入层Nginx上,从而相同KEY的将打到同一台接入层Nginx上;另一种解决方案就是使用Nginx+Lua(OpenResty)调用分布式限流逻辑实现。

lua-resty-limit-traffic

之前介绍的两个模块使用上比较简单,指定KEY、指定限流速率等就可以了,如果我们想根据实际情况变化KEY、变化速率、变化桶大小等这种动态特性,使用标准模块就很难去实现了,因此我们需要一种可编程来解决我们问题;而OpenResty提供了lua限流模块lua-resty-limit-traffic,通过它可以按照更复杂的业务逻辑进行动态限流处理了。其提供了limit.conn和limit.req实现,算法与nginx limit_conn和limit_req是一样的。

此处我们来实现ngx_http_limit_req_module中的【场景2.2测试】,不要忘记下载lua-resty-limit-traffic模块并添加到OpenResty的lualib中。

配置用来存放限流用的共享字典:

lua_shared_dict limit_req_store 100m;

以下是实现【场景2.2测试】的限流代码limit_req.lua:

local limit_req = require “resty.limit.req”
local rate = 2 --固定平均速率 2r/s
local burst = 3 --桶容量
local error_status = 503
local nodelay = false --是否需要不延迟处理
local lim, err = limit_req.new(“limit_req_store”, rate, burst)
if not lim then --没定义共享字典
ngx.exit(error_status)
end
local key = ngx.var.binary_remote_addr --IP维度的限流
–流入请求,如果请求需要被延迟则delay > 0
local delay, err = lim:incoming(key, true)
if not delay and err == “rejected” then --超出桶大小了
ngx.exit(error_status)
end
if delay > 0 then --根据需要决定是延迟或者不延迟处理
if nodelay then
–直接突发处理了
else
ngx.sleep(delay) --延迟处理
end
end

即限流逻辑再nginx access阶段被访问,如果不被限流继续后续流程;如果需要被限流要么sleep一段时间继续后续流程,要么返回相应的状态码拒绝请求。

在分布式限流中我们使用了简单的Nginx+Lua进行分布式限流,有了这个模块也可以使用这个模块来实现分布式限流。

另外在使用Nginx+Lua时也可以获取ngx.var.connections_active进行过载保护,即如果当前活跃连接数超过阈值进行限流保护。

if tonumber(ngx.var.connections_active) >= tonumber(limit) then
//限流
end

nginx也提供了limit_rate用来对流量限速,如limit_rate 50k,表示限制下载速度为50k。

到此笔者在工作中涉及的限流用法就介绍完,这些算法中有些允许突发,有些会整形为平滑,有些计算算法简单粗暴;其中令牌桶算法和漏桶算法实现上是类似的,只是表述的方向不太一样,对于业务来说不必刻意去区分它们;因此需要根据实际场景来决定如何限流,最好的算法不一定是最适用的。

参考资料

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

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

http://redis.io/commands/incr

http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html

https://github.com/openresty/lua-resty-limit-traffic

http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate

系统监控和流控-java应用

目前系统的监控方面,linux机器,可以定时的获取cpu、load、IO、网络等情况,统计之后,如果超过阀值,即可报警。

web的请求,可以通过分析apache的日志,获取PV、UV以及页面的响应时间等信息,统计这些信息,如果有异常,报警即可。

但是java系统(一个java进程)中的bean的情况如何做到监控和流控呢?

双十一,各个系统都有一些监控和流控的策略,了解了一圈之后,打算总结一下,只是粗略的写一下思路,记录一下,不涉及到细节(因为细节坑很多)。监控的目前是为了了解系统的运行细节,流控是为了在出现问题(瞬间请求暴涨、持续一段时间请求超过平均值、底层依赖的应用或者DB出现异常)的时候能够做出处理(动态限流拒绝新的请求进来<限流策略可以设置>、利用开关<修改静态类的属性>进行应用降级处理减少非核心的调用)。(1)业务日志监控业务代码抛出来的日志信息(可以在linux中通过crontab定时程序来抓取,也可以通过控制台来远程处理,也可以把文件拉到集中的分析服务器来处理),如果异常数太多,或者出现自己之前设置过的报警关键字,则进行报警;(2)java gc的日志java在运行过程中,如果配置了参数(-verbose:gc -Xloggc:/home/admin/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps),能够看到系统的gc的情况,如果FullGc特别频繁(超过一小时一次),可以提示报警;(3)拥堵线程数作为监控指标创建一个对象,里面一个key是方法的代表,然后属性中有个计数器(这个key此时对应的拥堵线程数),如果方法体内处理变慢,并发情况下拥堵线程数会增加,此时,可以进行报警,也可以把后续的请求当掉,降低负载。计数器进行变化可以有两种方式:第一种就是,通过AOP的方式,在方法调用前(before)拥堵线程数加一,方法返回后(after)拥堵线程数减一,这种好处就是不用硬编码;另外一种就需要写代码,在方法体内,调用开始加以,然后try住,在finally的时候减一。(4)根据方法调用的QPS和RTQPS即一秒内方法调用的次数,RT即一秒内方法调用的平均返回时间。对于web请求,一般在web服务器(apache/nginx)中做掉这个,在服务器A中部署一个client,用来做计数统计,具体的计数规则可能会比较复杂,超过阀值(有可能是攻击),则请求server端此时的拒绝策略,是让用户等待,还是跳转待验证码页面让其输入验证码之后再进行访问,还是直接返回错误。对于java中的bean方法,如何获取呢?如三种描述,两种方式一种是AOP拦截,一种是代码中硬编码。(5)基于AOP获取一个特定方法在特定时间段(例如一分钟)的拥堵线程数、执行次数(总次数、成功次数、失败次数)、响应时间做一个本地内存对象,用于记录这些信息,通过AOP的before获取信息(当前时间,线程数加一,调用次数加一),afterReturen获取信息(返回时间、线程数减一,调用次数加一,可以定制啥叫成功失败,然后相应次数增加),afterThrowing获取信息(返回时间、线程数减一、失败次数加一),最后定时dump内存中的数据,存储到DB或者日志,然后对于这些信息进行监控(可以根据历史来进行同比和环比)进行报警。(6)动态来添加QPS和RT的流控这种流控方式最为灵活,应用端依赖一个jar包,然后添加一个全局的AOP配置(对于页面的请求,在web.xml中添加filter即可​),拦截所有的方法(性能消耗可以忽略),但是只有符合规则的方法才会进行统计,然后有一个控制台设置规则,设置好规则后推送到应用端,应用端获取这个规则,根据这个规则来进行统计,超出阀值则进行限流。这种方式最为灵活,遇到紧急问题的时候可以通过控制台来限流掉。(7)请求是海量的情况下如何进行监控在请求很大的情况下,此时应用就不要处理监控的逻辑了,只要获取调用的信息(时间点、响应时间、方法签名等信息),然后把这些信息打印到日志中,异步来进行处理,把这些日志拉到专门的分析集群,然后在分析集群里面来做实时的分析,把分析的结果持久话到BD,对于分析后的结构化数据进行监控。

转载公司一位同事的文章:

http://rdc.taobao.com/team/jm/archives/2594

流量预警和限流方案中,比较常用的有两种。第一种滑窗模式,通过统计多个单元时间的访问次数来进行控制,当单位时间的访问次数达到的某个峰值时进行限流。第二种为响应模式,通过控制当前活跃请求数,来进行流量控制。下面来简单分析下两种的优缺点。

1、滑窗模式

**模式分析:
**
在每次有访问进来时,我们判断前N个单位时间里总访问量是否超过了设置的阈值,若超过则不允许执行。

这种模式的实现的方式更加契合流控的本质意义。理解较为简单。但由于访问量的预先不可预见性,会发生单位时间的前半段有大量的请求涌入,而后半段则拒绝所有请求的情况发生。(一般,需要会将单位时间切的足够的细来解决这个问题)其次,我们很难确定这个阈值设置在多少比较合适,只能通过经验或者模拟(如压测)来进行估计,不过即使是压测也很难估计的准确,线上每台机器的硬件参数的不同,或者同一台机子在不同的时间点其可以接受的阈值也不尽相同(系统中),每个时间点导致能够承受的最大阈值也不尽相同,我们无法考虑的周全。

所以滑窗模式往往用来对某一资源的保护上(或者说是承诺比较合适:我对某一接口的提供者承诺过,最高调用量不超过XX),如对db的保护,对某一服务的调用的控制上。因为对于我们应用来说,db或某一接口就是一共单一的整体。

代码实现思路:

每一个窗(单位时间)就是一个独立的计数器(原子计数器),用以数组保存。将当前时间以某种方式(比如取模)映射到数组的一项中。每次访问先对当前窗内计数器+1,再计算前N个单元格的访问量综合,超过阈值则限流。

这里有个问题,时间永远是递增的,单纯的取模,会导致数组过长,使用内存过多,我们可以用环形队列来解决这个问题。

2、响应模式

模式分析:

每次操作执行时,我们通过判断当前正在执行的访问数是否超过某个阈值在决定是否限流。

该模式看着思路比较的另类,但却有其独到之处。实际上我们限流的根本是为了保护资源,防止系统接受的请求过多,应接不暇,拖慢系统中其他接口的服务,造成雪崩。也就是说我们真正需要关心的是那些运行中的请求,而那些已经完成的请求已是过去时,不再是需要关心的了。

我们来看看其阈值的计算方式,对于一个请求来说,响应时间rt/qps是一个比较容易获取的参数,那么我们这样计算:qps/1000*rt。

此外,一个应用往往是个复杂的系统,提供的服务或者暴露的请求、资源不止一个。内部GC、定时任务的执行、其他服务访问的骤增,外部依赖方、db的抖动,抑或是代码中不经意间的一个bug。都可能导致相应时间的变化,导致系统同时可以执行请求的变化。而这种模式,则能恰如其分的自动做出调整,当系统不适时,rt增加时,会自动的对qps做出适应。

代码实现思路:

当访问开始时,我们对当前计数器(原子计数器)+1,当完成时,-1。该计数器即为当前正在执行的请求数。只需判断这个计数器是否超过阈值即可。

标签: 分布式系统

[好文要顶](javascript:void(0)😉 [关注我](javascript:void(0)😉 [收藏该文](javascript:void(0)😉 [](javascript:void(0); “分享至新浪微博”) [](javascript:void(0); “分享至微信”)

xuwc
关注 - 52
粉丝 - 100

[+加关注](javascript:void(0)😉

11

0

currentDiggType = 0;

« 上一篇: spring-springmvc源码分析(二)
» 下一篇: spring-spring IOC源码分析(一)

posted on 2018-06-01 18:58 xuwc 阅读(84564) 评论(6) 编辑 [收藏](javascript:void(0)) [举报](javascript:void(0))

var allowComments = true, cb_blogId = 399119, cb_blogApp = ‘xuwc’, cb_blogUserGuid = ‘fd3a71cd-565d-e611-9fc1-ac853d9f53cc’; var cb_entryId = 9123078, cb_entryCreatedDate = ‘2018-06-01 18:58’, cb_postType = 1; updatePostStats( [cb_entryId], function(id, count) { $("#post_view_count").text(count) }, function(id, count) { $("#post_comment_count").text(count) }) zoomManager.apply("#cnblogs_post_body img:not(.code_img_closed):not(.code_img_opened)");

评论:

#1楼 [楼主] 2020-07-09 23:19 | xuwc

@2J
工作太忙,没时间整理了。

[支持(0)](javascript:void(0)😉 [反对(0)](javascript:void(0)😉

https://pic.cnblogs.com/face/1005861/20180318163451.png [回复](javascript:void(0)😉 [引用](javascript:void(0)😉

#2楼 2020-08-25 12:29 | levi125

mark

[支持(0)](javascript:void(0)😉 [反对(0)](javascript:void(0)😉

https://pic.cnblogs.com/face/2129350/20200818105600.png [回复](javascript:void(0)😉 [引用](javascript:void(0)😉

#3楼 2020-09-29 13:16 | 雨落寒沙

好文,但是排版看起来是不是有点太费劲了。。。。

[支持(0)](javascript:void(0)😉 [反对(0)](javascript:void(0)😉

https://pic.cnblogs.com/face/1201110/20191016091957.png [回复](javascript:void(0)😉 [引用](javascript:void(0)😉

#4楼 2021-04-12 09:18 | xiaoye-2018

硬核

[支持(0)](javascript:void(0)😉 [反对(0)](javascript:void(0)😉

[回复](javascript:void(0)😉 [引用](javascript:void(0)😉

#5楼 2021-07-13 11:40 | 死不了好气呦

在看,在实践

[支持(0)](javascript:void(0)😉 [反对(0)](javascript:void(0)😉

https://pic.cnblogs.com/face/1846333/20200528212221.png [回复](javascript:void(0)😉 [引用](javascript:void(0)😉

#6楼 4934548 2021/9/7 下午9:20:52 2021-09-07 21:20 | 狂-奔

赞赞

[支持(0)](javascript:void(0)😉 [反对(0)](javascript:void(0)😉

[回复](javascript:void(0)😉 [引用](javascript:void(0)😉

[刷新评论](javascript:void(0)😉刷新页面返回顶部

发表评论

编辑 预览

3fd815ad-ed59-4b29-71cd-08d81758c719

自动补全

[不改了](javascript:void(0)😉 [退出](javascript:void(0)😉 [订阅评论](javascript:void(0); “订阅后有新评论时会邮件通知您”) 我的博客

[Ctrl+Enter快捷键提交]

var commentEditor = initCommentEditor(“tbCommentBody”);

【推荐】跨平台组态\工控\仿真\CAD 50万行C++源码全开放免费下载!
【推荐】并行超算云面向博客园粉丝推出“免费算力限时申领”特别活动
【推荐】HMS Core线上Codelabs挑战赛第3期:用3D建模构建元宇宙

编辑推荐:
· 一次 fork 引发的惨案!
· OAuth 2.0 的探险之旅
· .NET 生态系统的蜕变之 .NET 6
· 从顶层设计聊公司治理
· 存储技术发展过程

最新新闻
· 曝腾讯将实行“965”工作制 员工:没收到通知、不如弹性工作制(2021-11-04 11:07)
· 苹果高管解释为Apple Watch Series 7加大屏幕尺寸的工程挑战(2021-11-04 11:00)
· 谷歌新闻服务明年重返西班牙:可与出版商直接谈判(2021-11-04 10:53)
· Waymo人工驾驶汽车扩展到纽约 希望让其软件系统更适应大城市(2021-11-04 10:47)
· 摩托罗拉G51发布 搭载骁龙480+芯片支持120Hz高刷(2021-11-04 10:40)
» 更多新闻…

var commentManager = new blogCommentManager(); commentManager.renderComments(0); fixPostBody(); setTimeout(function() { incrementViewCount(cb_entryId); }, 50); deliverT2(); deliverC1C2(); loadNewsAndKb(); LoadPostCategoriesTags(cb_blogId, cb_entryId); LoadPostInfoBlock(cb_blogId, cb_entryId, cb_blogApp, cb_blogUserGuid); GetPrevNextPost(cb_entryId, cb_blogId, cb_entryCreatedDate, cb_postType); loadOptUnderPost(); GetHistoryToday(cb_blogId, cb_blogApp, cb_entryCreatedDate);

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 68-数据结构设计——共享栈

    题目要求&#xff1a;设有两个栈 S1,S2 都采用顺序栈方式&#xff0c;并且共享一个存储区[0…maxsize-1],为了尽量利用空间&#xff0c;减少溢出的可能&#xff0c;可采用栈顶相向&#xff0c;迎面增长的存储方式。试设计 S1,S2有关入栈和出栈的操作算法。 1.结构体设计 #def…...

    2024/4/19 16:44:23
  2. SQLite数据库及在Android开发中的基本使用

    SQLite数据库及在Android开发中的基本使用 SQLite数据库的特点 轻量级数据库&#xff0c;无需搭建服务器。 SQLite环境配置 这里提供从官网下载的方法&#xff0c;如果有Android Studio的项目&#xff0c;可以跳转到本文&#xff1a;SQLite在Android中的应用。 1.进入sqli…...

    2024/4/18 4:09:50
  3. Mybatis基础模块讲解与强化核心原理

    基础支持层 基础支持层位于MyBatis整体架构的最底层&#xff0c;支撑着MyBatis的核心处理层&#xff0c;是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块。不仅仅为MyBatis提供基础支撑&#xff0c;也可以在合适的场景中直接复用。 反射模块 MyBatis在进行…...

    2024/4/18 9:11:04
  4. Web前端期末大作业---响应式唯美婚庆公司网站网页设计

    临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大&#xff1f;网页要求的总数量太多&#xff1f;HTML网页作业无从下手&#xff1f;没有合适的模板&#xff1f;等等一系列问题。你想要解决的问题&#xff0c;在这里常见网页设计作业题材有 个人、 美食、 公司、…...

    2024/4/17 19:51:52
  5. Codeforces Round #748 (Div. 3) 题解代码

    题目链接 A #include<iostream> #include<algorithm> #include<cstring> using namespace std; #define x first #define y second typedef pair<int,int> PII; const int N 1e4 10;int main() {int T;cin >> T;while(T--){int mx 0;int a[…...

    2024/4/16 16:02:37
  6. (3)图像处理-cv2一些函数记录

    1、putText cv2.putText(Img,text,(50,50),cv2.FONT_HERSHEY_COMPLEX,6,(0,0,255),5)图片/需要打印的字符串/左下角坐标位置/字体样式/字体大小/字体颜色&#xff08;BGR&#xff09;&#xff0c;字体粗细...

    2024/4/7 0:59:47
  7. Element Table 实现点击行变色效果

    Element Table 实现点击行变色效果1、el-table组件中对row-style和row-click属性绑定处理函数2、在data中定义currentRowId记录当前点击的行&#xff0c;通过row-click的点击事件进行切换当前行1、el-table组件中对row-style和row-click属性绑定处理函数 <el-table:data&qu…...

    2024/4/19 8:34:18
  8. 能能AI文章伪原创生成工具【长期更新】

    ...

    2024/4/19 18:22:51
  9. Impala导出查询结果到文件

    想用impala-shell 命令行中将查询的结果导出到本地文件&#xff0c;想当然的以为impala 和 hive 一样可以用 insert overwrite local directory ‘/home/test.txt’ select ……. 这样的命令导出到本地&#xff0c;执行了一下&#xff0c;发现impala 不支持这个。 然后查了一下…...

    2024/4/15 4:19:35
  10. (PTA)实验3

    7-1 栈的实现及基本操作 (15 分) 给定一个初始为空的栈和一系列压栈、弹栈操作&#xff0c;请编写程序输出每次弹栈的元素。栈的元素值均为整数。 输入格式: 输入第1行为1个正整数n&#xff0c;表示操作个数&#xff1b;接下来n行&#xff0c;每行表示一个操作&#xff0c;格式…...

    2024/4/16 14:54:11
  11. 小Q百度指数批量查询工具【持久更新】

    ...

    2024/4/19 13:07:30
  12. 剑指 Offer 58 - II. 左旋转字符串——记录(C++)

    class Solution { public:string reverseLeftWords(string s, int n) {int ls.size();string a(l,0);int j0;for(int in;i<l;i){a[j]s[i];j;}for(int i0;i<n;i){a[j]s[i];j;}return a;} }; 加油&#xff01;...

    2024/4/15 4:19:55
  13. 资深投资人也在关注的办公软件

    我有几款非常好用的远程办公软件&#xff0c;是我从网络上各种好评推荐中选出几款&#xff0c;然后进行实际体验综合得出来的五款最火&#xff0c;实用的远程办公软件&#xff01;据说顶级投资人都在关注呦&#xff01;嘻嘻&#xff01; 1.亿图脑图 亿图脑图即MindMaster&…...

    2024/4/15 4:19:50
  14. Date类、DateFormat类、Calendar类的简单介绍

    一、包的位置 java.util.Date&#xff1b;java.text.DateFormat&#xff1b;java.lang.Calendar&#xff1b; 二、整体概括三者的介绍 java.util.Date 表示的是特定的时刻&#xff0c;精度为毫秒不适合国际化处理&#xff1b; java.text.DateFormat 日期格式化类&#xff1…...

    2024/4/17 3:33:08
  15. SAP Commerce Cloud 产品明细页面设计概述

    url&#xff1a;http://localhost:4299/electronics-spa/en/USD/jerryproduct/300938/Photosmart%20E317%20Digital%20Camera template&#xff1a;ProductDetailsPageTemplate 17个 content slot&#xff1a; 我关心的是这个 slot&#xff1a;TabsSlot CMSTabParagraphC…...

    2024/4/5 6:18:34
  16. IDEA14:maven快速排查依赖包冲突

    文章目录一、提问&#xff1a;二、解决步骤&#xff1a;一、提问&#xff1a; 在maven导入架包的过程中&#xff0c;如果架包出现冲突&#xff0c;如何快速去排查依赖包的冲突并进行解决&#xff1f; 二、解决步骤&#xff1a; 1、IDEA打开pom.xml–定位到内容里点击右键–D…...

    2024/4/18 15:54:19
  17. 2022年计算机二级考试的一些心得、经验和资料总结分享

    相信很多在校的计算机以及计算机相关专业的同学都知道计算机考级的事情&#xff0c;也有很多同学规划着要考二级等等。 那么计算机考级到底有没有用&#xff1f;这个仁者见而智者见智&#xff0c;总的来说&#xff0c;如果有时间&#xff0c;考了肯定比考是有优势的&#xff0…...

    2024/4/19 12:29:09
  18. Java Swing 如何让界面更加美观

    文章目录一、设置窗体的背景图二、设置Button组件三、设置字体大小和颜色四、设置组件的背景色五、综合测试案例一、设置窗体的背景图 利用JLable类的构造方法或方法加载图片 ImageIcon image new ImageIcon("D:\\背景.jpg"); JLabel jlable new JLabel(image); …...

    2024/4/18 1:47:31
  19. 云计算学习笔记7——分布式存储及计算

    Hadoop生态系统 2005年&#xff0c;雅虎工程师&#xff0c;分布式计算系统Hadoop&#xff0c;后开源。采用MapReduce分布式计算框架&#xff0c;并根据GFS开发了HDFS分布式文件系统。 核心组件&#xff1a; HDFSMapReduce 1. 分布式文件系统——GFS 1.1 GFS 超大规模分布式…...

    2024/4/19 20:15:08
  20. Linux Shell脚本与Vim常用操作笔记

    本篇笔记记录最最基本的vim和shell操作。ShellScript能干什么 Shellscript类似与Windows的批处理,可以把很多指令汇总到一起,于是可以很容易地通过一个操作执行多个命令。很轻量,有效率。在Vim中编写 Vim/Vi编辑器很强大,功能非常多,快捷键、指令更多,只需要知道最常用的…...

    2024/4/16 8:27:57

最新文章

  1. C++-命名空间

    C 命名空间是一种用于组织代码的机制&#xff0c;可以帮助避免命名冲突&#xff0c;提高代码的可读性和可维护性。命名空间将代码分组到逻辑单元中&#xff0c;允许在不同的代码单元中使用相同的名称而不会产生冲突。 命名空间通过将代码放置在一个命名空间内部来实现。在 C 中…...

    2024/4/19 21:36:34
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 单片机学习day4

    1. 嵌入式驱动 嵌入式驱动&#xff08;Embedded Driver&#xff09;是一种软件模块&#xff0c;用于控制和管理嵌入式系统中的外部设备或组件。 2. 嵌入式驱动程序功能 2.1 设备初始化 2.2 数据传输 2.3 错误处理 2.4 资源管理 2.5 接口适配 3. 中断系统 3.1 定义 中断…...

    2024/4/14 16:56:42
  4. Go语言中如何实现继承

    完整课程请点击以下链接 Go 语言项目开发实战_Go_实战_项目开发_孔令飞_Commit 规范_最佳实践_企业应用代码-极客时间 Go语言中没有传统意义上的类和继承的概念&#xff0c;但可以通过嵌入类型&#xff08;embedded types&#xff09;来实现类似的功能。嵌入类型允许一个结构…...

    2024/4/19 7:49:59
  5. STL--vector有哪些应用场景

    vector 在 C 中是一种非常灵活和强大的容器&#xff0c;适用于多种不同的应用场景。以下是一些常见的应用场景&#xff1a; 1 动态数据集合&#xff1a;当你不确定数据集的大小&#xff0c;或者数据集的大小会随时间变化时&#xff0c;vector 是理想的选择。例如&#xff0c;在…...

    2024/4/19 12:44:56
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/19 14:24:02
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/19 18:20:22
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/19 11:57:31
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/19 11:57:31
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/19 11:57:52
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/19 11:57:53
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/19 11:58:14
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/19 11:58:20
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/19 11:58:32
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/19 11:58:39
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/19 11:58:51
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/19 18:09:34
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/19 11:59:15
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/19 11:59:23
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/19 11:59:44
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/19 11:59:48
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/19 12:00:06
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/19 16:57:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/19 12:00:25
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/19 12:00:40
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57