当前位置: 首页 > news >正文

SpringCloud 学习笔记1(Spring概述、工程搭建、注册中心、负载均衡、 SpringCloud LoadBalancer)

文章目录

  • SpringCloud
    • SpringCloud 概述
      • 集群和分布式
        • 集群和分布式的区别和联系
      • 微服务
        • 什么是微服务?
        • 分布式架构和微服务架构的区别
        • 微服务的优缺点?
        • 拆分微服务原则
      • 什么是 SpringCloud ?
        • 核心功能与组件
    • 工程搭建
      • 父项目的 pom 文件
    • 注册中心
      • RestTemplate
      • 注册中心介绍
      • CAP 理论
      • Eureka
        • 添加依赖
        • 配置文件
        • 启动类
        • 查看
        • 服务注册
          • 添加依赖
          • 配置文件
          • 查看
        • 服务发现
          • 依赖、配置
          • 修改代码
        • Eureka 和 Zookeeper 区别
    • 负载均衡
      • 多次在不同的端口号上开启同一个服务
      • 出现的问题
      • 解决问题
      • 负载均衡正式介绍
        • 什么是负载均衡
        • 负载均衡的一些实现
          • 服务端负载均衡
          • 客户端负载均衡
        • SpringCloud LoadBalancer
          • 负载均衡策略
          • LoadBalancer 原理
            • 随机选择策略
            • 轮询策略
          • 服务部署

SpringCloud

SpringCloud 概述

集群和分布式

集群:是将一个系统完整的部署到多个服务器上,每个服务器都能提供系统的所有服务,多个服务器通过负载均衡调度完成任务,每个服务器称为集群的节点。

分布式:是将一个系统拆分成多个子系统,多个子系统部署在多个服务器上,多个服务器上的子系统协同合作完成一个特定任务。

集群和分布式的区别和联系
  1. 集群是多个计算机做同样的事情,分布式是多个计算机做不同的事。
  2. 集群的每一个节点的功能是相同的,并且是可以替代的。分布式也是多个节点组成的系统,但是每个节点完成的任务是不同的,一个节点出现问题,这个业务就无法访问了。
  3. 分布式和集群在实践中,很多时候都是相互配合使用的。比如分布式的某一个节点,可能由一个集群来代替。分布式架构大多数是建立在集群上的。所以实际的分布式架构中并不会把分布式和集群单独区分,而是统称:分布式架构。

微服务

什么是微服务?

微服务是一种经过良好架构设计的分布式架构方案。

一个服务只对应一个单一的功能,只做一件事,这个服务可以单独部署运行。

分布式架构和微服务架构的区别

分布式:服务拆分,拆了就行。

微服务:指非常微小的服务,更细粒度的垂直拆分,通常指不能再拆的服务。

分布式架构侧重于压力的分散,强调的是服务的分散化,微服务侧重于能力的分散,更强调服务的专业化和精细分工。

微服务的优缺点?

优点:

image-20250311173842098

缺点:

image-20250311173920969

拆分微服务原则
  1. 单一职责原则

    单一职责原则原本是面向对象程序设计中的一个基本原则,它指的是一个类应该专注于单一功能。

    在微服务架构中,一个微服务也应该只负责一个功能或业务领域,每个服务应该有清晰的定义和边界,只关注自己的特定业务领域。

  2. 服务自治

    服务自治是指每个微服务都应该具备高度自治的能力,即每个服务要能做到独立开发,独立测试, 独立构建, 独立部署,独立运行。

  3. 单向依赖

    微服务之间需要做到单向依赖,严禁循环依赖,双向依赖。

    image-20250311174451006

    如果一些场景确实无法避免循环依赖或者双向依赖,,可以考虑使用消息队列等其他方式来实现。

什么是 SpringCloud ?

Spring Cloud 是一套基于 Spring Boot 的微服务开发工具集,用于简化分布式系统(如微服务架构)的构建、部署和管理。它整合了多种开源组件,提供了一站式解决方案,帮助开发者快速实现服务治理、配置管理、负载均衡、熔断降级等分布式系统中的常见问题。

简单的说,Spring Cloud 就是分布式微服务架构的一站式解决方案。

核心功能与组件
  1. 服务注册与发现
    • 组件:EurekaNacosConsul
    • 功能:服务自动注册到注册中心,并通过服务名实现动态发现,避免硬编码服务地址。
  2. 负载均衡
    • 组件:RibbonLoadBalancer
    • 功能:在多个服务实例间分配请求,支持轮询、随机等策略。
  3. 服务调用
    • 组件:OpenFeign
    • 功能:声明式的 HTTP 客户端,简化服务间的 RESTful 调用。
  4. 熔断与容错
    • 组件:HystrixResilience4jSentinel
    • 功能:防止服务雪崩,提供降级逻辑和故障隔离。
  5. 配置中心
    • 组件:Spring Cloud ConfigNacos
    • 功能:集中管理配置文件,支持动态更新。
  6. API 网关
    • 组件:Spring Cloud GatewayZuul
    • 功能:统一入口,处理路由、鉴权、限流等跨服务功能。
  7. 分布式链路追踪
    • 组件:Sleuth + Zipkin
    • 功能:追踪请求在微服务间的调用路径,便于排查问题。

工程搭建

父项目的 pom 文件

指定父项目的打包方式:

image-20250311175319332

添加依赖:

image-20250311175027099

子项目被创建时,父项目的 pom 文件中会自动添加

image-20250311175514057

上面的代码表示这里有两个子项目(模块),分别是 order-service 和 product-service 。

注册中心

RestTemplate

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它是一个同步的 REST API 客户端,提供了常见的 REST 请求方案的模版。

在项目中,当我们需要远程调用一个 HTTP 接口时,我们经常会用到 RestTemplate 这个类。这个类是 Spring 框架提供的一个工具类。

定义 RestTemplate

@Configuration
public class BeanConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}

使用 RestTemplate

@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);// 通过 RestTemplate 从指定 URL 获取 ProductInfo 对象String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}
}

在前面的示例中 URL 是写死的。写死这个事情做的不好,如果服务器的 IP 发生变化,我们还得把所有 IP 都修改,太麻烦了,而且没有技术含量。

String url = "http://127.0.0.1:9090/product/";

注册中心介绍

有没有什么办法来解决这个问题呢?

我们可以这样做:

当服务 启动/变更 时, 向注册中⼼报道。注册中⼼记录应⽤和 IP 的关系。

调⽤⽅调⽤时,先去注册中⼼获取服务⽅的 IP,再去服务⽅进⾏调⽤。

image-20250311212947467

image-20250311213409451

CAP 理论

image-20250311213758583

  • 一致性(C):CAP 理论中的一致性,指的是强一致性。所有节点在同一时间具有相同的数据。

    image-20250311214959974

    强一致性:主库和从库不论何时,服务器对外提供的服务都是一致的。

    弱一致性:随着时间的推移,主库和从库最终达到了一致性。

  • 可用性(A):保证每个请求都有响应。

  • 分区容错性(P):当出现网络分区后,系统仍然能够对外提供服务。

    网络分区:指分布式系统中,由于网络故障导致集群中的节点被分割成多个孤立的子集,子集之间的节点无法正常通信,但子集内部的节点仍然可以正常通信。这种现象也被称为“脑裂”(Split-Brain)。

    举个例子

    假设有一个分布式系统,由 5 个节点(A、B、C、D、E)组成,它们之间通过网络通信。如果由于网络故障,节点 A 和 B 之间的网络断开,那么可能会形成两个分区:

    • 分区 1:节点 A、B
    • 分区 2:节点 C、D、E

    此时,分区 1 和分区 2 之间的节点无法通信,但分区内部的节点仍然可以正常通信。

在分布式系统中,系统间的⽹络不能100%保证健康, 服务⼜必须对外保证服务. 因此 分区容错性(P) 不可避免. 那就只能在 C 和 A 中选择⼀个. 也就是 CP 或者 AP 架构。

正常情况:

image-20250311215348627

网络异常:

image-20250311215423635

CP架构:为了保证分布式系统对外的数据⼀致性,于是选择不返回任何数据。

AP架构:为了保证分布式系统的可⽤性,节点2返回V0版本的数据(即使这个数据不正确)。

关于CAP的更多信息,可以看看这篇文章:一文看懂|分布式系统之CAP理论-腾讯云开发者社区-腾讯云

Eureka

添加依赖
 <!--  给客户端用  --><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency></dependencies><!--  给服务器用  -->
<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency></dependencies><!--  借助 Maven 打包  --><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
配置文件
# Eureka相关配置
# Eureka 服务
server:port: 10010
spring:application:# 这个应用的名称name: eureka-server
eureka:instance:# 主机的名称hostname: localhostclient:# 表示是否从Eureka Server获取注册信息,默认为true.因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,这里设置为falsefetch-registry: false# 表示是否将自己注册到Eureka Server,默认为true.register-with-eureka: false service-url:# 设置Eureka Server的地址,查询服务和注册服务都需要依赖这个地址defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类
@EnableEurekaServer  // 开启 Eureka 的功能
@SpringBootApplication
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}
}
查看
http://127.0.0.1:10010/

image-20250314164624808

服务注册
添加依赖
        <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>
配置文件
spring:application:# 配置应用名称name: product-service#Eureka Client
eureka:client:service-url:# 注册到哪里 / 从哪里拿相关信息defaultZone: http://127.0.0.1:10010/eureka/
查看
http://127.0.0.1:10010/

image-20250314164722794

服务发现
依赖、配置

同服务注册。

修改代码
import org.springframework.cloud.client.discovery.DiscoveryClient;   
// 注意不要引错包@Autowiredprivate DiscoveryClient discoveryClient;// 从 Eureka 中获取服务列表,括号内写应用名称List<ServiceInstance> instances = discoveryClient.getInstances("product-service");String uri = instances.get(0).getUri().toString();// 替换之前写死的 url String url = uri + "/product/" + orderInfo.getProductId();
Eureka 和 Zookeeper 区别

Eureka 和 Zookeeper 都是用于服务注册和服务发现的工具,区别如下:

  1. Eureka 基于 AP 原则,保证高可用。Zookeeper 基于 CP 原则,保证数据一致性。
  2. Eureka 每个节点都是均等的,Zookeeper 的节点区分 Leader 和 Follower 或 Observer,如果 Zookeeper 的 Leader 发生故障时,需要重新选举,选举过程集群会有短暂时间的不可用。

负载均衡

多次在不同的端口号上开启同一个服务

image-20250314172405488

点击 Services 并添加应用

image-20250314172548516

选择 Application

image-20250314172632852

复制你要多开的服务

image-20250314172710731

设置端口号,设置完毕后点击 Apply。

image-20250314172839615

可以看到,已经配置好了。

image-20250314173002148

出现的问题

当我们进行多次访问时,每次的 discoveryClient.getInstances(“product-service”); 拿到的列表是不固定的。

    public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);// 从 Eureka 中获取服务列表List<ServiceInstance> instances = discoveryClient.getInstances("product-service");String uri = instances.get(0).getUri().toString();// 替换之前写死的 urlString url = uri + "/product/" + orderInfo.getProductId();log.info("远程调用 url:{}", url);ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}

image-20250314173928563

显然这并不是很合理。

解决问题

假设我们想让请求平均分配到每个端口上。可以使用以下方法:

image-20250314174244230

修改代码:

package com.demo.order.service;import com.demo.order.mapper.OrderMapper;
import com.demo.order.model.OrderInfo;
import com.demo.order.model.ProductInfo;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;/*** @author hanzishuai* @date 2025/03/07 19:19* @Description*/
@Slf4j
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;// 计数器private AtomicInteger count = new AtomicInteger(1);// 将实例提取出来private List<ServiceInstance> instances;@PostConstructpublic void init() {// 从 Eureka 中获取服务列表instances = discoveryClient.getInstances("product-service");}// 这是之前的写法:    
//        public OrderInfo selectOrderById(Integer orderId) {
//        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
//        String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
//    
//        // 从 Eureka 中获取服务列表,这里每次请求拿到的 instances 是不固定的 
//        List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
//        String uri = instances.get(0).getUri().toString();
//    
//        // 替换之前写死的 url
//        String url = uri + "/product/" + orderInfo.getProductId();
//        log.info("远程调用 url:{}", url);
//        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
//        orderInfo.setProductInfo(productInfo);
//        return orderInfo;
//    }public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);// 实现平均分配int index = count.getAndIncrement() % instances.size();String uri = instances.get(index).getUri().toString();String url = uri + "/product/" + orderInfo.getProductId();log.info("远程调用 url:{}", url);ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}
}

但是上述写法会带来一个新的问题:当实例发生变化,这里的服务并不能实时的感知到。

不要较真,在这里只是为了演示一下。

更改后的效果:

image-20250314180245882

负载均衡正式介绍

什么是负载均衡

负载均衡用来在多个机器或者其他资源中,按照一定的规则合理分配负载。

比如说,有很多请求和很多服务器,负载均衡就是把这些请求合理的分配到各个服务器上。

负载均衡的一些实现
服务端负载均衡

服务端负载均衡就是在服务端进行负载均衡算法的分配。

以 Nginx 为例,请求先到达 Nginx 负载均衡器,然后通过负载均衡算法,在多个服务器之间选一个进行访问。

image-20250314182324386
客户端负载均衡

服务端负载均衡就是在客户端进行负载均衡算法的分配。

以 SpringCloud 的 Ribbon 为例,请求发送到客户端,客户端从注册中心获取服务器列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问。

image-20250314183108271
SpringCloud LoadBalancer

加上 @LoadBalanced 注解

@Configuration
public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
}

修改代码

    public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);// 修改前// String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();// 修改后String url = "http://product-service/product/" + orderInfo.getProductId();log.info("远程调用 url:{}", url);ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}

效果:

image-20250314203553542

image-20250314203658809

image-20250314203605370

负载均衡策略

SpringCloud LoadBalancer 仅支持两种负载均衡策略:

  1. 轮询策略: 指服务器轮流处理用户的请求.
  2. 随机选择: 随机选择一个后端服务器来处理请求.

自定义负载均衡策略

public class CustomLoadBalancerConfiguration {@BeanReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);}
}

使用方法:

  1. 需要在关联负载均衡策略的配置类上添加 @LoadBalancerClient 或者 @LoadBalancerClients 注解.

    // name 表示要对那个服务生效, configuration 表示你采取的负载均衡策略是什么.
    @LoadBalancerClient(name = "product-service",configuration = CustomLoadBalancerConfiguration.class)
    @Configuration
    public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
    }
    
  2. 自定义负载均衡策略的配置类(如 CustomLoadBalancerConfiguration)上不能使用 @Configuration 注解.

    原因: 如果 CustomLoadBalancerConfiguration 类被标记为 @Configuration,并且位于主应用程序组件扫描的路径下,它会被 Spring 自动加载为一个配置类。而当通过 @LoadBalancerClient 的 configuration 属性引用它时,可能会导致 Spring 尝试再次加载它,从而产生冲突或重复的 bean 定义。

  3. 自定义负载均衡策略的配置类(如 CustomLoadBalancerConfiguration)必须能被 Spring 容器发现。

是不是感觉与第三条第二条有矛盾?

  • 在之前的回答中提到,CustomLoadBalancerConfiguration 不能添加 @Configuration 注解,这是为了避免被 Spring 自动扫描到后重复加载。
  • 但同时又需要确保该类能被 Spring 容器发现,这里的矛盾需要通过以下方式解决:
    • 通过 @LoadBalancerClient(configuration = ...) 显式引用该类,而不是依赖组件扫描。上面的 BeanConfig 采用的就是这种方法。
    • 确保 CustomLoadBalancerConfiguration 不在主应用的扫描范围内,但能被 @LoadBalancerClient 正确引用。
LoadBalancer 原理

tip: 按 Ctrl + alt + ← 或者 → 可以快速定位上/下次查看的位置

在 LoadBalancerInterceptor 中有一个 intercept 方法:

	@Overridepublic ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) throws IOException {// 拿到 uri final URI originalUri = request.getURI();// 拿到 hostString serviceName = originalUri.getHost();Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}

它会拦截所有的请求。

它做了三件事:

  1. 拿到 uri,也就是 http://product-service/product/1001
  2. 拿到 host,也就是 product-service
  3. 执行

接下来具体看看 this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution))它干了什么。

   public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {String hint = this.getHint(serviceId);LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter(request, this.buildRequestContext(request, hint));Set<LoadBalancerLifecycle> supportedLifecycleProcessors = this.getSupportedLifecycleProcessors(serviceId);supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onStart(lbRequest);});// 通过 choose 方法返回了一个应用ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);if (serviceInstance == null) {supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, new EmptyResponse()));});throw new IllegalStateException("No instances available for " + serviceId);} else {return this.execute(serviceId, serviceInstance, lbRequest);}}

接下来看一下 choose 方法

    public <T> ServiceInstance choose(String serviceId, Request<T> request) {// 根据应用名称获取负载均衡策略ReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;} else {// 如果 loadBalancer 不为空,这里又进行了一次选择Response<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block();return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();}}

如果 loadBalancer 不为空,这里又进行了一次选择接下来进入 Response<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block() 中的 choose 看看,

可以看到它有两个实现,一个是RandomLoadBalancer,一个是RoundRobinLoadBalancer

image-20250314222100838

随机选择策略

先来看一下 RandomLoadBalancer

    public Mono<Response<ServiceInstance>> choose(Request request) {// 做了一些处理ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);// 根据请求获取到服务列表return supplier.get(request).next().map((serviceInstances) -> {// 对服务列表进行处理return this.processInstanceResponse(supplier, serviceInstances);});}

进入 processInstanceResponse 看一下

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

getInstanceResponse 看一下

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else {// 生成随机数int index = ThreadLocalRandom.current().nextInt(instances.size());// 根据生成的随机数,在服务列表中选择ServiceInstance instance = (ServiceInstance)instances.get(index);// 进行下一步的处理return new DefaultResponse(instance);}}
轮询策略

进入RoundRobinLoadBalancer

image-20250314223558314

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

进入 processInstanceResponse

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

进入 getInstanceResponse

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else if (instances.size() == 1) {return new DefaultResponse((ServiceInstance)instances.get(0));} else {// 计数器int pos = this.position.incrementAndGet() & 2147483647;// 通过计数器 % instances.size() 来拿到坐标ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());return new DefaultResponse(instance);}}
服务部署

参考 博客系统笔记总结 2( Linux 相关) 中的部署 Web 项目到 Linux


本文到这里就结束啦~

在这里插入图片描述

相关文章:

SpringCloud 学习笔记1(Spring概述、工程搭建、注册中心、负载均衡、 SpringCloud LoadBalancer)

文章目录 SpringCloudSpringCloud 概述集群和分布式集群和分布式的区别和联系 微服务什么是微服务&#xff1f;分布式架构和微服务架构的区别微服务的优缺点&#xff1f;拆分微服务原则 什么是 SpringCloud &#xff1f;核心功能与组件 工程搭建父项目的 pom 文件 注册中心Rest…...

go 安装swagger

1、依赖安装&#xff1a; # 安装 swag 命令行工具 go install github.com/swaggo/swag/cmd/swaglatest# 安装 gin-swagger 和 swagger 文件的依赖 go get -u github.com/swaggo/gin-swagger go get -u github.com/swaggo/files 2、测试 cmd中输入&#xff1a; swag -v 如果…...

Java中关于Optional的 orElse 操作,以及 orElse 与 orElseGet 的区别

文章目录 1. 大概说明2. 详细分析2.1 .orElse 操作2.2 .orElse 的作用&#xff1a;避免空指针异常2.3 为什么要用&#xff1f;2.4 orElseGet如何使用2.5 orElse和orElseGet的区别 1. 大概说明 这篇文章的目的是为了说明&#xff1a; orElse 如何使用orElseGet 如何使用两者的…...

Sqlmap注入工具简单解释

安装 1. 安装 Python SQLMap 是基于 Python 开发的&#xff0c;所以要先安装 Python 环境。建议安装 Python 3.9 或更高版本&#xff0c;可从 Python 官方网站 下载对应操作系统的安装包&#xff0c;然后按照安装向导完成安装。 2. 获取 SQLMap 可以从 SQLMap 的官方 GitHu…...

petalinxu 在zynq的FPGA下的ST7735S的驱动配置

spi的接线&#xff1a; 【TFT模块排针8】 【开发板spi,gpio】【antminers9】 VCC ----------- 3.3V ----------- 3.3V GND ----------- GND ----------- GND BLK(背光&#xff09;-------GPIO----------- BANK34_L4N_RXD2(w13; j2.12; gpio[2]) RST(复位&#xff…...

数据篇| App爬虫入门(一)

App 的爬取相比 Web 端爬取更加容易,反爬虫能力没有那么强,而且数据大多是以 JSON 形式传输的,解析更加简单。在 Web 端,我们可以通过浏览器的开发者工具监听到各个网络请求和响应过程,在 App 端如果想要查看这些内容就需要借助抓包软件。常见抓包软件有: ‌工具名称‌‌…...

【6】拓扑排序学习笔记

前言 有向无环图和拓扑排序直接关联到中后期的图论建模思想&#xff0c;是很重要的基础知识。这个如果不彻底弄懂&#xff0c;以后图论会很困难。 有向无环图 正如其名&#xff0c;一个边有向&#xff0c;没有环的图&#xff0c;也叫DAG。 DAG图实际运用&#xff1a;描述含…...

OpenCV实现图像特征提取与匹配

‌一、特征检测与描述子提取‌ ‌选择特征检测器‌ 常用算法包括&#xff1a; ‌ORB‌&#xff1a;一种高效的替代SIFT和SURF的算法&#xff0c;主要用于移动机器人和增强现实等领域。适合实时应用&#xff0c;结合FAST关键点与BRIEF描述子‌。‌SIFT&#xff08;尺度不变特征变…...

MyBatis 的核心配置文件是干什么的? 它的结构是怎样的? 哪些是必须配置的,哪些是可选的?

MyBatis 的核心配置文件&#xff08;通常命名为 mybatis-config.xml&#xff09;是 MyBatis 应用程序的入口点&#xff0c;它定义了 MyBatis 的全局配置信息 。 核心配置文件的作用&#xff1a; 配置 MyBatis 的运行时行为: 通过 <settings> 标签设置全局参数&#xff…...

VLAN,DHCP实验访问物理机

目标 三层交换机完成DHCP自动分配IP地址不同vlan间完成通信DNS服务器能够解析www.baidu.com&#xff0c;使PC机能够访问连接真实物理机&#xff0c;PC机与物理机能够互相访问 步骤 一、创建VLAN&#xff0c;配置好PC机和交换机的IP地址 LSW1 [LSW1]vlan batch 10 20 Info: T…...

六十天前端强化训练之第十七天React Hooks 入门:useState 深度解析

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、知识讲解 1. Hooks 是什么&#xff1f; 2. useState 的作用 3. 基本语法解析 4. 工作原理 5. 参数详解 a) 初始值设置方式 b) 更新函数特性 6. 注意事项 7. 类组…...

解决 HTTP 请求中的编码问题:从乱码到正确传输

文章目录 解决 HTTP 请求中的编码问题&#xff1a;从乱码到正确传输1. **问题背景**2. **乱码问题的原因**2.1 **客户端编码问题**2.2 **请求头缺失**2.3 **服务器编码问题** 3. **解决方案**3.1 **明确指定请求体编码**3.2 **确保请求头正确**3.3 **动态获取响应编码** 4. **调…...

跨国企业网络案例分析:SD-WAN智能组网

跨国企业面临的网络问题日益增加&#xff0c;如全球供应链协同、跨国研发协作及实时生产数据传输等场景&#xff0c;对网络质量提出更高要求。本文将深度解析某跨国工业集团如何通过SD-WAN实现网络架构智能化转型。 该集团以上海全球总部为核心&#xff0c;构建了覆盖亚欧美三大…...

视频孪生与三维视频融合:重构工业现场的“数字视网膜“

在浙江某精密制造企业的总控中心&#xff0c;30米长的曲面屏上实时跳动着工厂的每个生产细节&#xff1a;机械臂的运动轨迹与数字模型完全同步&#xff0c;质检工位的操作误差被自动标记&#xff0c;AGV小车的行进路径在三维空间中以光带形式可视化呈现。这种虚实交融的场景并非…...

STM32Cubemx-H7-7-OLED屏幕

如何把江科大的OLED标准库文件换成hal库的文件 前言 本文讲解如在hHAL库中使用OLED&#xff0c;其实江科大做的文件好已经很好了 只讲操作&#xff0c;不讲废话&#xff0c;默认大家都有32基本操作 创建工程 首先创建工程 把那两个引脚设置成开漏 获取标准库文件 打开江科大O…...

FPGA为何要尽量减少组合逻辑的使用

在FPGA设计中&#xff0c;组合逻辑的使用确实需要谨慎&#xff0c;尤其是要尽量减少它的复杂性。这并不是因为组合逻辑本身不好&#xff0c;而是因为它在实际应用中容易引发一系列问题&#xff0c;而这些问题往往与FPGA的设计哲学和硬件特性相冲突。让我从几个关键点来和你聊聊…...

缓存使用的具体场景有哪些?缓存的一致性问题如何解决?缓存使用常见问题有哪些?

缓存使用场景、一致性及常见问题解析 一、缓存的核心使用场景 1. 高频读、低频写场景 典型场景&#xff1a;商品详情页、新闻资讯、用户基本信息。特点&#xff1a;数据更新频率低&#xff0c;但访问量极高。策略&#xff1a; Cache-Aside&#xff08;旁路缓存&#xff09;&a…...

基于 RWA 模型与 AI - Agent 协同的企业级 aPAAS 架构设计

一、引言 在企业数字化转型不断深化的当下&#xff0c;现实世界资产&#xff08;RWA&#xff09;模型与人工智能智能体&#xff08;AI - Agent&#xff09;的协同融合&#xff0c;为企业级应用平台即服务&#xff08;aPAAS&#xff09;架构的创新发展带来了新契机。这种架构旨在…...

基于“动手学强化学习”的知识点(一):第 14 章 SAC 算法(gym版本 >= 0.26)

第 14 章 SAC 算法&#xff08;gym版本 &#xff1e; 0.26&#xff09; 摘要SAC 算法&#xff08;连续&#xff09;SAC 算法&#xff08;离散&#xff09; 摘要 本系列知识点讲解基于动手学强化学习中的内容进行详细的疑难点分析&#xff01;具体内容请阅读动手学强化学习&…...

【QT:信号和槽】

QT信号涉及的三要素&#xff1a;信号源、信号类型、信号的处理方式。 QT的信号槽机制&#xff1a; 给按钮的点击操作关联一个处理函数&#xff0c;用户点击按钮时触发&#xff0c;对应的处理函数就会执行 QT中使用connect函数将信号和槽关联起来&#xff0c;信号触发&#xf…...

Oracle中的INHERIT PRIVILEGES权限

Oracle中的INHERIT PRIVILEGES权限 存储过程和用户函数的AUTHID属性调用者权限vs定义者权限一个简单的示例INHERIT PRIVILEGES权限的含义INHERIT PRIVILEGES权限的安全隐患注意到Oracle 19c数据库中有如下权限信息: SQL> select grantor,grantee,table_name,privilege fro…...

Compose笔记(九)--Checkbox

这一节主要了解一下Compose中的Checkbox&#xff0c;它是Jetpack Compose UI框架中的一个组件&#xff0c;用于创建复选框功能。它允许用户从一个集合中选择一个或多个项目&#xff0c;可以将一个选项打开或关闭。与传统的Android View系统中的Checkbox相比&#xff0c;Compose…...

CSS中粘性定位

1.如何设置为粘性定位? 给元素设置posttion:sticky 即可实现粘性定位. 可以使用left, right ,top, bottom 四个属性调整位置,不过最常用的是top 值. 2.粘性定位的参考点在哪里? 离他最近的一个拥有"滚动机制"的祖先元素,即便这个祖先不是最近的真实可滚动祖先. 3.粘…...

日本IT|AWS工作内容及未来性、以及转职的所需资质和技能

AWSとは AWSはAmazon Web Services&#xff08;アマゾンウェブサービス&#xff09;の略称です。 名称から分かるとおり、ネットを通じた通販などを事業として行っているAmazon.com社がクラウドサービスとして運営しています。 本来であれば自分たちでインフラ環境を構築する…...

《Spring日志整合与注入技术:从入门到精通》

1.Spring与日志框架的整合 1.Spring与日志框架进行整合&#xff0c;日志框架就可以在控制台中&#xff0c;输出Spring框架运行过程中的一些重要的信息。 好处&#xff1a;方便了解Spring框架的运行过程&#xff0c;利于程序的调试。 Spring如何整合日志框架 Spring5.x整合log4j…...

如何判断一个项目用的是哪个管理器

如何判断一个项目用的是哪个管理器 npm: 如果项目中存在 package-lock.json 文件&#xff0c;这通常意味着项目使用 npm 作为包管理器。package-lock.json 文件会锁定项目的依赖版本&#xff0c;确保在不同环境中安装相同的依赖。 pnpm: 如果项目中存在 pnpm-lock.yaml 文件&a…...

软件工程概述

软件开发生命周期 软件定义时期&#xff1a;包括可行性研究和详细需求分析&#xff0c;任务是确定软件开发的总目标。 问题定义可行性研究&#xff08;经济、技术、操作、社会可行性&#xff0c;确定问题和解决办法&#xff09;需求分析&#xff08;确定功能需求&#xff0c;性…...

文件系统 linux ─── 第19课

前面博客讲解的是内存级文件管理,接下来介绍磁盘级文件管理 文件系统分为两部分 内存级文件系统 : OS加载进程 ,进程打开文件, OS为文件创建struct file 和文件描述符表 ,将进程与打开的文件相连, struct file 内还函数有指针表, 屏蔽了底层操作的差异,struct file中还有内核级…...

一篇博客搞定时间复杂度

时间复杂度 1、什么是时间复杂度&#xff1f;2、推导大O的规则3、时间复杂度的计算3.1 基础题 13.2 基础题 23.3基础题 33.4进阶题 13.5进阶题 23.6 偏难题 13.7偏难题 2&#xff08;递归&#xff09; 前言&#xff1a; 算法在编写成可执行程序后&#xff0c;运行时要耗费时间和…...

微信小程序实现根据不同的用户角色显示不同的tabbar并且可以完整的切换tabbar

直接上图上代码吧 // login/login.js const app getApp() Page({/*** 页面的初始数据*/data: {},/*** 生命周期函数--监听页面加载*/onLoad(options) {},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {},/*** 生命周期函数--监听页面显示*/onShow() {},/*** 生命周期函…...

S_on@atwk的意思

S_onatwk 可能是某种自动化或控制系统中的符号或标记&#xff0c;尤其在PLC&#xff08;可编程逻辑控制器&#xff09;编程中&#xff0c;类似的表达方式通常用于表示特定的信号、状态或操作。 我们可以分析这个表达式的各个部分&#xff1a; S_on&#xff1a;通常&#xff0…...

Liunx启动kafka并解决kafka时不时挂掉的问题

kafka启动步骤 先启动zookeeper&#xff0c;启动命令如下 nohup ./zookeeper-server-start.sh /home/kafka/kafka/config/zookeeper.properties > /home/kafka/kafka/zookeeper.log 2>&1 &再启动kafka&#xff0c;启动命令如下 nohup ./kafka-server-start.sh…...

16 | 实现简洁架构的 Store 层

提示&#xff1a; 所有体系课见专栏&#xff1a;Go 项目开发极速入门实战课&#xff1b;欢迎加入 云原生 AI 实战 星球&#xff0c;12 高质量体系课、20 高质量实战项目助你在 AI 时代建立技术竞争力&#xff08;聚焦于 Go、云原生、AI Infra&#xff09;&#xff1b;本节课最终…...

华为hcia——Datacom实验指南——以太网帧和IPV4数据包格式(一)

实验开始 第一步配置环境 第二步配置客户端 如图所示&#xff0c;我们把客户端的ip配置成192.168.1.10&#xff0c;网关设为192.168.1.1 第三步配置交换机1 system-view sysname LSW1 vlan batch 10 interface ethernet0/0/1 port link-type access port default vlan 10 qu…...

ubuntu软件——视频、截图、图片、菜单自定义等

视频软件&#xff0c;大部分的编码都能适应 sudo apt install vlc图片软件 sudo apt install gwenview截图软件 sudo apt install flameshot设置快捷键 flameshot flameshot gui -p /home/cyun/Pictures/flameshot也就是把它保存到一个自定义的路径 菜单更换 sudo apt r…...

CSS中z-index使用详情

定位层级 1.定位元素的显示层级比普通元素高,无论什么定位,显示层级都是一样的; 2.如果位置发生重叠,默认情况是:后面的元素,会显示在前面元素之上; 3.可以通过CSS属性z-index调整元素的显示层级; 4.z-index的属性值是数字,没有单位,值越大显示层级越高; 5.只有定位的元素…...

qt 自带虚拟键盘的编译使用记录

一、windows 下编译 使用vs 命令窗口&#xff0c;分别执行&#xff1a; qmake CONFIG"lang-en_GB lang-zh_CN" nmake nmake install 如果事先没有 指定需要使用的输入法语言就进行过编译&#xff0c;则需要先 执行 nmake distclean 清理后执行 qmake 才能生效。 …...

杨辉三角形(信息学奥赛一本通-2043)

【题目描述】 例5.11 打印杨辉三角形的前n(2≤n≤20)行。杨辉三角形如下图&#xff1a; 当n5时 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 输出&#xff1a; 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 【输入】 输入行数n。 【输出】 输出如题述三角形。n行&#…...

CentOS 7 系统上安装 SQLite

1. 检查系统更新 在安装新软件之前&#xff0c;建议先更新系统的软件包列表&#xff0c;以确保使用的是最新的软件源和补丁。打开终端&#xff0c;执行以下命令&#xff1a; sudo yum update -y -y 选项表示在更新过程中自动回答 “yes”&#xff0c;避免手动确认。 2. 安装 …...

程序化广告行业(13/89):DSP的深入解析与运营要点

程序化广告行业&#xff08;13/89&#xff09;&#xff1a;DSP的深入解析与运营要点 大家好&#xff01;一直以来&#xff0c;我都对程序化广告行业保持着浓厚的学习兴趣&#xff0c;在探索的过程中积累了不少心得。今天就想把这些知识分享出来&#xff0c;和大家一起学习进步…...

使用 Doris 和 LakeSoul

作为一种全新的开放式的数据管理架构&#xff0c;湖仓一体&#xff08;Data Lakehouse&#xff09;融合了数据仓库的高性能、实时性以及数据湖的低成本、灵活性等优势&#xff0c;帮助用户更加便捷地满足各种数据处理分析的需求&#xff0c;在企业的大数据体系中已经得到越来越…...

datax源码分析

文章目录 前言一、加载配置文件二、根据加载的配置文件进行调度三、根据配置文件执行读取写入任务总结 前言 在上一篇文章当中我们已经了解了datax的启动原理&#xff0c;以及datax的最基础的配置&#xff0c;datax底层java启动类的入口及关键参数。 接下来我将进行启动类执行…...

【HDLbits--分支预测器简单实现】

HDLbits--分支预测器简单实现 1 timer2.branche predicitors3.Branch history shift4.Branch direction predictor 以下是分支预测器的简单其实现&#xff1b; 1 timer 实现一个计时器&#xff0c;当load1’b1时&#xff0c;加载data进去&#xff0c;当load1’b0时进行倒计时&…...

优化Go错误码管理:构建清晰、优雅的HTTP和gRPC错误码规范

在系统开发过程中&#xff0c;如何优雅地管理错误信息一直是个棘手问题。传统的错误处理方式往往存在不统一、难以维护等缺点。而 errcode 模块通过对错误码进行规范化管理&#xff0c;为系统级和业务级错误提供了统一的编码标准。本文将带您深入了解 errcode 的设计原理、错误…...

批量压缩与优化 Excel 文档,减少 Excel 文档大小

当我们在 Excel 文档中插入图片资源的时候&#xff0c;如果我们插入的是原图&#xff0c;可能会导致 Excel 变得非常的大。这非常不利于我们传输或者共享。那么当我们的 Excel 文件非常大的时候&#xff0c;我们就需要对文档做一些压缩或者优化的处理。那有没有什么方法可以实现…...

MongoDB分页实现方式对比:PageRequest vs Skip/Limit

MongoDB分页实现方式对比&#xff1a;PageRequest vs Skip/Limit 一、基本概念1.1 PageRequest分页1.2 Skip/Limit分页 二、主要区别2.1 使用方式2.2 参数计算2.3 适用场景PageRequest适用场景&#xff1a;Skip/Limit适用场景&#xff1a; 三、性能考虑3.1 PageRequest的性能特…...

SAP Commerce(Hybris)营销模块(一):商城产品折扣配置

基于Hybris的Backoffice后台管理系统&#xff0c;创建一个基于模板的营销规则&#xff0c;并配置上对应的优惠活动。 架构设计 先从一张架构图说起 Hybris的促销模块&#xff0c;是基于Promotion引擎来实现的&#xff0c;可以通过Backoffice来进行配置。 通过上面的架构图又可…...

如何在 React 中实现错误边界?

在 React 中实现错误边界 错误边界是 React 提供的一种机制&#xff0c;用于捕获子组件树中的 JavaScript 错误&#xff0c;并展示回退 UI。它可以帮助开发者更好地处理错误&#xff0c;提升用户体验。本文将详细介绍如何在 React 中实现错误边界&#xff0c;包括其工作原理、…...

从头开始开发基于虹软SDK的人脸识别考勤系统(python+RTSP开源)(五)完整源码已上传!

本篇是对照之前代码剩余的部分代码做补充&#xff0c;分享给大家&#xff0c;便于对照运行测试。 完整版的全功能单文件版本已上传&#xff01;https://download.csdn.net/download/xiaomage_cn/90484179 人脸识别抽象层&#xff0c;这个大家应该都知道&#xff0c;就是为了方…...

PySide(PyQT)的mouseMoveEvent()和hoverMoveEvent()的区别

在 PySide中&#xff0c;mouseMoveEvent 和 hoverMoveEvent 都是用于处理鼠标移动相关操作的事件&#xff0c;但它们之间存在明显的区别&#xff1a; 事件触发条件 • mouseMoveEvent&#xff1a; 当鼠标在对应的图形项&#xff08;如 QGraphicsPixmapItem&#xff09…...