主要内容

  • Eureka服务注册与发现
  • 单机Eureka构建
  • 集群Eureka构建

    1 Eureka基础知识

  • 前面我们没有服务注册中心,也可以服务间调用,为什么还要服务注册?

  • 当服务很多时,单靠代码手动管理是很麻烦的,需要一个公共组件,统一管理多服务之间的相互调用。
  • Eureka用于服务注册,目前官网已经停止更新。

1.1 什么是服务治理  

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理 在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。 n个生产者与那个消费者之间相互调用

1.2 什么是服务注册与发现

如果你的服务注册中心只有一台机器,那么只要它一宕机,就会产生单点故障。所以一般服务注册中心能配多个就配多个,主要就是为了避免单点故障。

Eureka采用了CS(Client-Server)的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

下左图是Eureka系统架构,右图是Dubbo的架构,请对比
image.png

1.3 Eureka包含两个组件:Eureka Server和Eureka Client

Eureka Server提供服务注册服务

各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

EurekaClient通过注册中心进行访问

是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

2 单机Eureka构建步骤

image.png

2.1 建Module

image.png

2.2 改pom

1.x和2.x的Eureka依赖对比

  1. 1.x: server跟client合在一起
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-eureka</artifactId>
  5. </dependency>
  6. 2.x: server跟client分开
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.cloud</groupId>
  13. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  14. </dependency>

我们当前模块时eureka的server端,那么引入依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>jdk8cloud2021</artifactId>
  7. <groupId>com.atguigu.springcloud</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-eureka-server7001</artifactId>
  12. <properties>
  13. <maven.compiler.source>8</maven.compiler.source>
  14. <maven.compiler.target>8</maven.compiler.target>
  15. </properties>
  16. <dependencies>
  17. <!--eureka-server-->
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  21. </dependency>
  22. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  23. <dependency>
  24. <groupId>com.atguigu.springcloud</groupId>
  25. <artifactId>cloud-api-commons</artifactId>
  26. <version>${project.version}</version>
  27. </dependency>
  28. <!--boot web actuator-->
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-web</artifactId>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-starter-actuator</artifactId>
  36. </dependency>
  37. <!--一般通用配置-->
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-devtools</artifactId>
  41. <scope>runtime</scope>
  42. <optional>true</optional>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.projectlombok</groupId>
  46. <artifactId>lombok</artifactId>
  47. </dependency>
  48. <dependency>
  49. <groupId>org.springframework.boot</groupId>
  50. <artifactId>spring-boot-starter-test</artifactId>
  51. <scope>test</scope>
  52. </dependency>
  53. <dependency>
  54. <groupId>junit</groupId>
  55. <artifactId>junit</artifactId>
  56. </dependency>
  57. </dependencies>
  58. </project>

2.3 写yml

  1. server:
  2. port: 7001
  3. eureka:
  4. instance:
  5. hostname: localhost #eureka服务端的实例名称
  6. client:
  7. #false表示不向注册中心注册自己。
  8. register-with-eureka: false
  9. #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  10. fetch-registry: false
  11. service-url:
  12. #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
  13. defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

2.4 主启动类

注意事项:增加@EnableEurekaServer注解,指定该模块作为Eureka注册中心的服务器。

  1. @SpringBootApplication
  2. @EnableEurekaServer //表示当前是Eureka的服务注册中心
  3. public class EurekaMain7001 {
  4. public static void main(String[] args) {
  5. SpringApplication.run(EurekaMain7001.class, args);
  6. }
  7. }

2.5 service类

Eureka Server 不需要写Service类

2.6 测试

游览器访问 http://localhost:7001
image.png

3 微服务注册

3.1 生产者微服务8001注册进EurekaServer

3.1.1 改pom

添加一个Eureka-Client依赖:

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

3.1.2 改yml

yml文件添加如下配置

  1. eureka:
  2. client:
  3. # 表示将自己注册进EurekaServer默认为true
  4. register-with-eureka: true
  5. # 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  6. fetch-registry: true
  7. service-url:
  8. defaultZone: http://localhost:7001/eureka

3.1.3 改主启动类

添加注解:@EnableEurekaClient。 指明这是一个Eureka客户端,要注册进EurekaServer中。

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. @EnableDiscoveryClient
  4. //@MapperScan("com.atguigu.springcloud.dao")
  5. public class PaymentMain8001 {
  6. public static void main(String[] args) {
  7. SpringApplication.run(PaymentMain8001.class, args);
  8. }
  9. }

3.1.4 测试

启动8001模块
image.png
红色字体表示eureka的自我保护机制。
注意:注册进来的服务名,就是我们在该服务yml文件中配置的微服务名。
image.png

3.2 消费者微服务80注册进EurekaServer

3.2.1 改pom

与8001一样

3.2.2 改yml

  1. # 微服务名称
  2. spring:
  3. application:
  4. name: cloud-order-service
  5. eureka:
  6. client:
  7. # 表示将自己注册进EurekaServer默认为true
  8. register-with-eureka: true
  9. # 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  10. fetch-registry: true
  11. service-url:
  12. defaultZone: http://localhost:7001/eureka

3.2.3 修改主启动类

同8001加上@EnableEurekaClient 注解

3.2.4 测试

启动80消费者端微服务
刷新http://localhost:7001/页面:消费者80微服务也注册进来了。
image.png

如果不想将服务注册进eureka

在yml文件中,设置 register-with-eureka: false

4 集群Eureka搭建

单机版Eureka构建到这就结束了,但是企业中不可能有谁敢说它的服务注册中心是单机版。 因为没有集群的高可用,就会带来一个严重的问题,单点故障。

  1. Eureka集群原理说明
  2. EurekaServer集群环境构建步骤
  3. 将支付服务8001微服务发布到上面2台Eureka集群配置中
  4. 将订单服务80微服务发布到上面2台Eureka集群配置中
  5. 测试01
  6. 支付服务提供者8001集群环境构建。
  7. 负载均衡
  8. 测试02

4.1 Eureka集群原理说明

Eureka流程原理:

image.png
问题:微服务RPC远程服务调用最核心的是什么?
高可用,试想你的注册中心只有一个only one, 它出故障了那就呵呵( ̄▽ ̄)”了,会导致整个为服务环境不可用。
解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错

Eureka集群原理:互相注册,相互守望

image.png

4.2 EurekaServer集群环境构建

4.2.1 新建一个cloud-eureka-server7002模块

4.2.2 pom依赖

与cloud-eureka-server7001依赖相同

4.2.3 修改两个EurekaServer的yml配置

现有存在多台eureka服务器,因此每一台eureka服务器需要有自己的主机名,同时各服务器需要相互注册

修改域名映射配置文件

C:\Windows\System32\drivers\etc 路径下的host文件
image.png
配置之后我们可以直接通过http://eureka7001.com:7001http://eureka7002.com:7002来访问这两个EurekaServer。

修改eureka的yml文件

eureka1:

  1. server:
  2. port: 7001
  3. eureka:
  4. instance:
  5. hostname: eureka7001.com #eureka服务端的实例名称 这里跟host配置文件中的一致
  6. client:
  7. #false表示不向注册中心注册自己。
  8. register-with-eureka: false
  9. #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  10. fetch-registry: false
  11. service-url:
  12. #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
  13. # 单机就是自己
  14. # defaultZone: http://eureka7001.com:7001/eureka/
  15. # 集群指向其他eureka
  16. #defaultZone: http://eureka7002.com:7002/eureka/
  17. #写成这样可以直接通过可视化页面跳转到7002
  18. defaultZone: http://eureka7002.com:7002/

eureka2:

  1. server:
  2. port: 7002
  3. eureka:
  4. instance:
  5. hostname: eureka7002.com #eureka服务端的实例名称 这里跟host配置文件中的一致
  6. client:
  7. #false表示不向注册中心注册自己。
  8. register-with-eureka: false
  9. #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  10. fetch-registry: false
  11. service-url:
  12. #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
  13. # 单机就是自己
  14. # defaultZone: http://eureka7002.com:7002/eureka/
  15. #集群版
  16. #defaultZone: http://eureka7001.com:7001/eureka/
  17. #写成这样可以直接通过可视化页面跳转到7001
  18. defaultZone: http://eureka7001.com:7001/

4.2.4 主配置类

  1. @SpringBootApplication
  2. @EnableEurekaServer
  3. public class EurekaMain7002 {
  4. public static void main(String[] args) {
  5. SpringApplication.run(EurekaMain7002.class, args);
  6. }
  7. }

4.2.5 测试

开启eureka7001和eureka7002这两个服务
访问http://eureka7001.com:7001http://eureka7002.com:7002
image.pngimage.png
两个服务器的相互注册成功。

5 注册提供这微服务和消费者微服务

5.1 将提供者服务8001微服务和消费者服务80微服务发布到上面的2台Eureka集群配置中

修改yml

  1. defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

5.2 测试

image.png
这里的CLOUD-ORDER-SERVICE和CLOUD-PAYMENT-SERVICE
分别对应了提供服务端和消费服务端的yml配置文件中的spring.application.name

6 生产者(provider)微服务集群构建

一个提供者微服务不可能应对所有的消费者微服务

image.png

6.1 新建cloud-provider-payment8002 微服务模块

1. pom与cloud-provider-payment8001一致

唯一区别在于<artifactId>cloud-provider-payment8002</artifactId>

2. 写yml

与8001一致,将port改为8002

3. 主启动类

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. public class PaymentMain8002 {
  4. public static void main(String[] args) {
  5. SpringApplication.run(PaymentMain8002.class, args);
  6. }
  7. }

4. 其他业务诶和payment8001一致

6.2 修改controller

  1. 注意:两个provider微服务在注册中心中的注册名是一样的,这两个微服务对外暴露的都是同一个名字,即yml文件中spring.application.name均为image.png
  2. 消费者微服务调用的是cloud-payment-service这个服务,但是在这个名字下面可能有多台机器。那么如何确定消费者微服务调用哪台机器上的cloud-payment-service服务呢?

通过端口号来区分,我们有两个provider微服务,端口号分别为8001、8002

  1. 修改两个生产者微服务的controller方法,改法一致。

    1. @RestController
    2. @Slf4j
    3. public class PaymentController {
    4. @Autowired
    5. private PaymentService paymentService;
    6. @Value("${server.port}")
    7. private String serverPort;
    8. //插入数据到数据库
    9. @PostMapping("/payment/create")
    10. public CommonResult create(@RequestBody Payment payment) {
    11. int result = paymentService.create(payment);
    12. log.info("***********插入结果:" + result);
    13. int age = 10 / 2;
    14. if (result > 0) {
    15. return new CommonResult(200, "插入数据库成功,serverPort:" + serverPort, result);
    16. }
    17. return new CommonResult(444, "插入数据库失败");
    18. }
    19. //查询数据库中指定id的记录
    20. @GetMapping(value = "/payment/get/{id}")
    21. public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
    22. Payment payment = paymentService.getPaymentById(id);
    23. log.info("***********查询结果:" + payment);
    24. if (payment != null) {
    25. return new CommonResult(200, "查询成功,serverPort:" + serverPort, payment);
    26. }
    27. return new CommonResult(404, "查询id失败:" + id);
    28. }
    29. }
  2. 测试启动EurekaServer

image.png

  1. 测试消费者微服务

image.png
发现若干次不同请求,走的都是provider8001。这是因为我们在消费者为服务中将地址写死了
image.png
所以在访问消费者微服务后,消费者微服务再去调用生产者中的方法。 调用发送的请求一直都是http://localhost:8001/… 所以一直调用了8001端口的微服务。 那么现在我们不指明具体的微服务端口,指定一个这两个生产者微服务共有的指向: 即微服务名:CLOUD-PAYMENT-SERVICE
image.png
那么消费者再去调用生产者中的方法时,就没有指定具体的端口(具体的微服务)。那么哪一个微服务被调用由其他方式来决定。
注意这个说法:

  • 这个程序的使用者会知道这些微服务的端口或者地址吗?
  • 消费者使用时,他们根本不想知道具体哪个微服务对应哪个端口或者具体的地址。他只关心微服务的名称。
  • 他们只想通过看到的微服务名,来使用这些微服务。
  • 也就是说,我们对外暴露的只能是微服务名,来供使用者使用
  1. 再次测试

报错:image.png
原因: 这个微服务下面对应着多个微服务,这个微服务名下的多个微服务到底是哪个出来响应,没有办法识别。

6.3 使用@LoadBlanced注解赋予RestTemplate负载均衡的能力

  1. 以前我们把生产者微服务写死,没关系,因为只有一个生产者微服务,只认一个。
    但是现在不能写死了,因为这个微服务名下对应有多个微服务,那么调用时,必须得说明 哪一个被调用了。 需要规定一种默认的负载均衡机制:@LoadBlanced;在Order80端的配置类上加入@LoadBlanced注解开启负载均衡
    image.png

    7 actuator微服务信息完善

    7.1 主机名称:服务名称的规范和修改

  2. 按照规范的要求只暴露服务名,不带有主机名。
    2. 修改生产者微服务的yml
    image.pngimage.png
    3.测试:仅暴露服务名
    image.png

    7.2 访问信息有IP信息提示

    image.png
    1. 我现在点击这个微服务的链接,没有ip信息提示。 实际工作中,我们都会说这个微服务是部署在几号机器上面的几号端口。我们要让访问信息 有ip信息提示。
    2. 在每个微服务的yml中添加如下
    image.png

    8 服务发现Discovery

    服务发现:对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
    @EnableDiscoveryClient注解
    不排除我们微服务要对外提供一种功能。那么我们就需要拿到在eureka上注册了的微服务的信息,例如:主机名称、端口号。
    1 修改cloud-provider-payment8001的controller:写好提供给外部的信息
    注意写在80和8002中也都可以获得所有的微服务信息,这里只是在8001上进行测试。 ```java @Resource //发现自己的服务信息 private DiscoveryClient discoveryClient;

//服务发现 @GetMapping(value = “/payment/discovery”) public Object discovery() { //得到全部服务清单 List services = discoveryClient.getServices(); for (String service : services) { log.info(“*element:” + service); } //获取微服务实例 List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”); for (ServiceInstance instance : instances) { log.info(instance.getServiceId() + “\t” + instance.getHost() + “\t”

  1. + instance.getPort() + "\t" + instance.getUri());
  2. }
  3. return discoveryClient;

} ``` 2 在8001的主启动类中开启注解@EnableDiscoveryClient
测试,访问http://localhost:8001/payment/discovery
image.png
控制台打印信息
image.png

9 eureka自我保护

9.1 自我保护

  1. 概述: 保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式, EurekaServer将会尝试保护其服务注册表中的信息,不在删除服务注册表中的数据,也就是不会注销任何微服务。
    如果在EurekaServer的首页看到以下这段提示,则说明Eureka进入了保护模式:
    image.png
    一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
    属于CAP里面的AP分支。
    Eureka它的设计思想: 分布式CAP里面的AP分支,某时刻某一个微服务不可用了,Eureka不会立刻清理, 依旧会对该微服务的信息进行保存。
    2. 为什么会产生Eureka自我保护机制?
    因为可能存在这样的情况: EurekaClient可以正常运行,但是与EurekaServer网络不通。 此时EurekaServer不会立刻将EurekaClient服务剔除。
    3. 自我保护
    image.png
    如果一定时间内丢失大量该微服务的实例,这时Eureka就会开启自我保护机制,不会剔除该服务。 因为这个现象可能是因为网络暂时不通,出现了Eureka的假死,拥堵,卡顿。 稍等会客户端还能正常发送心跳。 这就是CAP里面的AP分支思想。
    4.设计哲学
    宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例

    CAP

  • Consistency(一致性):即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
  • Avaliability(可用性):即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
  • Partition Tolerance(分区容错性):即分布式系统在遇到某节点或网络故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求应用虽然是一个分布式系统,但看上去切好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统要求,对于用户而言并没有什么体验上的影响。
  • CP和AP:分区容错是必须保证的,当发生网络分区的时候,如果要继续服务,那么强一致性和可用性只能2选1

9.2 怎么禁止自我保护

修改7001的yml配置文件
image.png
修改eurekaClient端8001
它有两个默认的配置
# Eureka客户端向服务端发送心跳的时间间隔默认30秒
eureka.instance.lease-renewal-interval-in-seconds: 30 单位秒
# Eureka服务端在收到最后一次心跳后,90s没有收到心跳,剔除服务
eureka.instance.lease-expiration-duration-in-seconds: 90 单位秒
image.png