Spring Cloud Gateway3.x自定义Spring Cloud Loadbalancer负载均衡策略以及实现动态负载均衡策略的方案

news2024/10/6 20:31:02

目录

前言

1.原理分析

1.1 ReactiveLoadBalancerClientFilter源码分析        

1.2 LoadBalancerClientFactory源码分析

2.代码实现       

2.1 扩展原生RoundRobinLoadBalancer轮询策略

2.1.1 自定义实现RoundRobinLoadBalancer

2.1.2 配置自定义的RoundRobinLoadBalancer

2.2 扩展原生RandomLoadBalancer随机策略

2.2.1 自定义实现RandomLoadBalancer

2.2.2 配置自定义的RandomLoadBalancer

2.3 动态绑定client和loadBalancer并注册到LoadBalancerClientFactory


前言

        工作和兴趣的使然,由于需要对各种开源的项目做一些自定义的插件以及扩展,所以会经常研究一些开源组件的源码。正好前段阵子公司内部计划进行产品依赖版本升级,springcloud升级到2021.0.6,spring boot升级到2.7.11了,自然spring cloud gateway就随之升级到了3.1.6,带来的问题就是gateway内部组件的大调整和更新,比如scg在Hoxton.M2 RELEASED版本之前,内部的负载均衡组件一直用的都是ribbon,现在新版本的cloud包括boot内部的负载均衡组件统一用了spring自己的loadbalancer,那么我们开发的针对于老版本的一些插件和扩展就不能用了,需要对新的组件进行适配了,本人正好负责这次的版本升级工作,所以想空闲时间就把这些升级的东西和过程写下来。都是本人手搓的一手代码,创作不易,望诸君高台贵手,点赞支持。

1.原理分析

        直接进入主题,本文主要核心有两点:

  1. 针对scg的loadbalancer组件做自定义扩展,官方内置的负载策略目前只有两种:RandomLoad和RoundLoad,那么我们在实际业务中这两种可能满足不了业务需求,例如:使用轮询策略RoundRobinLoadBalancer会每次轮询访问目标服务实例,但是有可能业务上会有一些自己业务的判断逻辑,比如我的下游目标服务起了5个实例,每次轮询都会在这五个实例里面公平的循环轮询,现在公司说service端需要升级,但是又不想强制要client端升级才能使用,这样客户才会比较好容易接受,所以现在把其中3个实例升级到最新版本,剩下的两个实例还是老版本,那么在网关这里如果还是使用的老的轮询策略,就会出现一个问题:假如当前请求是来自老的客户端,那么如果轮询到了新版本实例上去了或者新客户端请求轮询到老的版本实例上去了,就有可能会出现一系列问题,这个时候就需要在轮询的时候加一些自己业务的判断上去,好让实例choose能满足业务需求。
  2. 实现一个动态配置服务负载均衡策略的逻辑:就比如在scg里面,服务a使用轮询,服务b使用随机,运行了一段时间后,想把服务a的负载均衡策略改成随机或者是其他自定义的策略,无需改代码以及启停服务,直接动态修改配置热更新生效
1.1 ReactiveLoadBalancerClientFilter源码分析        

        查看scg的源码我们不难发现,scg内部负载均衡的核心逻辑都是由一个GlobalFilter来实现的,这个全局filter叫:ReactiveLoadBalancerClientFilter,查看这个filter的代码我们可以看到它有个核心方法choose(),这个方法的作用就是根据当前所需要路由的serviceId来选择对应的loadBalancer,然后通过loadBalancer来选择出最终要路由到的serviceInstance,那么通过这个方法我们能抓到两个核心要素:

1.拿到路由目标service对应的loadBalancer;

2.通过loadBalancer来choose出最终要路由的实例serviceInstance。

经过上面分析,我们就能明白了,实现自定义loadBalancer的核心逻辑就是:

1.自定义自己的ReactorLoadBalancer;

2.在自定义的ReactorLoadBalancer里面实现自己的choose逻辑;

3.动态将serviceId和自己的loadBalancer绑定并注册到LoadBalancerClientFactory(由代码this.clientFactory.getInstance()可知)中去。

1.2 LoadBalancerClientFactory源码分析

         spring cloud loadBalancer是怎么实现每个client绑定自己的配置的呢,官方提供了两个注解:@LoadBalancerClient@LoadBalancerClients,首先我们来看@LoadBalancerClient的源码,可以看到@LoadBalancerClient提供了三个参数:name、value、configuration[],看注解就知道name、value就是配置客户端的名称(客户端名称我们就可以理解为一个应用的applicationName,因为在实际负载中都是用服务名作为clientId、serviceId),configuration就是客户端对应的自定义的配置,包括负载策略的配置、健康检查策略的配置等等,这里我们只需要关注负载策略的自定义就好,其他的用默认就行,如果有需求也可以都自定义。

接下来我们看@LoadBalancerClients源码

可以发现有个@Import注解,我们打开Import里面的LoadBalancerClientConfigurationRegister类,发现它实现了ImportBeanDefinitionRegister接口并重写了registerBeanDefinitions方法,我们重点看下这个方法,发现它里面干了3件事:

1.获取所有的@LoadBalancerClients注解的元数据,拿到代码里LoadBalancerClients注解的配置,然后根据配置进行loadBalancerClientConfiguration的绑定;

2.如果第一步里面@LoadBalancerClients里面如果配置的是defaultConfiguration,那么就用默认的配置进行绑定;

3.获取所有的@LoadBalancerClient注解的元数据,然后同样的拿到代码LoadBalancerClient注解的配置,然后根据配置进行loadBalancerClientConfiguration的绑定

那我们看下具体是怎么绑定的,看源码:

本质就是构建一个LoadBalancerClientSpecification bean,这个spec就是每个客户端自定义负载策略的核心bean,我们看下它的源码,不难发现,它有两个属性:name、configuration[],是不是发现似曾相识,就是@LoadBalancerClient注解的两个属性,所以最终就是为了装配这个bean,构建完ben之后,那俩注解的作用就完成了,接下来我们在继续看下spring是如何实现前面我们所讲的:在路由时,是如何通过serviceId获取到这个客户端配置的。

接下来我们继续,所有的配置动作已经解析完成了,那么就是将配置交给spring容器了,spring是如何加载这个LoadBalancerClientSpecification的呢,我们跟随配置:spring.cloud.loadbalancer.ebabled会发现有个自动装配类:LoadBalancerAutoConfiguration,查看这个配置类源码我们可以发现,有参构造的参数就是LoadBalancerClientSpecification,然后再进行初始化bean:LoadBalancerClientFactory这个loadBalancer的核心factory。

查看LoadBalancerClientFactory的源码我们可以看到它继承了:NamedContextFactory,那么这个作用是什么呢:子容器之间的数据隔离。NamedContextFactory的作用是创建一个子容器(子上下文context),然后每个子容器通过LoadBalancerClientSpecification来定义客户端容器name以及数据配置。我们回到开头所讲的ReactiveLoadBalancerClientFilter这个filter,在请求进来的时候通过LoadBalancerClientFactory拿serviceId去获取这个客户端对应的loadBalancer,跟进源码,我们发现它最终调用的是NamedContextFactory的getInstance()方法,然后调用getContext(),主要逻辑就是通过serviceId去获取子容器,如果没有那么就创建一个新的子容器(子上下文),查看源码我们不难发现,新的子容器name就是用的serviceId,然后再拿到对应的LoadBalancerClientSpecification来注册到子容器中去;所以到这里,再回到上面,我们看LoadBalancerAutoConfiguration中的LoadBalanceClientrFactory的初始化就干了一件事:将所有的客户端的配置LoadBalancerClientSpecification注册到NamedContextFactory中去;然后随着请求过来时,拿到已经注册好的LoadBalancerClientSpecification对当前请求的客户端进行子上下文的初始化。

createContext()源码截图:

        到此,经过上述简单的源码分析,那么原理和实现方案我们就已经大致明白了,接下来就直接上代码来验证。

2.代码实现       

想要实现自定义负载策略,首先需要实现官方接口:ReactorServiceInstanceLoadBalancer ,查看代码我们会发现这个接口是spring loadBalancer官方提供的接口,所有的策略都需要实现它,所以我们自定义的策略也不例外,具体代码如下:

2.1 扩展原生RoundRobinLoadBalancer轮询策略
2.1.1 自定义实现RoundRobinLoadBalancer
package com.primeton.gateway.core.lb;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description 轮询
 * @Author wx
 * @Date 2023/5/26
 */
public class GatewayRoundLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Log log = LogFactory.getLog(GatewayRoundLoadBalancer.class);

    final AtomicInteger position;

    private final String serviceId;

    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public GatewayRoundLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                    String serviceId) {
        this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
    }

    public GatewayRoundLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                    String serviceId,
                                    int seedPosition) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(seedPosition);
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, request));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances,
                                                              Request request) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances, request);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,
                                                          Request request) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }

        // Do not move position when there is only 1 instance, especially some suppliers
        // have already filtered instances
        if (instances.size() == 1) {
            return new DefaultResponse(instances.get(0));
        }

        List<ServiceInstance> useInstances = customChoose(instances, request);

        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;

        ServiceInstance instance = useInstances.get(pos % useInstances.size());

        return new DefaultResponse(instance);
    }

    /**
     * 自定义instances choose出满足业务请求的实例,然后按照轮询策略来从
     * 剩下的满足业务需求的实例列表选出最终的实例
     */
    private List<ServiceInstance> customChoose(List<ServiceInstance> instances, Request request) {
        //todo 比如根据request中的参数来筛选、 或者筛选出实例元数据中含有某些符合参数的实例 等等
        List<ServiceInstance> use = new ArrayList<>();
        for (ServiceInstance instance : instances) {
            Map<String, String> metadata = instance.getMetadata();
            if (metadata.containsKey("xxx")) use.add(instance);
        }
        return use;
    }

}
2.1.2 配置自定义的RoundRobinLoadBalancer
package com.primeton.gateway.core.lb;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

/**
 * @Description TODO
 * @Author wx
 * @Date 2024/6/13
 */
public class GatewayRoundLoadBalancerConfiguration {

    @Bean
    public GatewayRoundLoadBalancer gatewayRoundLoadBalancer(Environment environment,
                                                             LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new GatewayRoundLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }

}
2.2 扩展原生RandomLoadBalancer随机策略
2.2.1 自定义实现RandomLoadBalancer
package com.primeton.gateway.core.lb;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @Description 随机
 * @Author wx 
 * @Date 2023/5/26
 */
public class GatewayRandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Log log = LogFactory.getLog(GatewayRandomLoadBalancer.class);

    private final String serviceId;

    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public GatewayRandomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                     String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, request));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances,
                                                              Request request) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances, request);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,
                                                          Request request) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }

        List<ServiceInstance> useInstances = customChoose(instances, request);
        
        int index = ThreadLocalRandom.current().nextInt(useInstances.size());

        ServiceInstance instance = useInstances.get(index);

        return new DefaultResponse(instance);
    }

    //todo 节合实际业务来筛选
    private List<ServiceInstance> customChoose(List<ServiceInstance> instances, Request request) {
        return instances;
    }

}
2.2.2 配置自定义的RandomLoadBalancer
package com.primeton.gateway.core.lb;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

/**
 * @Description TODO
 * @Author wx
 * @Date 2024/6/13
 */
public class GatewayRandomLoadBalancerConfiguration {

    @Bean
    public GatewayRandomLoadBalancer gatewayRandomLoadBalancer(Environment environment,
                                                               LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new GatewayRandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }

}
2.3 动态绑定client和loadBalancer并注册到LoadBalancerClientFactory

看完前面的原理分析,那我们就明白要实现client动态绑定loadBalancer并注册到LoadBalancerClientFactory去,要做的就是两件事:1.根据业务配置对每个客户端进行LoadBalancerClientSpecification组装;2.组装好LoadBalancerClientSpecification注册到LoadBalancerClientFactory也就是NamedContextFactory上下文里面去。具体实现如下:

package com.primeton.gateway.core.lb;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @Description TODO
 * @Author wx
 * @Date 2023/5/26
 */
@Configuration
public class GatewayLoadBalancerConfiguration {

    private static final String SUFFIX = ".loadbalancer.LoadBalancer-configuration-class-name";

    @Autowired
    private ConfigurableEnvironment env;

    @Autowired
    private LoadBalancerClientFactory loadBalancerClientFactory;

    @PostConstruct
    public void postConstruct() {
        //第一步:解析业务配置,从而解析出来每个客户端对应的负载策略配置
        HashMap<String, String> configs = new HashMap<>();
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            if (propertySource instanceof EnumerablePropertySource) {
                for (String name : ((EnumerablePropertySource) propertySource).getPropertyNames()) {
                    if (name != null && name.endsWith(SUFFIX)) {
                        configs.put(name, env.getProperty(name));
                    }
                }
            }
        }

        //第二步:组装Specification并绑定到spring上下文中去
        List<LoadBalancerClientSpecification> configurations = new ArrayList<>();

        for (String clientId : configs.keySet()) {
            String id = clientId.substring(0, clientId.length() - SUFFIX.length());
            try {
                Class<?>[] classes = {Class.forName(configs.get(clientId))};

                LoadBalancerClientSpecification specification = new LoadBalancerClientSpecification();
                specification.setName(id);
                specification.setConfiguration(classes);
                configurations.add(specification);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

        }
        loadBalancerClientFactory.setConfigurations(configurations);
    }


}

上面代码我做一下简单的思路描述:我们可以把上面代码分为两部分:

1.第一部分我是自定义了一个配置规则:serviceId..loadbalancer.LoadBalancer-configuration-class-name=xxxxx (全路径);比如:DEMO01..loadbalancer.LoadBalancer-configuration-class-name=com.primeton.gateway.core.lb.GatewayRoundLoadBalancerConfiguration(这个配置类看2.1.2章节),这就表示客户端应用DEMO01的负载均衡策略就是我在2.1.2自定义的轮询策略,所有需要路由到DEMO01的请求都要走我们2.1.1里面的逻辑进行choose()筛选出最终要路由的实例,这个配置可以放在配置文件,也可以放在其他地方;

2.第二部分就是针对所有的客户端负载策略配置进行组装spring loadBalancer需要的LoadBalancerClientSpecification,然后最终模拟源码里面的绑定动作将我们自己组装好的数据设置到NamedContextFactory上下文中去。

到此我们自定义客户端负载均衡策略方案就实现了,但是还差最后一步:怎么动态更新呢,比如现在DEMO01我们配置的是我们写的GatewayRoundLoadBalancerConfiguration,我们想要将它换成GatewayRandomLoadBalancerConfiguration 随机策略,由于时间问题,我这里就给大家出个方案,具体实现我就不写了,有时间再给大家写:

方案1:结合配置中心:nacos、Apollo等做配置热更新,监听nacos、Apollo配置,当配置有变化时,nacos、Apollo服务端都会发送通知,你只需要在代码里创建一个listener,然后针对我们上面定义的key,然后把上面代码的步骤再走一遍就行了。

方案2:将配置更新做成接口化,gateway写个controller专门用来管理配置,然后配置有变化通过调用gateway对应接口来通知gateway进行更新,最后再走一遍上面代码即可

方案3:结合redis,配置数据存在redis里面,利用redis的键空间监听通知监听这个配置,当配置有改动的时候redis会发出通知,我们在gateway里面监听好,然后进行上面的代码即可

当然还有很多其他方案,核心实现已经分享给大家了,剩下的就看诸位结合自身业务采取什么样的配置方案了。

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

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

相关文章

【源码+文档+调试讲解】基于Java的推箱子游戏设计与实现

摘 要 社会在进步&#xff0c;人们生活质量也在日益提高。高强度的压力也接踵而来。社会中急需出现新的有效方式来缓解人们的压力。此次设计符合了社会需求&#xff0c;Java推箱子游戏可以让人们在闲暇之余&#xff0c;体验游戏的乐趣。具有操作简单,易于上手的特点。 推箱子…

Vulnhub靶场DC-4练习

目录 0x00 准备0x01 主机信息收集0x02 站点信息收集0x03 漏洞查找与利用1. 爆破登录2. 命令执行3. 反弹shell4. hydra爆破ssh5. 提权 0x04 总结 0x00 准备 下载链接&#xff1a;https://download.vulnhub.com/dc/DC-4.zip 介绍&#xff1a; DC-4 is another purposely built …

短视频矩阵系统搭建APP源码开发

前言 短视频矩阵系统不仅有助于提升品牌影响力和营销效率&#xff0c;还能帮助企业更精准地触达目标受众&#xff0c;增强用户互动&#xff0c;并利用数据分析来持续优化营销策略。 一、短视频矩阵系统是什么&#xff1f; 短视频矩阵系统是一种通过多个短视频平台进行内容创作…

汉语翻译藏语软件,这几款软件不妨一试!

在全球化日益加深的今天&#xff0c;语言障碍成为了许多人在文化交流、商务洽谈或旅游探险中不得不面对的问题。特别是对于汉语和藏语这两种语言来说&#xff0c;由于其独特的文化背景和语法结构&#xff0c;翻译起来更是难上加难。不过&#xff0c;好在科技的进步为我们带来了…

可变分区管理 分区分配算法

First Fit Algorithm Best Fit Algorithm FFA&#xff1a;按照起始地址从小到大&#xff08;本题为分区编号&#xff09;找到第一个能装下进程的起始地址填入第二个表 此时 原表中将起始地址进程大小 分区大小-进程大小 如此继续 BFA&#xff1a;按分区大小排序 从小到大 找到…

面试-synchronized(java5以前唯一)和ReentrantLock的区别

1.ReentrantLock&#xff08;再入锁&#xff09;&#xff1a; (1).在java.util.concurrent.locks包 (2).和CountDownLatch,FutureTask,Semaphore一样基于AQS实现。 AQS:AbstractQueuedSynchronizer 队列同步器。Java并发用来构建锁或其他同步主键的基础框架&#xff0c;是j.u.c…

vue3通过vue-video-player实现视频倍速、默认全屏、拖拽进度条等功能

效果图&#xff1a; 1、场景&#xff1a; js原生的video标签在不同浏览器及不同型号手机上都展示的不一样&#xff0c;一部分没有倍速&#xff0c;一部分没有全屏等功能&#xff0c;为了统一视频播放的交互功能&#xff0c;使用vue-video-player插件来完成&#xff0c;vue-vid…

3_电机的发展及学习方法

一、电机组成及发展 1、什么是励磁&#xff1f; 在电磁学中&#xff0c;励磁是通过电流产生磁场的过程。 发电机或电动机由在磁场中旋转的转子组成。磁场可以由 永磁体或励磁线圈产生。对于带有励磁线圈的机器&#xff0c;电流必须在线圈中流动才能产生&#xff08;激发&#x…

基于JSP的列车票务信息管理系统

开头语&#xff1a; 你好&#xff0c;我是专注于计算机科学与技术研究的学长。如果你对列车票务信息管理系统感兴趣或有相关需求&#xff0c;欢迎联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;IDE、数据库管理工具…

【java计算机毕设】网络教学平台java MySQL ssm vue html maven 项目设计 源代码+万字文档

目录 1项目功能 2项目介绍 3项目地址 1项目功能 【java计算机毕设】网络教学平台系统MySQL ssm vue html maven项目代码文档 小组作业 2项目介绍 系统功能&#xff1a; 网络教学平台系统包括管理员、学生、教师俩种角色。 管理员功能包括个人中心模块用于修改个人信息和密码…

HBDNY-40/1端子排电压继电器 DC110V 导轨安装 约瑟JOSEF

HBDNY系列端子排型电压电流继电器 系列型号&#xff1a;&#xff08;3、4过/低电压型&#xff0c;5、6过/低电流型&#xff09; HBDNY-30/1端子排型电压继电器&#xff1b;HBDNY-30/2端子排型电压继电器&#xff1b; HBDNY-30/3端子排型电压继电器&#xff1b;HBDNY-30/4端子…

芒果YOLOv10改进64:主干Backbone篇RepVGG结构:简单但功能强大的卷积神经网络架构

💡本篇内容:YOLOv10改进RepVGG结构:简单但功能强大的卷积神经网络架构 💡🚀🚀🚀本博客 改进源代码改进 适用于 YOLOv10 按步骤操作运行改进后的代码即可 💡本文提出改进 原创 方式:二次创新,YOLOv10 应部分读者要求,新增一篇RepVGG 论文理论部分 + 原创最…

AI视频教程下载-定制GPT:使用您的数据创建一个定制聊天GPT

Custom GPTs_ Create a Custom ChatGPT with Your Data 构建一个定制的GPT&#xff0c;与您自己的数据进行聊天。添加文档&#xff0c;生成图像&#xff0c;并集成API和Zapier。 这门全面的Udemy课程专为那些渴望学习如何创建自己定制版ChatGPT的人设计&#xff0c;以满足他们…

Java 面试笔记 | Java 基础:线程池

前言 在日常的工作学习生活中&#xff0c;用一种好的方法去学习&#xff0c;可以更加有效&#xff0c;比如费曼学习法&#xff1a;将学到的知识用自己的组织的语言表达出来&#xff0c;如果能够清晰明白的向别人解释清楚&#xff0c;那么就说明你是真的懂了&#xff0c;学会了…

常用调试器的接口介绍、支持标准及接线方法

目录 一、STLink接口介绍及接线方法 二、DAPLink接口介绍及接线方法 三、推荐购买链接&#x1f517; 一、STLink接口介绍及接线方法 第一种&#xff1a; 有10个接口&#xff0c;其中SWIM是STM8专用引脚。 查阅资料发现仅支持SWD标准&#xff0c;不支持JTAG标准。我们就知道…

深入理解桥接模式(Bridge Pattern)及其实际应用

引言 在软件开发过程中&#xff0c;设计模式为我们提供了优雅且高效的解决方案&#xff0c;以应对常见的设计问题。桥接模式&#xff08;Bridge Pattern&#xff09;作为一种结构型设计模式&#xff0c;旨在将抽象部分与其实现部分分离&#xff0c;使它们可以独立变化&#xf…

老杨说运维 | 基于业务全链路的端到端排障分析(文末附现场视频)

前言 青城山脚下的滔滔江水奔涌而过&#xff0c;承载着擎创一往无前的势头&#xff0c;共同去向未来。2024年6月&#xff0c;双态IT成都用户大会擎创科技“数智化可观测赋能双态运维”专场迎来了完满的收尾。 本期回顾来自擎创科技产品总监殷传旺的现场演讲&#xff1a;云原生…

雨量传感器设备的监测控制和智慧运维

雨量传感器是一种用于测量降雨量的设备&#xff0c;它通常通过一些感应机制来检测雨水的数量或强度。雨量传感器在气象监测、农业、水利等领域有着广泛应用。 在气象领域&#xff0c;它能为天气预报和气候研究提供重要的数据支持&#xff1b;在农业方面&#xff0c;可以帮助农民…

从零开始如何学习人工智能?

我接触AI的时候&#xff0c;是在研一。那个时候AlphaGo战胜围棋世界冠军李世石是大新闻&#xff0c;人工智能第一次出现我面前&#xff0c;当时就想搞清楚背后的原理以及这些技术有什么作用。 就开始找资料&#xff0c;看视频。随着了解的深入&#xff0c;对AI的兴趣就越大。这…

【已解决】Python报错:AttributeError: module ‘json‘ has no attribute ‘loads‘

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《AI实战中的各种bug…