三. SpringCloud服务注册与发现

时间:2023-12-23 19:07:19

1. Eureka

1.1 Eureka理解

什么是服务治理

Spring Cloud封装了Netflix公司开发的Eurkeka模块来实现服务治理

在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂。管理比较复杂服务之间的依赖关系可以实现服务调用,负载均衡,容错等,实现服务发现与注册。

什么是服务注册与发现

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

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

下图左边是Eureka系统架构,右边是Dubbo系统架构

三. SpringCloud服务注册与发现

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

  • Eureka Server 提供服务注册中心

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

  • Eureka Client 通过注册中心进行访问

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

1.2 单机Eureka构建步骤

IDEA生成EurekaServer端服务注册中心

类似物业公司

  • 建Module cloud-eureka-server7001
  • pom.xml
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 公共模块 -->
<dependency>
<groupId>com.polaris</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- boot web actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 通用配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

SpringBoot 1x 和 SpringBoot 2x对比

<!-- SpringBoot1.X对应的SpringCloud eureka,不要再用了!!! -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- SpringBoot2.X对应的SpringCloud eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  • yml配置文件
server:
port: 7001 eureka:
instance:
hostname: localhost # eureka服务端的实例名称
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • 主启动类
@SpringBootApplication
@EnableEurekaServer // 声明我是服务注册中心
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain.class);
}
}
  • 测试

运行该Eureka Server主启动类,访问 localhost:7001,就会看到下面的服务注册中心,可以发现目前还没有任何服务入驻进服务注册中心中,在应用中显示:No instances available

三. SpringCloud服务注册与发现

EurekaClient端 服务提供者cloud-provider-payment8001修改

  • pom.xml添加依赖
<!-- 注意:与Eureka Server一样,这里SpringBoot2x不再使用spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • yml配置文件添加配置
spring:
application:
name: cloud-payment-service # 入驻Eureka服务注册中心的服务名称 eureka:
client:
# 表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 单机版
defaultZone: http://localhost:7001/eureka # 入驻的服务注册中心地址
  • 主启动类添加注解
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain {
public static void main(String[] args) {
SpringApplication.run(PaymentMain.class,args);
}
}
  • 测试

注意先要启动EurekaServer,因为有了服务注册中心具体的服务提供者才能后向其中注册自己的服务

可以发现注册到服务注册中心的服务名(图中蓝框)即为我们在yml配置文件中设置的服务名,下面页面中出现的 红字 是Eureka的 自我保护机制

三. SpringCloud服务注册与发现

EurekaClient端 服务消费者cloud-sonsumer-order80修改

与服务提供者cloud-provider-payment8001修改差不多,不再赘述

spring:
application:
name: cloud-order-service # 入驻Eureka服务注册中心的服务名称
eureka:
client:
# 表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 单机
defaultZone: http://localhost:7001/eureka

总结

此时再回看最开始的Eureka系统架构,在服务注册中心和服务提供者没有集群的情况下,7001端口的微服务就对应了服务注册中心,而该服务不需要向服务注册中心注册自己,8001端口的微服务作为服务提供方入住到服务注册中心,8002端口的微服务作为服务消费方也同样注册到服务注册中心

三. SpringCloud服务注册与发现

1.3 集群Eureka构建步骤

集群Eureka原理

服务注册中心Eureka Server中分为 服务注册服务发现,服务注册过程将服务信息注册进服务注册中心,服务发现过程从服务注册中心上获取服务信息,而这个过程的实质就是:将服务名作为key存储,然后根据value取得服务的调用地址。

整个Eureka的过程如下:

  • 先启动Eureka注册中心
  • 启动服务提供者服务
  • 服务提供者服务将自身信息(比如服务地址)以别名方式注册到Eureka注册中心
  • 消费者服务在需要调用接口时,使用服务别名到注册中心获取实际的RPC远程调用地址
  • 消费者获得调用地址后,底层实际是利用 HttpClient 技术实现远程调用
  • 消费者获得服务地址后会缓存字本地JVM内存中,默认每间隔30秒更新一次服务调用地址

那么微服务RPC远程服务调用最核心的是什么呢?

高可用!如果注册中心只有一个,而这个注册中心出现了故障那么整个微服务就直接GG了,整个微服务环境就不可用了,所以应该搭建Eureka注册中心集群, 实现 负载均衡 + 故障容错

那怎么实现Eureka注册中心的集群呢?用一句话总结就是 - 互相注册,相互守望。如下图所示,服务注册中心实现相互注册让彼此都知道对方的存在,也就是注册中心集群中的每一个注册中心都知道整个集群中的其他注册中心,比如如果有三个注册服务中心7001,7002,7003,那么就将7002和7003注册给7001, 将7002和7001注册给7003, 将7003和7001注册给7002, 以此类推,而这些个注册服务中心 作为一个整体对外看做一个注册服务中心。

三. SpringCloud服务注册与发现

Eureaka集群环境构建

参考cloud-eureka-server7001新建一个服务注册中心cloud-eureka-server7002

  • 修改pom.xml

copy复制cloud-eureka-server7001的POM文件即可

  • 修改映射配置(域名映射),用不同的端口号来映射同一个地址

找到C:\Windows\System32\drivers\etc路径下的hosts文件,将其内容修改成如下内容:

# learn-spring-cloud
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
  • yml配置文件,7001与7002都修改一下(以前是单机)

以前是单机版配置,而现在已经有两个注册中心可以看做两台机器,显然 hostname 不能再叫localhost

更改了服务端的实例名称后,最重要的是在defaultZone中将自己注册给其他注册中心

server:
port: 7001 eureka:
instance:
hostname: eureka7001.com # eureka服务端的实例名称
client:
register-with-eureka: false
fetch-registry: true
service-url:
# 互相注册,相互守望
defaultZone: http://eureka7002.com:7002/eureka/
server:
port: 7002 eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: true
service-url:
# 互相注册,相互守望
defaultZone: http://eureka7001.com:7001/eureka/
  • 测试

两个服务中心已经完成了互相注册。主页面DS Replicas下面的信息就表示是这个Eureka Server相邻节点且这些节点加上自己互为一个集群。

  • 将服务提供者8001和服务消费者80发布到2台Eureka集群配置中

修改其配置文件即可,就是将自己的微服务注册到每一个服务注册中心里去,见配置文件中的defaultZone。

eureka:
client:
# 表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  • 测试

三. SpringCloud服务注册与发现

服务提供者集群环境构建

  • 参考8001服务新建8002服务

  • pom.xml

8002和8001的POM文件一样

  • yml配置文件

将端口改为8002,其他和8001相同,两个微服务 对外暴露的服务名相同均为cloud-payment-service 从而构成集群。

server:
port: 8002 spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://mpolaris.top:3306/cloud-test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456 eureka:
client:
# 表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.polaris.springcloud.entities # 所有Entity别名类所在包
  • 主启动类与业务类与8001基本一致

主启动类名字分别为PaymentMain8001与PaymentMain8002

  • controller

修改controller,添加端口号以区分这两个具体的微服务:读取配置文件中设置的端口号。8002的修改同8001。

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService; @Value("${server.port}")
private String serverPort; @PostMapping("/save")
public CommonResult save(@RequestBody Payment payment) {
int result = paymentService.save(payment);
log.info("===> result: " + result);
if(result > 0) {
return new CommonResult(200,
"保存到数据库成功,端口号:" + serverPort,result);
}
return new CommonResult(400,"保存到数据库失败",null);
} @GetMapping("/get/{id}")
public CommonResult<Payment> save(@PathVariable("id") Long id) {
Payment paymentById = paymentService.getPaymentById(id);
log.info("===> payment: " + paymentById);
if(paymentById != null) {
return new CommonResult(200,
"查询成功,端口号:" + serverPort,paymentById);
}
return new CommonResult(400,"查询失败",null);
}
}
  • 测试

如图可以看到此时服务注册中心构成集群,而相同名字的服务提供方的实际提供者已经出现了两个,分别是8001和8002,也就是说服务提供方微服务也实现了集群。

三. SpringCloud服务注册与发现

负载均衡

  • 发现问题:通过服务消费者80访问,只能访问到服务提供者8001

三. SpringCloud服务注册与发现

  • 也就是说每次访问的具体微服务都是8001端口的CLOUD-PAYMENT-SERVICE服务,这明显是不符合业务逻辑的,原因就是在消费方代码中我们将服务访问地址写死了,没有实现负载均衡,这显然是不对的,所以我们应该让80访问服务名而不是具体的服务,即应该将其改为服务提供者 微服务名称!

三. SpringCloud服务注册与发现

  • 同时在配置文件中通过 @LoadBalanced 注解赋予RestTemplate负载均衡能力,该负载均衡默认为轮询方式。所以将80服务的配置文件修改如下:
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //使用该注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
  • 然后重启80端口,发现每次访问 CLOUD-PAYMENT-SERVICE 服务时,具体的微服务在8001和8002之间进行轮询切换。

三. SpringCloud服务注册与发现

  • 当然此时负载均衡我们还没有用到Ribbon,在Ribbon和Eureka整合后,消费者可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能。

总结

三. SpringCloud服务注册与发现

1.4 actuator微服务信息完善

主机名称:服务名称修改

发现问题:在注册中心显示的微服务中,我们发现服务名含有主机名称,这显然不是我们希望看到的

三. SpringCloud服务注册与发现

怎么能解决这个问题呢,只需要修改服务提供方(8001和8002)的配置文件,向其中的eureka部分加入instance实例即可配置该服务显示的服务名称

instance:
instance-id: payment8001

最终的整体配置文件如下:

server:
port: 8001 spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://mpolaris.top:3306/cloud-test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 1234321 eureka:
client:
# 表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
# 单机版
# defaultZone: http://localhost:7001/eureka
instance:
instance-id: payment8001 mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.polaris.springcloud.entities

8002服务的修改同上,此时再访问注册中心,看到的服务具体名称中就没有主机名了,而是我们配置好的服务名称:

三. SpringCloud服务注册与发现

访问信息有IP信息提示

发现问题:我们在鼠标移动到具体服务时,提示的地址信息中并没有服务所在具体主机的IP地址,这在开发中不方便定位具体微服务。

三. SpringCloud服务注册与发现

解决方式仍然是通过配置文件,在配置文件中向其中的eureka部分加入优先ip地址即可配置该服务访问路径可以显示IP地址:

instance:
prefer-ip-address: true # 访问路径可以显示IP地址

三. SpringCloud服务注册与发现

最终的配置文件如下:

eureka:
client:
# 表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001
prefer-ip-address: true #访问路径可以显示IP地址
1.5 服务发现Discovery

对于注册进Eureka服务注册中心的微服务,可以通过服务发现来获取该服务的信息

修改微服务的Controller

向其中注入DiscoveryClient,并编写相应Controller方法

DiscoveryClient对象中的 getServices 方法用于获取服务列表的信息,也就是有哪些服务,如cloud-payment-service服务, getInstances 方法用于获取服务列表对应的具体服务实例,如cloud-payment-service服务对应的8001和8002服务。

import org.springframework.cloud.client.discovery.DiscoveryClient;
//...
@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
//... @Resource
private DiscoveryClient discoveryClient; @GetMapping("/discovery")
public Object discovery() {
//获取服务列表的信息
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("===> service:" + service);
} //根据微服务名称获取具体服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info("===> " + instance.getServiceId()
+ "\t" + instance.getHost()
+ "\t" + instance.getPort()
+ "\t" + instance.getUri());
}
return this.discoveryClient;
}
//...
}

修改主启动类

只需要在主启动类上添加注解@EnableDiscoveryClient,修改后的主启动类:

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

测试

访问地址http://localhost:8001/payment/discovery,我们可以看到获取的服务信息,即完成了服务发现:

三. SpringCloud服务注册与发现

后台也对服务列表进行了日志打印:

<img src="https://gitee.com/mp2333/blog-img/raw/master/SpringCloud/20210126234348.png" alt="image-20210126234348515" https://gitee.com/mp2333/blog-img/raw/master/SpringCloud/20210126234854.png />

1.6 Eureka自我保护(属于CAP里面的AP分支)

自我保护机制

保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。换句话说就是,某时刻某一个微服务不可用了,Eureka不会立刻清理,而是依旧会对该微服务的信息进行保存。

如果在Eureka Server的首页看到以下提示,说明Eureka进入了保护模式

三. SpringCloud服务注册与发现

产生原因

为什么会产生Eureka自我保护机制? => 为了防止 EurekaClient可以正常运行,但是与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除。

什么是自我保护模式? => 默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之前无法正常通信,以上行为可能变得非常危险 - 因为微服务本身是健康的,只是由于网络问题链接不到EurekaServer,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题 :当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障,网络延时),那么这个节点就会进入自我保护模式。在自我保护模式中,EurekaServer会保护服务注册表中的信息,不再注销任何服务实例,宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

怎么禁止自我保护

先在EurekaServer端修改配置文件即可设置关闭自我保护机制

eureka:
server:
# 关闭自我保护机制,保证不可用服务被及时剔除。默认为true开启
enable-self-preservation: false
# 时间间隔,单位ms
eviction-interval-time-in-ms: 2000

然后在EurekaClient端修改配置文件

eureka:
instance:
instance-id: payment8001
# Eureka客户单向服务端发送心跳的时间间隔,默然是30秒,这里改成1秒
lease-renewal-interval-in-seconds: 1
# Eureka服务端在收到最后一次心跳后等待时间上限,默然为90秒,超时将剔除服务,这里改成2秒
lease-expiration-duration-in-seconds: 2

这样就会使EurekaClient客户端的微服务很快死亡。

2. Zookeeper

2.1 Eureka停止更新

https://github.com/Netflix/eureka/wiki

我们可以使用SpringCloud整合Zookeeper替代Eureka

2.2 Zookeeper理解

Zookeeper是一个分布式协调工具,可以实现注册中心功能

安装Zookeeper

# 关闭Linux服务器防火墙(关闭默认端口2181也行)
# 2181 对Client端提供服务的端口
# 3888 选举Leader
# 2888 集群内的机器通讯使用。(Leader使用此端口)
systemctl stop firewalld
systemctl status firewalld # 我这里使用的是zookeeper-3.4.11.tar.gz,解压即可 # bin目录下启动zookeeper服务器
./zkServer.sh start # 连接zookeeper客户端
# 如果是连接同一台主机上的zk进程,那么直接运行bin/目录下的kCli.sh,即可连接上zk。
# 直接执行zkCli.sh命令默认以主机号 127.0.0.1,端口号 2181 来连接zk
# 如果要连接不同机器上的zk,可以使用 -server 参数,例如:
./zkCli.sh -server 192.168.0.1:2181 # 启动报错?
# grep: /usr/local/zookeeper-3.4.11/bin/../conf/zoo.cfg: No such file or directory
# 这里的原因是因为下载下来的zoo.cfg名字是zoo_sample.cfg,只需要改名字即可
mv zoo_sample.cfg zoo.cfg

Zookeeper服务器取代Eureka服务器,zk作为服务注册中心

2.3 服务提供者

新建cloud-provider-payment8004

pom.xml

<dependencies>
<!-- 公共模块 -->
<dependency>
<groupId>com.polaris</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- boot web actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency> <!--SpringBoot整合Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency> <!-- 通用配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml配置文件

server:
# 8004表示注册到zookeeper服务器的支付服务提供者端口号
port: 8004
spring:
application:
# 服务别名 - 注册zookeeper到注册中心的名称
name: cloud-provider-payment
cloud:
zookeeper:
# zookeeper访问地址
connect-string: mpolaris.top:2181

主启动类

@SpringBootApplication
//该注解用于向使用consul或zookeeper作为注册中心时注册服务
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}

controller

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort; @RequestMapping(value = "/zk")
public String paymentZk() {
return "===> SpringCloud with zookeeper:"
+ serverPort
+ "\t"
+ UUID.randomUUID().toString();
}
}

启动8004注册进zookeeper

  • 启动zk: zkServer.sh start
  • 启动8004后报错

三. SpringCloud服务注册与发现

  • why?

    • 解决zookeeper版本jar包冲突问题

三. SpringCloud服务注册与发现

  • 排除zk冲突后的新pom.xml
<!--SpringBoot整合Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!-- 先排除自带的zookeeper3.5.3 -->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency> <!-- 添加zookeeper3.4.11版本zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
</dependency>

验证测试1

三. SpringCloud服务注册与发现

访问 http://localhost:8004/payment/zk

三. SpringCloud服务注册与发现

验证测试2

三. SpringCloud服务注册与发现

获得json串后用在线工具查看如下

三. SpringCloud服务注册与发现

思考

服务节点是临时节点还是持久节点? => 临时节点

三. SpringCloud服务注册与发现

2.4 服务消费者

新建cloud-consumerzk-order80

pom.xml

<dependencies>
<!-- 公共模块 -->
<dependency>
<groupId>com.polaris</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringBoot整合Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<exclusions>
<!--先排除自带的zookeeper3.5.3-->
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.11版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 通用配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml配置文件

server:
port: 80
spring:
application:
# 服务别名
name: cloud-consumer-order
cloud:
zookeeper:
# 注册到zookeeper地址
connect-string: mpolaris.top:2181

主启动类

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

业务类

配置类注入RestTemplate

@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

controller

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderZkController { public static final String INVOKE_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate; /**
* http://localhost/consumer/payment/zk
* @return
*/
@GetMapping("/payment/zk")
public String paymentInfo() {
return restTemplate.getForObject(INVOKE_URL
+ "/payment/zk", String.class);
}
}

测试验证

访问地址 http://localhost/consumer/payment/zk

3. Consul

3.1 理解

什么是Consul

Consul是一套开源的分布式服务发现配置管理系统,由HashiCorp公司用Go语言开发。提供了微服务系统中的 服务治理配置中心控制总线 等功能。这些功能中的每一个都可以根据单独需要使用,也可以一起使用以构建全方位的服务网络,总之Consul提供了一种完整的服务网络解决方案。其官方介绍见Consul官网

它具有很多优点。包括基于raft协议,比较简洁;支持健康检查,同时支持HTTPDNS协议,支持跨数据中心的WAN集群,提供图形界面,跨平台,支持Linux/Mac/Windows。

Consul作用

  • 服务发现Service Discovery:提供HTTP和DNS两种发现方式。
  • 健康监测Health Checking:支持多张方式,HTTP、TCP、Docker、Shell脚本定制化
  • KV存储:Key、Value的存储方式
  • 多数据中心:Consul支持多数据中心
  • 可视化Web界面
3.2 安装并运行Consul

官网下载Windows的64位版本Consul后下载的是zip压缩包,将压缩包解压后里面只有一个consul.exe文件。在该路径下双击其exe文件就可以运行Consul,进入cmd命令行运行,输入以下命令查看Consul的版本号信息,我用的是1.7.3版本:

consul --version

当然也可以不用有点low的双击Consul启动,可以在命令行中用开发模式启动,输入以下命令

consul agent -dev

启动后我们访问 localhost:8500 ,就可以看到Consul和Eureka一样,有一个前端可视化Web界面。这样的话Consul服务注册中心就已经启动了,且运行在8500端口

三. SpringCloud服务注册与发现

3.3 服务提供者

新建cloud-provider-consul-payment8006

pom.xml

在Eureka的服务提供方中我们引入了如下的依赖:

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

而我们现在服务注册中心用的不再是Eureka而是Consul,只需要被Eureka Client的依赖更改为如下依赖,这样引入了让Consul服务注册中心发现自己微服务的相关jar包。

<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

yml配置文件

server:
port: 8006 # consul服务端口号 spring:
application:
name: consul-provider-payment
# consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}

主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8006.class);
}
}

业务类Controller

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController { @Value("${server.port}")
private String serverPort; @RequestMapping("/consul")
public String paymentConsul() {
return "springcloud with consul: "
+ serverPort
+ "\t"
+ UUID.randomUUID().toString();
}
}

测试

经过以上配置,我们会在Consul服务注册中心中发现入驻的 consul-provider-payment 服务

三. SpringCloud服务注册与发现

3.4 服务消费者

新建cloud-consumer-consul-order80

pom.xml

为了可以将自己注册到Consul服务注册中心,和服务提供方一样,仍然是引入如下依赖:

<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

yml配置文件

和服务提供方几乎完全相同,只需要修改下自己的端口号和自己的服务名称

# consul服务端口号
server:
port: 80 spring:
application:
name: cloud-consumer-order
# consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
# hostname: 127.0.0.1
service-name: ${spring.application.name}

主启动类

@SpringBootApplication
@EnableDiscoveryClient //用于向使用Consul或Zookeeper作为注册中心时注册服务
public class OrderConsulMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderConsulMain80.class);
}
}

配置类注入RestTemplate

@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //使用该注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

业务类Controller

@RestController
@Slf4j
public class OrderConsulController { //要访问的服务提供方的微服务名称
public static final String INVOKE_URL = "http://consul-provider-payment"; @Resource
private RestTemplate restTemplate; @GetMapping("/consumer/payment/consul")
public String paymentInfo() {
return restTemplate.getForObject(INVOKE_URL
+ "/payment/consul", String.class);
}
}

测试

将服务消费方微服务启动后,在Consul服务注册中心我们可以发现同时有了提供者和消费者

服务提供方自测是没有问题的,下面我们通过服务消费方来访问提供方服务,发现也可以正常访问

三. SpringCloud服务注册与发现

4. 三者异同

4.1 比较
组件名 编写语言 CAP 服务健康检查 对外暴露接口 SpringCloud集成
Eureka Java AP 可配支持 HTTP 已集成
Consul Go CP 支持 HTTP/DNS 已集成
Zookeeper Java CP 支持 客户端 已集成
4.2 什么是CAP

CAP理论:首先我们要知道CAP对应的都是什么。

C A P
Consistency Available Partition tolerance
强一致性 可用性 分区容错性

所谓CAP原则又称CAP定理,指的是在一个分布式系统中,一致性、可用性、分区容错性。CAP 原则指的是这三个要素 最多只能同时实现两点,不可能三者兼顾。在分布式架构中,P永远要求被保证,所以当前的分布式架构只有AP和CP两种。因此根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

  • CA:单点集群,满足一致性、可用性的系统,通长在可扩展性上不太强大。
  • CP:满足一致性、分许容错性的系统,通常性能不是特别高。

三. SpringCloud服务注册与发现

  • AP:满足可用性、分区容错性的系统,通常可能对一致性要求低一些。

三. SpringCloud服务注册与发现