手写服务限流6种实现方式

news2024/10/3 6:32:59

服务限流,我有 6 种实现方式

ImportNew 2023-05-29 11:31 发表于上海

以下文章来源于码农参上 ,作者Dr Hydra

码农参上.

专注后端技术分享,有趣、深入、直接,与你聊聊技术。

今天来和大家聊聊服务的限流。

服务限流,是指通过控制请求的速率或次数来达到保护服务的目的,在微服务中,我们通常会将它和熔断、降级搭配在一起使用,来避免瞬时的大量请求对系统造成负荷,来达到保护服务平稳运行的目的。下面就来看一看常见的 6 种限流方式,以及它们的实现与使用。

固定窗口算法

固定窗口算法通过在单位时间内维护一个计数器,能够限制在每个固定的时间段内请求通过的次数,以达到限流的效果。

算法实现起来也比较简单,可以通过构造方法中的参数指定时间窗口大小以及允许通过的请求数量,当请求进入时先比较当前时间是否超过窗口上边界,未越界且未超过计数器上限则可以放行请求。

@Slf4jpublic class FixedWindowRateLimiter {    // 时间窗口大小,单位毫秒    private long windowSize;    // 允许通过请求数    private int maxRequestCount;
    // 当前窗口通过的请求计数    private AtomicInteger count=new AtomicInteger(0);    // 窗口右边界    private long windowBorder;
    public FixedWindowRateLimiter(long windowSize,int maxRequestCount){        this.windowSize = windowSize;        this.maxRequestCount = maxRequestCount;        windowBorder = System.currentTimeMillis()+windowSize;    }
    public synchronized boolean tryAcquire(){        long currentTime = System.currentTimeMillis();        if (windowBorder < currentTime){            log.info("window  reset");            do {                windowBorder += windowSize;            }while(windowBorder < currentTime);            count=new AtomicInteger(0);        }
        if (count.intValue() < maxRequestCount){            count.incrementAndGet();            log.info("tryAcquire success");            return true;        }else {            log.info("tryAcquire fail");            return false;        }    }}

进行测试,允许在 1000 毫秒内通过 5 个请求:

void test() throws InterruptedException {    FixedWindowRateLimiter fixedWindowRateLimiter            = new FixedWindowRateLimiter(1000, 5);
    for (int i = 0; i < 10; i++) {        if (fixedWindowRateLimiter.tryAcquire()) {            System.out.println("执行任务");        }else{            System.out.println("被限流");            TimeUnit.MILLISECONDS.sleep(300);        }    }}

运行结果:

固定窗口算法的优点是实现简单,但是可能无法应对突发流量的情况,比如每秒允许放行 100 个请求,但是在 0.9 秒前都没有请求进来,这就造成了在 0.9 秒到 1 秒这段时间内要处理 100 个请求,而在1秒到1.1秒间可能会再进入 100 个请求,这就造成了要在 0.2 秒内处理 200 个请求,这种流量激增就可能导致后端服务出现异常。

滑动窗口算法

滑动窗口算法在固定窗口的基础上,进行了一定的升级改造。它的算法的核心在于将时间窗口进行了更精细的分片,将固定窗口分为多个小块,每次仅滑动一小块的时间。

并且在每个时间段内都维护了单独的计数器,每次滑动时,都减去前一个时间块内的请求数量,并再添加一个新的时间块到末尾,当时间窗口内所有小时间块的计数器之和超过了请求阈值时,就会触发限流操作。

看一下算法的实现,核心就是通过一个 int 类型的数组循环使用来维护每个时间片内独立的计数器:

@Slf4jpublic class SlidingWindowRateLimiter {    // 时间窗口大小,单位毫秒    private long windowSize;    // 分片窗口数    private int shardNum;    // 允许通过请求数    private int maxRequestCount;    // 各个窗口内请求计数    private int[] shardRequestCount;    // 请求总数    private int totalCount;    // 当前窗口下标    private int shardId;    // 每个小窗口大小,毫秒    private long tinyWindowSize;    // 窗口右边界    private long windowBorder;
    public SlidingWindowRateLimiter(long windowSize, int shardNum, int maxRequestCount) {        this.windowSize = windowSize;        this.shardNum = shardNum;        this.maxRequestCount = maxRequestCount;        shardRequestCount = new int[shardNum];        tinyWindowSize = windowSize/ shardNum;        windowBorder=System.currentTimeMillis();    }
    public synchronized boolean tryAcquire() {        long currentTime = System.currentTimeMillis();        if (currentTime > windowBorder){            do {                shardId = (++shardId) % shardNum;                totalCount -= shardRequestCount[shardId];                shardRequestCount[shardId]=0;                windowBorder += tinyWindowSize;            }while (windowBorder < currentTime);        }
        if (totalCount < maxRequestCount){            log.info("tryAcquire success,{}",shardId);            shardRequestCount[shardId]++;            totalCount++;            return true;        }else{            log.info("tryAcquire fail,{}",shardId);            return false;        }    }
}

进行一下测试,对第一个例子中的规则进行修改,每 1 秒允许 100 个请求通过不变,在此基础上再把每 1 秒等分为 10 个 0.1 秒的窗口。

void test() throws InterruptedException {    SlidingWindowRateLimiter slidingWindowRateLimiter            = new SlidingWindowRateLimiter(1000, 10, 10);    TimeUnit.MILLISECONDS.sleep(800);
    for (int i = 0; i < 15; i++) {        boolean acquire = slidingWindowRateLimiter.tryAcquire();        if (acquire){            System.out.println("执行任务");        }else{            System.out.println("被限流");        }        TimeUnit.MILLISECONDS.sleep(10);    }}

查看运行结果:

程序启动后,在先休眠了一段时间后再发起请求,可以看到在 0.9 秒到1秒的时间窗口内放行了 6 个请求,在 1 秒到 1.1 秒内放行了 4 个请求,随后就进行了限流,解决了在固定窗口算法中相邻时间窗口内允许通过大量请求的问题。

滑动窗口算法通过将时间片进行分片,对流量的控制更加精细化,但是相应的也会浪费一些存储空间,用来维护每一块时间内的单独计数,并且还没有解决固定窗口中可能出现的流量激增问题。

漏桶算法

为了应对流量激增的问题,后续又衍生出了漏桶算法,用专业一点的词来说,漏桶算法能够进行流量整形和流量控制。

漏桶是一个很形象的比喻,外部请求就像是水一样不断注入水桶中,而水桶已经设置好了最大出水速率,漏桶会以这个速率匀速放行请求,而当水超过桶的最大容量后则被丢弃。

看一下代码实现:

@Slf4jpublic class LeakyBucketRateLimiter {    // 桶的容量    private int capacity;    // 桶中现存水量    private AtomicInteger water=new AtomicInteger(0);    // 开始漏水时间    private long leakTimeStamp;    // 水流出的速率,即每秒允许通过的请求数    private int leakRate;
    public LeakyBucketRateLimiter(int capacity,int leakRate){        this.capacity=capacity;        this.leakRate=leakRate;    }
    public synchronized boolean tryAcquire(){        // 桶中没有水,重新开始计算        if (water.get()==0){            log.info("start leaking");            leakTimeStamp = System.currentTimeMillis();            water.incrementAndGet();            return water.get() < capacity;        }
        // 先漏水,计算剩余水量        long currentTime = System.currentTimeMillis();        int leakedWater= (int) ((currentTime-leakTimeStamp)/1000 * leakRate);        log.info("lastTime:{}, currentTime:{}. LeakedWater:{}",leakTimeStamp,currentTime,leakedWater);
        // 可能时间不足,则先不漏水        if (leakedWater != 0){            int leftWater = water.get() - leakedWater;            // 可能水已漏光,设为0            water.set(Math.max(0,leftWater));            leakTimeStamp=System.currentTimeMillis();        }        log.info("剩余容量:{}",capacity-water.get());
        if (water.get() < capacity){            log.info("tryAcquire success");            water.incrementAndGet();            return true;        }else {            log.info("tryAcquire fail");            return false;        }    }}

进行一下测试,先初始化一个漏桶,设置桶的容量为 3,每秒放行 1 个请求,在代码中每 500 毫秒尝试请求 1 次:

void test() throws InterruptedException {    LeakyBucketRateLimiter leakyBucketRateLimiter   =new LeakyBucketRateLimiter(3,1);    for (int i = 0; i < 15; i++) {        if (leakyBucketRateLimiter.tryAcquire()) {            System.out.println("执行任务");        }else {            System.out.println("被限流");        }        TimeUnit.MILLISECONDS.sleep(500);    }}

查看运行结果,按规则进行了放行:

但是,漏桶算法同样也有缺点,不管当前系统的负载压力如何,所有请求都得进行排队,即使此时服务器的负载处于相对空闲的状态,这样会造成系统资源的浪费。由于漏桶的缺陷比较明显,所以在实际业务场景中,使用的比较少。

令牌桶算法

令牌桶算法是基于漏桶算法的一种改进,主要在于令牌桶算法能够在限制服务调用的平均速率的同时,还能够允许一定程度内的突发调用。

它的主要思想是系统以恒定的速度生成令牌,并将令牌放入令牌桶中,当令牌桶中满了的时候,再向其中放入的令牌就会被丢弃。而每次请求进入时,必须从令牌桶中获取一个令牌,如果没有获取到令牌则被限流拒绝。

假设令牌的生成速度是每秒 100 个,并且第一秒内只使用了 70 个令牌,那么在第二秒可用的令牌数量就变成了 130,在允许的请求范围上限内,扩大了请求的速率。当然,这里要设置桶容量的上限,避免超出系统能够承载的最大请求数量。

Guava 中的 RateLimiter 就是基于令牌桶实现的,可以直接拿来使用,先引入依赖:

<dependency>    <groupId>com.google.guava</groupId>    <artifactId>guava</artifactId>    <version>29.0-jre</version></dependency>

进行测试,设置每秒产生 5 个令牌:

void acquireTest(){    RateLimiter rateLimiter=RateLimiter.create(5);    for (int i = 0; i < 10; i++) {        double time = rateLimiter.acquire();        log.info("等待时间:{}s",time);    }}

 

运行结果:

可以看到,每 200ms 左右产生一个令牌并放行请求,也就是 1 秒放行 5 个请求,使用 RateLimiter 能够很好的实现单机的限流。

那么再回到我们前面提到的突发流量情况,令牌桶是怎么解决的呢?RateLimiter 中引入了一个预消费的概念。在源码中,有这么一段注释:

 * <p>It is important to note that the number of permits requested <i>never</i> affects the * throttling of the request itself (an invocation to {@code acquire(1)} and an invocation to {@code * acquire(1000)} will result in exactly the same throttling, if any), but it affects the throttling * of the <i>next</i> request. I.e., if an expensive task arrives at an idle RateLimiter, it will be * granted immediately, but it is the <i>next</i> request that will experience extra throttling, * thus paying for the cost of the expensive task.

大意就是,申请令牌的数量不同不会影响这个申请令牌这个动作本身的响应时间, acquire(1) 和 acquire(1000) 这两个请求会消耗同样的时间返回结果,但是会影响下一个请求的响应时间。

如果一个消耗大量令牌的任务到达空闲的 RateLimiter,会被立即批准执行,但是当下一个请求进来时,将会额外等待一段时间,用来支付前一个请求的时间成本。

至于为什么要这么做,通过举例来引申一下。当一个系统处于空闲状态时,突然来了 1 个需要消耗 100 个令牌的任务,那么白白等待 100 秒是毫无意义的浪费资源行为,那么可以先允许它执行,并对后续请求进行限流时间上的延长,以此来达到一个应对突发流量的效果。

看一下具体的代码示例:

void acquireMultiTest(){    RateLimiter rateLimiter=RateLimiter.create(1);        for (int i = 0; i <3; i++) {        int num = 2 * i + 1;        log.info("获取{}个令牌", num);        double cost = rateLimiter.acquire(num);        log.info("获取{}个令牌结束,耗时{}ms",num,cost);    }}

运行结果:

可以看到,在第二次请求时需要 3 个令牌,但是并没有等 3 秒后才获取成功,而是在等第一次的 1 个令牌所需要的1秒偿还后,立即获得了 3 个令牌得到了放行。同样,第三次获取 5 个令牌时等待的 3 秒是偿还的第二次获取令牌的时间,偿还完成后立即获取 5 个新令牌,而并没有等待全部重新生成完成。

除此之外 RateLimiter 还具有平滑预热功能,下面的代码就实现了在启动 3 秒内,平滑提高令牌发放速率到每秒 5 个的功能:

void acquireSmoothly(){    RateLimiter rateLimiter=RateLimiter.create(5,3, TimeUnit.SECONDS);    long startTimeStamp = System.currentTimeMillis();    for (int i = 0; i < 15; i++) {        double time = rateLimiter.acquire();        log.info("等待时间:{}s, 总时间:{}ms"                ,time,System.currentTimeMillis()-startTimeStamp);    }}

查看运行结果:

可以看到,令牌发放时间从最开始的 500ms 多逐渐缩短,在 3 秒后达到了 200ms 左右的匀速发放。

总的来说,基于令牌桶实现的 RateLimiter 功能还是非常强大的,在限流的基础上还可以把请求平均分散在各个时间段内,因此在单机情况下它是使用比较广泛的限流组件。

中间件限流

前面讨论的四种方式都是针对单体架构,无法跨 JVM 进行限流,而在分布式、微服务架构下,可以借助一些中间件进行限。Sentinel 是 Spring Cloud Alibaba 中常用的熔断限流组件,为我们提供了开箱即用的限流方法。

使用起来也非常简单,在 service 层的方法上添加 @SentinelResource 注解,通过 value 指定资源名称,blockHandler 指定一个方法,该方法会在原方法被限流、降级、系统保护时被调用。

@Servicepublic class QueryService {    public static final String KEY="query";
    @SentinelResource(value = KEY,            blockHandler ="blockHandlerMethod")    public String query(String name){        return "begin query,name="+name;    }
    public String blockHandlerMethod(String name, BlockException e){        e.printStackTrace();        return "blockHandlerMethod for Query : " + name;    }}

配置限流规则,这里使用直接编码方式配置,指定 QPS 到达 1 时进行限流:

@Componentpublic class SentinelConfig {    @PostConstruct    private void init(){        List<FlowRule> rules = new ArrayList<>();        FlowRule rule = new FlowRule(QueryService.KEY);        rule.setCount(1);        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);        rule.setLimitApp("default");        rules.add(rule);        FlowRuleManager.loadRules(rules);    }}

在 application.yml 中配置 sentinel 的端口及 dashboard 地址:

spring:  application:    name: sentinel-test  cloud:    sentinel:      transport:        port: 8719        dashboard: localhost:8088

启动项目后,启动 sentinel-dashboard:

java -Dserver.port=8088 -jar sentinel-dashboard-1.8.0.jar

在浏览器打开 dashboard 就可以看见我们设置的流控规则:

进行接口测试,在超过 QPS 指定的限制后,则会执行 blockHandler() 方法中的逻辑:

Sentinel 在微服务架构下得到了广泛的使用,能够提供可靠的集群流量控制、服务断路等功能。在使用中,限流可以结合熔断、降级一起使用,成为有效应对三高系统的三板斧,来保证服务的稳定性。

网关限流

网关限流也是目前比较流行的一种方式,这里我们介绍采用 Spring Cloud 的 gateway 组件进行限流的方式。

在项目中引入依赖,gateway 的限流实际使用的是 Redis 加 Lua 脚本的方式实现的令牌桶,因此还需要引入 Redis 的相关依赖:

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>

对 gateway 进行配置,主要就是配一下令牌的生成速率、令牌桶的存储量上限,以及用于限流的键的解析器。这里设置的桶上限为 2,每秒填充 1 个令牌:

spring:  application:    name: gateway-test  cloud:    gateway:      routes:        - id: limit_route          uri: lb://sentinel-test          predicates:          - Path=/sentinel-test/**          filters:            - name: RequestRateLimiter              args:                # 令牌桶每秒填充平均速率                redis-rate-limiter.replenishRate: 1                # 令牌桶上限                redis-rate-limiter.burstCapacity: 2                # 指定解析器,使用spEl表达式按beanName从spring容器中获取                key-resolver: "#{@pathKeyResolver}"            - StripPrefix=1  redis:    host: 127.0.0.1    port: 6379

我们使用请求的路径作为限流的键,编写对应的解析器:

@Slf4j@Componentpublic class PathKeyResolver implements KeyResolver {    public Mono<String> resolve(ServerWebExchange exchange) {        String path = exchange.getRequest().getPath().toString();        log.info("Request path: {}",path);        return Mono.just(path);    }}

启动 gateway,使用 jmeter 进行测试,设置请求间隔为 500ms,因为每秒生成一个令牌,所以后期达到了每两个请求放行 1 个的限流效果,在被限流的情况下,HTTP 请求会返回 429 状态码。

除了上面的根据请求路径限流外,我们还可以灵活设置各种限流的维度,例如根据请求 header 中携带的用户信息、或是携带的参数等等。当然,如果不想用 gateway 自带的这个 Redis 的限流器的话,我们也可以自己实现 RateLimiter 接口来实现一个自己的限流工具。

gateway 实现限流的关键是 spring-cloud-gateway-core 包中的 RedisRateLimiter 类,以及 META-INF/scripts 中的 request-rate-limiter.lua 这个脚本,如果有兴趣可以看一下具体是如何实现的。

总结

总的来说,要保证系统的抗压能力,限流是一个必不可少的环节,虽然可能会造成某些用户的请求被丢弃,但相比于突发流量造成的系统宕机来说,这些损失一般都在可以接受的范围之内。前面也说过,限流可以结合熔断、降级一起使用,多管齐下,保证服务的可用性与健壮性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/584537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023 年 14 个最佳隐私和安全 Linux 发行版

随着数字时代的到来&#xff0c;隐私和安全成为了越来越重要的议题。对于那些关注隐私和安全的用户来说&#xff0c;选择一款专注于保护用户数据和系统安全的Linux发行版是至关重要的。在本文中&#xff0c;我们将介绍2023年14个最佳的隐私和安全Linux发行版&#xff0c;这些发…

stm32学习笔记-11 SPI通信

11 SPI通信 文章目录 11 SPI通信11.1 SPI通信协议11.2 W25Q64简介11.3 实验&#xff1a;软件SPI读写W25Q6411.4 SPI通信外设11.5 实验&#xff1a;硬件SPI读写W25Q64 注&#xff1a;笔记主要参考B站 江科大自化协 教学视频“ STM32入门教程-2023持续更新中”。 注&#xff1a…

Acrel-2000系列监控系统在亚运手球比赛馆建设

10kV供配电工程中的应用 摘要:智能化配电监控系统是数字化和信息化时代应运而生的产物&#xff0c;已经被广泛应用于电网用户侧楼宇、体育场馆、科研设施、机场、交通、医院、电力和石化行业等诸多领域的高/低压变配电系统中。安科瑞自研的Acrel-2000系列监控系统可监控高压开…

【自动化测试】自动化测试框架那些事儿!

无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架。在教学的过程中&#xff0c;同学们一直对“框架”这个词知其然不知其所以然。 最近看了很多自动化相关的资料&#xff0c;加上一些实践&#xff0c;算是对“框架”有了一些理解…

OceanMind海睿思入选《2023中国企业数智化转型全景图中国数据智能产业图谱》

近日&#xff0c;国内知名大数据产业创新服务媒体数据猿携手上海大数据联盟发布了《2023中国企业数智化转型升级服务全景图/产业图谱》和《2023中国数据智能产业图谱》。 两份图谱系统梳理了中国数智化转型升级及数据智能行业发展现状和脉络&#xff0c;评选出极具商业合作价值…

​​​​Linux Shell 实现一键部署Oracle21 zip包方式

oracle前言 Oracle开发的关系数据库产品因性能卓越而闻名&#xff0c;Oracle数据库产品为财富排行榜上的前1000家公司所采用&#xff0c;许多大型网站也选用了Oracle系统&#xff0c;是世界最好的数据库产品。此外&#xff0c;Oracle公司还开发其他应用程序和软件。同时&#…

ABeam News|ABeam荣获「SAP BTP 卓越业务分析奖」

近日&#xff0c;「云铸数卯&#xff0c;榫合万象」SAP BTP 客户与生态峰会在桂林圆满落幕。此次年度峰会以全面的视角展现 RISE with SAPSAP BTP如何构建“新型中国企业”数字底座。 ABeam Consulting作为SAP金牌合作伙伴受邀出席&#xff0c;大中华区董事长兼总经理中野洋辅…

Ubuntu Go语言环境配置【GPT-4版】

目录 go语言的培训网课安装goGPT-4给出的过程在VSCode里怎样正确配置好Go的调试相关设置&#xff1f;如果我在Visual Studio Code中安装Go语言的相关工具总是失败怎么办&#xff1f;我已重启Visual Studio Code&#xff0c;接下来应该怎样检查日志&#xff1f;如果我还是不能成…

Vue2中给对象添加新属性界面不刷新

Vue2中给对象添加新属性界面不刷新? Vue2.x的响应式 实现原理 对象类型&#xff1a;通过Object.defineProperty()对属性的读取、修改进行拦截&#xff08;数据劫持&#xff09;。数组类型&#xff1a;通过重写更新数组的一系列方法来实现拦截。&#xff08;对数组的变更方法…

敏捷世界还需要有QA吗?

敏捷开发模型现在已经被越来越多的公司熟知并使用&#xff0c;很多公司会选择敏捷开发模式的转型&#xff0c;其主要目的就是为了不断适应变化以及客户快速交付的诉求。为什么敏捷开发能够被诸多大众接受&#xff1f;可以从两个维度来看&#xff1a; 首先&#xff0c;从开发人员…

条码系统(PC/PDA)对接NC接口功能

条码系统功能清单功能模块功能菜单明细菜单支持终端功能描述业务流程框架pc端框架搭建 PC端整体框架工具搭建,提供字典配置,PDA端的参数配置,NC数据源,NC接口调用工具等 PDA端框架搭建 PDA端安卓端框架基础功能APP创建,数据源,功能菜单等 服务器环境部署 PC端/PDA端pc端/P…

使用YOLOV8实现滑块缺口验证码识别,并使用Fastdeploy快速部署,精度高达99%!!

前言:首先大家对滑块缺口验证码的识别应该有很多经验了,大部分人可能阅读过我的文章或者其他人的文章,能从各方了解到,滑块缺口的各种实现方式,例如模板匹配、边缘检测、提取透明通道、yolov5,paddledection等,此篇文章将会讲解yolov8的使用和训练,yolov8相对于yolov5有…

真心牛x,阿里出品2023最新版Spring全家桶进阶笔记流出,堪称Java程序员跳槽神器

最近小伙伴在我后台留言是这样的&#xff1a; ​现在就这光景&#xff0c;不比以前&#xff0c;会个CRUD就有人要&#xff0c;即使大部分公司依然只需要做CRUD的事情......现在去面试&#xff0c;只会CRUD还要被吐槽&#xff1a; ​面试造火箭&#xff0c;工作拧螺丝&#xff0…

六级备考23天|CET-6|翻译技巧5|2019年12月真题|翻译荷花lotus|11:05-12:05

目录 一、中文 句子1 PRACTICE ANSWER 句子2 PRACTICE ANSWER 句子3 ​ PRACTICE ANSWER 句子4 PRACTICE ANSWER 句子5 PRACTICE ANSWER 句子6 PRACTICE ANSWER ​ 答案整合​ 一、中文 句子1 荷花是中国的名花之一&#xff0c;深受人们喜爱。 PRACTICE Lotus is one…

CMakeLists.txt 文件详解

目录 CMakeLists.txt 常见内容和结构: 文件中的命令和配置&#xff1a; 官方文档&#xff1a; CMakeLists.txt CMakeLists.txt 文件是用于描述 CMake 构建过程和项目配置的文件。它包含了一系列 CMake 命令、变量设置和流程控制结构&#xff0c;用于告诉 CMake 如何生成适合…

pycharm中关于debug模式中按钮的介绍

文章目录 前言一、pycharm中debug的按钮介绍&#xff08;横排按钮&#xff09;二、pycharm中debug的按钮介绍&#xff08;竖排按钮&#xff09;总结 前言 遇到了debug模式下的调试按钮问题。 一、pycharm中debug的按钮介绍&#xff08;横排按钮&#xff09; 步过&#xff1a;s…

七人拼团系统开发模式,助力企业三个月新增超十万会员!

现在市场上最不缺的就是好产品&#xff0c;产品在市场上目前已经趋于饱和。要想让自己的产品或企业快速打入市场&#xff0c;最好的办法是结合好的营销模式&#xff0c;让企业快速起步裂变提升销量。现在就有这么一款模式&#xff0c;助力企业在三个月内就新发展了超十万会员&a…

MySql高可用方案

MySql高可用方案 1、主从、主主(互为主从)keepalived 2、MHA MHA&#xff08;Master High Avaliable&#xff09; 是一款 MySQL 开源高可用程序&#xff0c;MHA 在监测到主实例无响应后&#xff0c;可以自动将同步最靠前的 Slave 提升为 Master&#xff0c;然后将其他所有的 S…

【JavaSE】Java基础语法(三十三):File 一文详解

文章目录 1. File类概述和构造方法2. 绝对路径和相对路径3. File类创建功能【应用】4. File类删除功能【应用】5. File类判断和获取功能【应用】6. 6File类练习一【应用】7. File类练习二【应用】 1. File类概述和构造方法 File类介绍 它是文件和目录路径名的抽象表示文件和目录…

阿里面试测试工程师,水太深,我把握不住了......

前言 去阿里面试测试工程师&#xff0c;这里面水太深&#xff0c;什么未来规划&#xff0c;职业发展的东西都是虚拟的&#xff0c;作者还太年轻&#xff0c;没有那个经历&#xff0c;把握不住。项目只有几个&#xff0c;开心快乐就行&#xff0c;不PK&#xff0c;文明PK。 很多…