【SpringCloud】服务注册与发现 - Eureka

news2024/9/19 20:50:11

目录

  • 服务注册/服务发现-Eureka
    • 背景
      • 问题描述
      • 解决思路
      • 什么是注册中心
      • CAP 理论
      • 常见的注册中心
    • Eureka 介绍
    • 搭建Eureka Server
      • 创建Eureka-server 子模块
      • 引入eureka-server依赖
      • 项目构建插件
      • 完善启动类
      • 编写配置文件
      • 启动服务
    • 服务注册
      • 引入eureka-client依赖
      • 完善配置文件
      • 启动服务
    • 服务发现
      • 引入eureka-client依赖
      • 完善配置文件
      • 远程调用
      • 启动服务
    • Eureka 和 Zookeeper 区别
    • 多机部署与负载均衡 - LoadBalance
      • 负载均衡介绍
      • 问题描述
      • 什么是负载均衡
      • 负载均衡的一些实现
        • 服务端负载均衡
        • 客户端负载均衡
    • Spring Cloud LoadBalancer
      • 快速上⼿
        • 使用Spring Cloud LoadBalancer实现负载均衡
        • 启动多个product-service实例
        • 测试负载均衡
      • 负载均衡策略
        • 自定义负载均衡策略
      • LoadBalancer 原理
    • 服务部署(Linux)
      • 准备数据
      • 服务构建打包
      • 启动服务
      • 开放端口号
      • 测试


服务注册/服务发现-Eureka

背景

问题描述

之前的例子中可以看到,远程调用时,我们的 URL 是写死的:

String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();

当更换机器或者新增机器时,这个 URL 就需要跟着变更,就需要去通知所有的相关服务去修改。随之而来的就是各个项目的配置文件反复更新,各个项目的频繁部署。这种没有具体意义,但又不得不做的工作,会让人非常痛苦。

解决思路

试想生活中的场景:

我们生活中避免不了和各个机构(医院、学校、政府部门等)打交道,就需要保存各个机构的电话号码。如果机构换了电话号码,就需要通知各个使用方,但这些机构的使用方群体是巨大的,没办法做到一通知,怎么处理呢?

机构电话如果发生变化,通知 114。用户需要联系机构时,先打 114 查询电话,然后再联系各个机构。

114 查号台的作用主要有两个:

  • 号码注册:服务方把电话上报给 114
  • 号码查询:使用方通过 114 可以查到对应的号码

同样的,微服务开发时,也可以采用类似的方案。

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

调用方调用时,先去注册中心获取服务方的 IP,再去服务方进行调用。

什么是注册中心

在最初的架构体系中,集群的概念还不那么流行,且机器数量也比较少,此时直接使用 DNS + Nginx 就可以满足几乎所有服务的发现。相关的注册信息直接配置在 Nginx。但随着微服务的流行与流量的激增,机器规模逐渐变大,并且机器会有频繁的上下线行为,这种时候需要运维手动地去维护这个配置信息是一个很麻烦的操作。所以开发者们开始希望有这么一个东西,它能维护一个服务列表,哪个机器上线了,哪个机器宕机了,这些信息都会自动更新到服务列表上,客户端拿到这个列表,直接进行服务调用即可。这个就是注册中心。

注册中心主要有三种角色:

  • 服务提供者 (Server):一次业务中,被其它微服务调用的服务。也就是提供接口给其它微服务。
  • 服务消费者 (Client):一次业务中,调用其它微服务的服务。也就是调用其它微服务提供的接口。
  • 服务注册中心 (Registry):用于保存 Server 的注册信息,当 Server 节点发生变更时,Registry 会同步变更。服务与注册中心使用一定机制通信,如果注册中心与某服务长时间无法通信,就会注销该实例。

服务提供者和服务消费者是相对的

他们之间的关系以及工作内容,可以通过两个概念来描述:

服务注册:服务提供者在启动时,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态。

服务发现:服务消费者从注册中心查询服务提供者的地址,并通过该地址调用服务提供者的接口。服务发现的一个重要作用就是提供给服务消费者一个可用的服务列表。

CAP 理论

谈到注册中心,就避不开 CAP 理论。

CAP 理论是分布式系统设计中最基础,也是最为关键的理论。

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

    客户端向数据库集群发送了一个数据修改的请求,数据库集群需要向客户端进行响应,响应的时机分为两种:

    1. 主库接收到请求,并处理成功,此时数据暂时还未完全同步到从库
    2. 主库接收到请求,并且所有从库数据同步成功

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

    弱一致性:随着时间的推移,最终达到了一致性

  • 可用性 (Availability):保证每个请求都有响应(响应结果可能不对)。

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

一个部门全国各地都有岗位,这时候,总部下发了一个通知,由于通知需要开会周知全员,当有客户咨询时:

  1. 所有成员对客户的回应结果都是一致的(一致性)。
  2. 客户咨询时,一定有回应(可用性)。
  3. 当其中一个成员休假时,这个部门的其他成员也可以对客户提供咨询服务(分区容错性)。

CAP 理论告诉我们:一个分布式系统不可能同时满足数据一致性、服务可用性和分区容错性这三个基本需求,最多只能同时满足其中的两个。

分布式系统中,系统间的网络不能 100% 保证健康,服务又必须对外保证服务。因此 P 不可避免,必须保证。

不选 P,一旦发生分区错误,整个分布式系统就完全无法使用了,这是不符合实际需要的。

那就只能在 C 和 A 中选择一个。也就是 CP 或者 AP 架构。

正常情况

网络异常

  • CP 架构:为了保证分布式系统对外的数据一致性,于是选择不返回任何数据。
  • AP 架构:为了保证分布式系统的可用性,节点 2 返回 V0 版本的数据(即使这个数据不正确)。

更多参考:CAP 理论详细解释。

常见的注册中心

  1. Zookeeper
    • Zookeeper 的官方并没有说它是一个注册中心,但是国内 Java 体系,大部分的集群环境都是依赖 Zookeeper 来完成注册中心的功能。
  2. Eureka
    • Eureka 是 Netflix 开发的基于 REST 的服务发现框架,主要用于服务注册、管理,负载均衡和服务故障转移。
    • 官方声明在 Eureka 2.0 版本停止维护,不建议使用。但是 Eureka 是 Spring Cloud 服务注册/发现的默认实现,所以目前还是有很多公司在使用。
  3. Nacos
    • Nacos 是 Spring Cloud Alibaba 架构中重要的组件,除了服务注册、服务发现功能之外,Nacos 还支持配置管理、流量管理、DNS、动态 DNS 等多种特性。

CAP 理论对比

组件ZookeeperEurekaNacos
CAP 理论CPAPCP 或 AP,默认 AP

在分布式环境中,即使拿到一个错误的数据,也胜过无法提供实例信息而造成请求失败要好(例如淘宝 11.11,京东 618 都是谨遵 AP 原则)。

后面会介绍 Eureka 和 Nacos 的使用。

Eureka 介绍

Eureka是Netflix OSS套件中关于服务注册和发现的解决方案。Spring Cloud对Eureka进行了集成,并作为优先推荐方案进行宣传。虽然目前Eureka 2.0已经停止维护,新的微服务架构设计中也不再建议使用,但目前依然有大量公司的微服务系统使用Eureka作为注册中心。

官方文档链接:Eureka GitHub Wiki

Eureka 主要分为两个部分

  • Eureka Server:作为注册中心Server端,向微服务应用程序提供服务注册、发现、健康检查等能力。
  • Eureka Client:服务提供者,服务启动时,会向Eureka Server注册自己的信息(IP、端口、服务信息等),Eureka Server会存储这些信息。

关于Eureka的学习,主要包含以下三个部分:

  1. 搭建Eureka Server。
  2. 将order-service,product-service都注册到Eureka。
  3. order-service远程调用时,从Eureka中获取product-service的服务列表,然后进行交互。

搭建Eureka Server

Eureka-server 是⼀个独⽴的微服务.

我们把原来的 spring-cloud-demo 项目复制一份成 spring-cloud-eureka 并且修改一些文件的内容

记得把新项目的 .idea 文件删除,可能会导致一些错误

创建Eureka-server 子模块

引入eureka-server依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

项目构建插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

完善启动类

给该项⽬编写⼀个启动类, 并在启动类上添加 @EnableEurekaServer 注解, 开启eureka注册中⼼服务

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

编写配置文件

server:
  port: 10010

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: false # 表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,这里设置为false。
    register-with-eureka: false # 表示是否将自己注册到Eureka Server,默认为true。由于当前应用就是Eureka Server,故而设置为false。
    service-url:
      # 设置与Eureka Server的地址,查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动服务

启动服务, 访问注册中⼼: http://127.0.0.1:10010/

可以看到eureka-server已经启动成功了

服务注册

接下来我们把 product-service 注册到eureka-server中

引入eureka-client依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

完善配置文件

添加服务名称和eureka地址

  1. 为应用程序命名为 “product-service”。
  2. 启用 Spring Cloud Config 的服务发现功能,允许应用程序动态获取配置信息。
  3. 注册应用程序到 Eureka 注册中心,使其能够被其他服务发现和调用。
spring:
  application:
    name: product-service
  cloud:
    config:
      discovery:
        enabled: true
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka

当前完整的配置文件

server:
  port: 9090

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: product-service
  cloud:
    config:
      discovery:
        enabled: true

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

启动服务

启动 product-service

然后刷新注册中⼼: http://127.0.0.1:10010/

可以看到product-service已经注册到 eureka上了

服务发现

接下来我们修改order-service, 在远程调⽤时, 从eureka-server拉取product-service的服务信息, 实现服务发现

引入eureka-client依赖

服务注册和服务发现都封装在eureka-client依赖中, 所以服务发现时, 也是引⼊eureka-client依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

完善配置文件

服务发现也需要知道eureka地址,因此配置内容依然与服务注册⼀致,都是配置eureka信息

  1. 动态配置管理: 应用程序可以从 Spring Cloud Config 服务器动态获取配置信息,而无需在代码中硬编码。
  2. 服务发现和注册: 应用程序会注册到 Eureka 注册中心,使其能够被其他服务发现和调用。
  3. 负载均衡和故障转移: Eureka 注册中心提供了服务发现和负载均衡的功能,可以帮助实现高可用的微服务架构。
spring:
  application:
    name: order-service
  cloud:
    config:
      discovery:
        enabled: true
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka

当前完整的配置文件

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: order-service
  cloud:
    config:
      discovery:
        enabled: true

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true # 配置驼峰自动转换

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka

远程调用

远程调⽤时, 我们需要从eureka-server中获取product-service的列表(可能存在多个服务), 并选择其中⼀个进⾏调⽤

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 lombok.extern.slf4j.Slf4j;

import javax.annotation.Resource;
import java.util.List;

@Slf4j
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Resource
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    public OrderInfo selectOrderById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        // 根据应用名称从Eureka获取服务列表
        List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
        // 服务可能有多个, 获取第一个
        ServiceInstance instance = instances.get(0);
        log.info(instance.getInstanceId());
        // 拼接URL
        String url = instance.getUri() + "/product/" + orderInfo.getProductId();
        log.info("远程调用url:{}", url);
        // 调用远程服务获取ProductInfo
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

启动服务

启动 order-service

然后刷新注册中⼼: http://127.0.0.1:10010/

可以看到order-service已经注册到 eureka上了

访问接⼝: http://127.0.0.1:8080/order/1

可以看到, 远程调⽤也成功了.

Eureka 和 Zookeeper 区别

  1. 项目背景

    • Eureka:由 Netflix 开源,用于服务注册和发现。
    • Zookeeper:由 Apache 开源,广泛用于分布式系统的协调服务。
  2. 一致性与可用性

    • Eureka:遵循 AP 原则,保证系统高可用性,但数据一致性可能会有延迟。
    • Zookeeper:遵循 CP 原则,确保数据一致性,但可能会在节点故障时影响系统的可用性。
  3. 节点角色

    • Eureka:所有节点在服务注册和发现中功能平等。
    • Zookeeper:节点分为 Leader、Follower 和 Observer。Leader 负责写操作,Follower 负责读操作。当 Leader 故障时,需要重新选举新 Leader,选举期间可能会出现短暂的系统不可用。

多机部署与负载均衡 - LoadBalance

负载均衡介绍

问题描述

观察之前远程调用的代码:

List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
//服务可能有多个, 获取第一个
EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(0);
  1. 根据应用名称获取了服务实例列表。
  2. 从列表中选择了一个服务实例。

思考: 如果一个服务对应多个实例呢?流量是否可以合理地分配到多个实例呢?

现象观察

我们再启动2个 product-service 实例。

选中要启动的服务,右键选择 Copy Configuration...

步骤1

在弹出的框中,选择 Modify options -> Add VM options

步骤2

添加 VM options : -Dserver.port=9091

9091 为服务启动的端口号,根据自己的情况进行修改

步骤3

现在IDEA的Service窗口就会多出来一个启动配置,右键启动服务即可

步骤4

同样的操作,再启动1个实例,共启动3个服务。

步骤5

观察Eureka,可以看到 product-service 下有三个实例:

步骤6

访问结果

访问: http://127.0.0.1:8080/order/1

11:46:05.684+08:00 INFO 23128 --- [nio-8080-exec-1] com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:06.435+08:00 INFO 23128 --- [nio-8080-exec-2] com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:07.081+08:00 INFO 23128 --- [nio-8080-exec-3] com.bite.order.service.OrderService : LUCF:product-service:9090

通过日志可以观察到,请求多次访问,都是同一台机器。

这肯定不是我们想要的结果,我们启动多个实例,是希望可以分担其他机器的负荷,那么如何实现呢?

解决方案

我们可以对上述代码进行简单修改:

@Slf4j
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Resource
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    // 计数器
    private AtomicInteger count = new AtomicInteger(1);

    private List<ServiceInstance> instances;

    // 如果这块代码写在方法中,不能保证Eureka给我们返回的结果是不变的
    // 可能第一次是120,第二次是012,第三次是210
    @PostConstruct
    public void init() {
        // 根据应用名称从Eureka获取服务列表
        instances = discoveryClient.getInstances("product-service");
    }

    public OrderInfo selectOrderById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        // 计算轮流的实例index
        int index = count.getAndIncrement() % instances.size();
        // 获取实例
        ServiceInstance instance = instances.get(index);
        log.info(instance.getInstanceId());
        // 拼接URL
        String url = instance.getUri() + "/product/" + orderInfo.getProductId();
        log.info("远程调用url:{}", url);
        // 调用远程服务获取ProductInfo
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}
@Slf4j
@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @RequestMapping("/{productId}")
    public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
        log.info("接收到参数:productId " + productId);
        return productService.selectProductById(productId);
    }
}

观察日志

12:02:13.245+08:00 INFO 1800 --- [nio-8080-exec-1] com.bite.order.service.OrderService : LUCF:product-service:9091
12:02:15.723+08:00 INFO 1800 --- [nio-8080-exec-2] com.bite.order.service.OrderService : LUCF:product-service:9090
12:02:16.534+08:00 INFO 1800 --- [nio-8080-exec-3] com.bite.order.service.OrderService : LUCF:product-service:9092
12:02:16.864+08:00 INFO 1800 --- [nio-8080-exec-4] com.bite.order.service.OrderService : LUCF:product-service:9091
12:02:17.078+08:00 INFO 1800 --- [nio-8080-exec-5] com.bite.order.service.OrderService : LUCF:product-service:9090
12:02:17.260+08:00 INFO 1800 --- [nio-8080-exec-6] com.bite.order.service.OrderService : LUCF:product-service:9092
12:02:17.431+08:00 INFO 1800 --- [nio-8080-exec-7] com.bite.order.service.OrderService : LUCF:product-service:9091

通过日志可以看到,请求被均衡地分配到了不同的实例上,这就是负载均衡。

什么是负载均衡

负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件。当服务流量增大时,通常会采用增加机器的方式进行扩容,负载均衡就是用来在多个机器或者其他资源中,按照一定的规则合理分配负载。

一个团队最开始只有一个人,后来随着工作量的增加,公司又招聘了几个人。负载均衡就是:如何把工作量均衡地分配到这几个人身上,以提高整个团队的效率。

负载均衡的一些实现

上面的例子中,我们只是简单地对实例进行了轮询,但真实的业务场景会更加复杂。比如根据机器的配置进行负载分配,配置高的分配的流量高,配置低的分配流量低等。

类似企业员工:能力强的员工可以多承担一些工作。

服务多机部署时,开发人员都需要考虑负载均衡的实现,所以也出现了一些负载均衡器,来帮助我们实现负载均衡。

负载均衡分为服务端负载均衡客户端负载均衡

服务端负载均衡

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

比较有名的服务端负载均衡器是 Nginx。请求先到达 Nginx 负载均衡器,然后通过负载均衡算法,在多个服务器之间选择一个进行访问。

服务端负载均衡

客户端负载均衡

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

把负载均衡的功能以库的方式集成到客户端,而不再是由一台指定的负载均衡设备集中提供。

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

Ribbon 是 Spring Cloud 早期的默认实现,由于不再维护,所以最新版本的 Spring Cloud 负载均衡集成的是 Spring Cloud LoadBalancer(Spring Cloud 官方维护)。

客户端负载均衡

客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置。

Spring Cloud LoadBalancer

快速上⼿

SpringCloud 从 2020.0.1 版本开始,移除了Ribbon 组件,使⽤Spring Cloud LoadBalancer 组件来代替 Ribbon 实现客⼾端负载均衡.

使用Spring Cloud LoadBalancer实现负载均衡
  1. 给 RestTemplate 这个Bean添加 @LoadBalanced 注解就可以
@Configuration
public class BeanConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  1. 修改IP端⼝号为服务名称
@Slf4j
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Resource
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    // 计数器
    private AtomicInteger count = new AtomicInteger(1);

    private List<ServiceInstance> instances;

    // 如果这块代码写在方法中,不能保证Eureka给我们返回的结果是不变的
    // 可能第一次是120,第二次是012,第三次是210
    @PostConstruct
    public void init() {
        // 根据应用名称从Eureka获取服务列表
        instances = discoveryClient.getInstances("product-service");
    }

    public OrderInfo selectOrderById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        // 使用服务名称替换IP地址和端口号
        String url = "http://product-service/product/" + orderInfo.getProductId();
        log.info("远程调用url:{}", url);
        // 调用远程服务获取ProductInfo
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}
启动多个product-service实例

按照之前的⽅式, 启动多个product-service实例

测试负载均衡

连续多次发起请求: http://127.0.0.1:8080/order/1

观察product-service的⽇志, 会发现请求被分配到这3个实例上了

负载均衡策略

负载均衡策略是⼀种思想, ⽆论是哪种负载均衡器, 它们的负载均衡策略都是相似的. Spring Cloud LoadBalancer 仅⽀持两种负载均衡策略: 轮询策略 和 随机策略

  1. 轮询(Round Robin): 轮询策略是指服务器轮流处理⽤⼾的请求. 这是⼀种实现最简单, 也最常⽤的策略. ⽣活中也有类似的场景, ⽐如学校轮流值⽇, 或者轮流打扫卫⽣.

  2. 随机选择(Random): 随机选择策略是指随机选择⼀个后端服务器来处理新的请求.

自定义负载均衡策略

Spring Cloud LoadBalancer 默认负载均衡策略是 轮询策略, 实现是 RoundRobinLoadBalancer, 如果服务的消费者如果想采⽤随机的负载均衡策略, 也⾮常简单.

参考官⽹地址:Spring Cloud LoadBalancer :: Spring Cloud Commons

  1. 定义随机算法对象, 通过 @Bean 将其加载到 Spring 容器中

此处使⽤Spring Cloud LoadBalancer提供的 RandomLoadBalancer

public class CustomLoadBalancerConfiguration {

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

注意: 该类需要满⾜:

  1. 不⽤ @Configuration 注释
  2. 在组件扫描范围内
  1. 使⽤ @LoadBalancerClient 或者 @LoadBalancerClients 注解

在 RestTemplate 配置类上⽅, 使⽤ @LoadBalancerClient@LoadBalancerClients 注解, 可以对不同的服务提供⽅配置不同的客⼾端负载均衡算法策略.

由于项⽬中只有⼀个服务提供者, 所以使⽤@LoadBalancerClient

@LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class)
@Configuration
public class BeanConfig {
    @Bean

    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@LoadBalancerClient 注解说明

  1. name: 该负载均衡策略对哪个服务⽣效(服务提供⽅)
  2. configuration : 该负载均衡策略 ⽤哪个负载均衡策略实现.

通过多次测试 http://127.0.0.1:8080/order/1 发现,上面 CustomLoadBalancerConfiguration 的实现是是随机选择的

LoadBalancer 原理

LoadBalancer 的实现, 主要是 LoadBalancerInterceptor , 这个类会对 RestTemplate 的请求进⾏拦截, 然后从Eureka根据服务id获取服务列表,随后利⽤负载均衡算法得到真实的服务地址信息,替换服务id

我们来看看源码实现:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    // ...

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);

        // Execute the request through the load balancer
        return (ClientHttpResponse) this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
}

可以看到这⾥的intercept⽅法, 拦截了⽤⼾的HttpRequest请求,然后做了⼏件事:

  1. request.getURI() 从请求中获取uri, 也就是 http://product-service/product/1001
  2. originalUri.getHost() 从uri中获取路径的主机名, 也就是服务id, product-service
  3. loadBalancer.execute 根据服务id, 进⾏负载均衡, 并处理请求

点进去继续跟踪

public class BlockingLoadBalancerClient implements LoadBalancerClient {

    // ...

    @Override
    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);
        });

        // 根据 serviceId 和负载均衡策略选择服务实例
        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);
        }
    }

    /**
     * 根据 serviceId 和负载均衡策略选择服务实例
     */
    public <T> ServiceInstance choose(String serviceId, Request<T> request) {
        // 获取负载均衡器
        ReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);
        if (loadBalancer == null) {
            return null;
        } else {
            // 根据负载均衡算法,在列表中选择一个服务实例
            Response<ServiceInstance> loadBalancerResponse = (Response) Mono.from(loadBalancer.choose(request)).block();
            return loadBalancerResponse == null ? null : (ServiceInstance) loadBalancerResponse.getServer();
        }
    }
}

服务部署(Linux)

接下来我们把服务部署在Linux系统上

准备数据

安装 MySQL

参考上面 MySQL安装 的笔记

数据初始化

同理参考上面 环境和工程搭建的数据准备 的笔记

修改配置文件

修改配置⽂件中, 数据库的密码

分别在 order-service 和 product-service 创建 application.yml、application-dev.yml、application-prod.yml,下面以 order-service 的举例,product-service 同理,eureka-server 不用修改

spring:
  profiles:
    active: @profile.name@
server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: order-service
  cloud:
    config:
      discovery:
        enabled: true

mybatis:
  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true # 配置驼峰自动转换

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka
server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
    username: root
    password: 云服务器的数据库密码
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: order-service
  cloud:
    config:
      discovery:
        enabled: true

mybatis:
  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true # 配置驼峰自动转换

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka

修改pom.xml

添加下面这部分

<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <profile.name>dev</profile.name>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <profile.name>prod</profile.name>
        </properties>
    </profile>
</profiles>

服务构建打包

采⽤Maven打包, 需要对3个服务分别打包:

eureka-server, order-service, product-service

  1. 打包⽅式和SpringBoot项⽬⼀致, 依次对三个项⽬打包即可.

启动服务

  1. 上传Jar包到云服务器

第⼀次上传需要安装lrzsz

apt install lrzsz

直接拖动⽂件到xshell窗⼝, 上传成功.

最好创建一个文件夹比如 spring_cloud 什么的,放这些jar包,然后在里面创建个 logs 文件夹放日志文件什么的

  1. 启动服务
#后台启动eureka-server, 并设置输出⽇志到logs/eureka.log
nohup java -jar eureka-server.jar >logs/eureka.log &

#后台启动order-service, 并设置输出⽇志到logs/order.log
nohup java -jar order-service.jar >logs/order.log &

#后台启动product-service, 并设置输出⽇志到logs/order.log
nohup java -jar product-service.jar >logs/product-9090.log &

再多启动两台product-service实例

#启动实例, 指定端⼝号为9091
nohup java -jar product-service.jar --server.port=9091 >logs/product-9091.log &

#启动实例, 指定端⼝号为9092
nohup java -jar product-service.jar --server.port=9092 >logs/product-9092.log &

开放端口号

根据⾃⼰项⽬设置的情况, 在云服务器上开放对应的端⼝号

不同的服务器⼚商, 开放端⼝号的⼊⼝不同, 需要⾃⾏找⼀找或者咨询对应的客服⼈员.

以腾讯云服务器举例:

  1. 进⼊防⽕墙管理⻚⾯

  1. 添加规则

端⼝号写需要开放的端⼝号, 多个端⼝号以逗号分割.

测试

  1. 访问Eureka Server: http://110.41.51.65:10010

  1. 访问订单服务接⼝: http://110.41.51.65:8080/order/1

远程调⽤成功.

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

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

相关文章

如图,从左到右在每个小格子中填入一个整数,使得其中任意三个相邻格子中所填整数之和都相等,若前m个格子中所填整数之和是2024,则m的值为?

1、如果图&#xff1a; 任务三个相邻的格子中所填整数之和都相等&#xff0c;则可以列个几个式子&#xff1a; 9ab abcbc(-5)c(-5)1 则可以求出&#xff1a; a-5&#xff0c;b1&#xff0c;c9 2、前m个格子中所填数之和是2024&#xff0c;则表示9abc(-5)1...第m个格子2024 …

LaTex2024 下载安装运行HelloWorld—全流程笔记

LaTex安装教程&#x1f680; 这是读博之后写的第一篇文章&#xff0c;来到新课题组之后&#xff0c;新课题组主要是用Latex&#xff0c;在之前的课题组&#xff0c;还是比较常用world&#xff0c;所以就研究了一下Latex的下载和安装&#xff0c;虽然网上已经有了不少教程&#…

编程基础:函数栈帧的创建和销毁

函数栈帧的创建和销毁 一、什么是函数栈帧二、常见的寄存器三、函数指令三、函数栈帧的创建和销毁&#xff08;一&#xff09;普通值传递1、开始执行函数2、main函数的函数栈帧的开辟&#xff08;1&#xff09;main 栈帧空间的开辟和默认初始化&#xff08;2&#xff09;main 局…

S-Procedure的基本形式及使用

理论 Lemma 1. ( S- Procedure[ 34] ) : Define the quadratic func- \textbf{Lemma 1. ( S- Procedure[ 34] ) : Define the quadratic func- } Lemma 1. ( S- Procedure[ 34] ) : Define the quadratic func- tions w.r.t. x ∈ C M 1 \mathbf{x}\in\mathbb{C}^M\times1 x…

Leetcode—815. 公交路线【困难】(unordered_map+queue)

2024每日刷题&#xff08;163&#xff09; Leetcode—815. 公交路线 bfs实现代码 class Solution { public:int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {if(source target) {return 0;}unordered_map<int, vector…

如何用安卓玩Java版Minecraft,安卓手机安装我的世界Java版游戏的教程

安卓手机使用FCL启动器安装我的世界Java版游戏的教程。如何用安卓玩Java版Minecraft 视频教程&#xff1a;https://www.bilibili.com/video/BV1CctYebEzR/ 前言 目前&#xff0c;安卓设备上可以用来运行Java版Minecraft的启动器主要有以下几款&#xff1a; PojavLauncher&a…

Matlab-Matpower制作IEEE14-电力虚假数据注入攻击FDIA数据集

文章目录 1. 加载Matpower-IEEE14电力数据2. 导入原始数据集两个数据集结合的意义潮流分析和状态估计的意义 3. 初始化变量4. 分离有功和无功功率4. 潮流计算5. 生成测量向量6. 选择是否篡改数据7. 状态估计和雅可比矩阵8. 保存未篡改数据9. 篡改数据生成FDIA仿真数据集完整代码…

硬件工程师笔试面试——显示器件

目录 14、显示器件 14.1 基础 显示器件实物图 14.1.1 概念 14.1.2 工作原理 14.1.3 性能参数 14.1.4 应用领域 14.2 相关问题 14.2.1 液晶显示器(LCD)和有机发光二极管 (OLED)显示器在性能上有哪些主要区别? 14.2.2 在设计显示器时,如何平衡分辨率和刷新率以满足不…

C#:强大编程语言的多面魅力

C#&#xff1a;强大编程语言的多面魅力 一、C# 语言的特点与优势 &#xff08;一&#xff09;简洁的语法与精心设计 C# 在继承 C 和 C 的强大功能的同时&#xff0c;去掉了一些复杂特性&#xff0c;如宏和多重继承&#xff0c;使得语言更加简洁易懂。C# 是一种面向对象的语言…

根文件夹下文件重复检测

功能介绍&#xff1a;在传入Windows路径后&#xff08;例如“D:\小米云服务下载”&#xff09;&#xff0c;遍历文件夹下所视频有文件&#xff08;包括子文件夹下的视频文件&#xff0c;其他类型不做判断&#xff09;&#xff0c;判断视频文件是否重复&#xff08;由于视频文件…

运筹说 第125期 | 存储论经典例题讲解1

通过前几期的学习&#xff0c;我们已经学会了存储论的基本概念、确定型存储模型、单周期的随机型存储模型、其他的随机型存储模型以及存储论应用研究中的一些问题。在实际工作中&#xff0c;我们能发现存储论在能源行业中有着许多应用&#xff0c;本期小编选择了其中一些确定型…

错误: 找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain

安装 zookeeper-3.8.4 版本的时候&#xff0c;启动zk打印如下错误日志 错误: 找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain后面查了下发现 zookeeper 3.5.5 版本以后&#xff0c;已编译的 jar 包&#xff0c;尾部有 bin下载的时候应该下载 编译后…

裸土检测算法实际应用、裸土检测算法样本、裸土检测算法精准检测

裸土检测算法是一种前沿的图像识别技术&#xff0c;它通过利用先进的图像处理技术和机器学习算法&#xff0c;从卫星图像、无人机拍摄的图像或其他地面监测数据中提取出裸土区域&#xff0c;并对其进行精确的分类和分析。 与传统的地面勘察方法相比&#xff0c;裸土检测算法具有…

1. YOLOv10: Real-Time End-to-End Object Detection

一、全文概述 文章主要介绍了YOLOv10在实时端到端目标检测方面的改进和创新。简要概述&#xff1a; 1.1 背景与挑战&#xff1a; 实时目标检测在计算机视觉领域具有重要意义&#xff0c;广泛应用于自动驾驶、机器人导航等场景。YOLO系列因其在性能和效率之间的有效平衡而受到…

fiddler抓包04_基础设置(字体/工具栏/抓包开关/清空)

课程大纲 1. 设置字体 菜单栏 “工具”&#xff08;tool&#xff09; - “选项”&#xff08;options&#xff09; - “appearance”&#xff0c;设置字号和字体后&#xff0c;点击确认&#xff0c;立刻生效&#xff08;无需重启&#xff09;。 2. 展开/收起工具栏 菜单栏 “…

Kotlin-Flow学习笔记

Channel 和 Flow 都是数据流&#xff0c;Channel 是“热”的&#xff0c;Flow 则是“冷”的。这里的冷&#xff0c;代表着 Flow 不仅是“冷淡”的&#xff0c;而且还是“懒惰”的。 Flow 从 API 的角度分类&#xff0c;主要分为&#xff1a;构造器、中间操作符、终止操作符。今…

Qt开发技巧(四)“tr“使用,时间类使用,Qt容器取值,类对象的删除,QPainter画家类,QString的转换,用好 QVariant类型

继续讲一些Qt技巧操作 1.非必要不用"tr" 如果程序运行场景确定是某一固定语言&#xff0c;就不需要用tr,"tr"之主要针对多语种翻译的&#xff0c;因为tr的本意是包含英文&#xff0c;然后翻译到其他语言比如中文&#xff0c;不要滥用tr&#xff0c;如果没有…

【算法】差分思想:强大的算法技巧

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

Linux(CentOS8)服务器安装RabbitMQ

我安装了很久都没有成功, 各种问题, 每次的异常都不一样, 现将成功安装过程做个总结 安装前工作 确保已经安装了一些基础工具和组件库 下载安装包 https://www.erlang.org/patches/otp-24.3.4.5 https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.9.15/ra…

Python热频随机森林分类器算法模型模拟

&#x1f3af;要点 研究发射测量斜率和时滞热频率表征&#xff0c;使用外推法计算三维磁场并定性比较使用基于焓的热演化环模型模拟每条线的热力学响应&#xff0c;测试低频、中频和高频热场景使用光学薄、高温、低密度等离子体的单位体积辐射功率或发射率公式等建模计算使用直…