Consul服务注册与发现
1、为什么不再使用传统的Eureka?
- Eureka停更进维护;
- Eureka对初学者还是不太友好的,它还有自我保护模式;
- 注册中心独立且与微服务各功能解耦是大势所趋,目前主流服务中心,希望单独隔离出来而不是作为一个独立微服务嵌入到系统中,按照Netflix的之前的思路,注册中心Eureka也是作为一个微服务且需要程序员自己开发部署的,但是,目前我们更加得希望微服务和注册中心分离解耦,注册中心和业务无关的,不要混为一谈。想要提供类似tomcat一样独立的组件,微服务注册上去使用,是个成品,开箱即用。
- 阿里巴巴Nacos的崛起,既能做注册与发现又可以做分布式配置与管理,一个人可以干俩活,何乐而不为?用帕鲁不香嘛?
2、什么是Consul?
官网:Consul by HashiCorp
HashiCorp Consul 是一种服务网络解决方案,使团队能够管理服务之间以及跨本地和多云环境和运行时的安全网络连接。Consul 提供服务发现、服务网格、流量管理和网络基础设施设备的自动更新。您可以单独使用这些功能,也可以在单个 Consul 部署中一起使用这些功能。
能干什么?
-
服务发现
提供HTTP和DNS两种发现方式。
-
健康监测
支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控。
-
KV存储
Key、Value的存储方式
-
多数据中心
Consul支持多数据中心
-
可视化界面
Spring Cloud Consul:Spring Cloud Consul
3、Consul下载
下载连接Windows:下载 (hashicorp.com)
下载完成后就只有一个consul.exe,在它的下载路径中我们打开命令框输入:
consul --version
看到版本号输出即可。
然后我们使用开发者模式启动,输入以下内容:
consul agent -dev
然后浏览器通过地址:http://localhost:8500就可以访问到Consul的首页了
4、服务注册与发现
4.1、服务提供者8001
支付服务provider8001注册进consul
在pom文件中导入SpringCloud Consul的依赖
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
application.yml文件添加consul配置
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
然后修改主启动类,打上@EnableDiscoveryClient注解,开启服务发现
@MapperScan("com.zm.cloud.mapper")
@SpringBootApplication
@EnableDiscoveryClient
public class Main8001 {
public static void main(String[] args) {
SpringApplication.run(Main8001.class,args);
}
}
然后启动微服务,到consul界面查看,会有一点点的延迟,但很快
我们看一下后台,发现有一个提示,这个提示不影响程序正常运行,它让你移除commons-logging.jar,我们就在consul的依赖下移除它就好了。
Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts
依赖变成这样
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
这样警告信息就没有了。
4.2、服务消费者80
同样的方法先导依赖
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
application.yml配置文件
spring:
application:
name: cloud-consumer-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
主启动类打上@EnableDiscoveryClient注解,开启服务发现。
@SpringBootApplication
@EnableDiscoveryClient
public class Main80 {
public static void main(String[] args) {
SpringApplication.run(Main80.class,args);
}
}
现在再来看一下原来我们写的硬编码问题,当时支付模块8001的地址写死了,看一下controller,做修改,我们要从Consul里获取8001的服务,就直接写成http://cloud-payment-service
//先写死,支付模块的地址
//private static final String PAY_MAN_SERVER = "http://localhost:8001";
private static final String PAY_MAN_SERVER = "http://cloud-payment-service";
启动测试一下,获取一个信息
发现报错了,为什么呢?
因为Consul天生支持负载均衡的,消费者去调用支付服务模块,默认的它会认为支付模块是一个集群,如果直接通过微服务的名字进行调用,它就找不着这个服务从而报错。
这里就需要我们去修改一下RestTemplateConfig了
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced//打开支持负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
这个注解是Spring Cloud特有的,用于开启客户端负载均衡。当RestTemplate
被标记为@LoadBalanced
时,它会使用Ribbon(或其他负载均衡器)来选择一个服务实例进行调用。这在微服务架构中特别有用,因为你可以有多个相同服务的实例运行在不同的服务器上,而@LoadBalanced
的RestTemplate
会帮助你自动选择一个实例进行调用,没有打上这个注解之前它都只认地址加端口号去找服务提供者,我们写成服务名称当然找不着服务了。
下面重启一下再试试
这下就查询到了,服务可用。
4.3、三个注册中心异同点
CAP
- C:Consistency(强一致性);
- A:Availability (可用性);
- P:Partition tolerance(分区容错性);
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好地满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
分析特点:
AP(Eureka)
AP架构
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性结论:违背了一致性C的要求,只满足可用性和分区容错,即AP;
CP(Zookeeper/Consul)
CP架构
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性,Consul 遵循CAP原理中的CP原则,保证了强一致性和分区容错性,且使用的是Raft算法,比zookeeper使用的Paxos算法更加简单。
虽然保证了强一致性,但是可用性就相应下降了,例如服务注册的时间会稍长一些,因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 ;在leader挂掉了之后,重新选举出leader之前会导致Consul 服务不可用。
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP;
5、分布式服务配置与刷新
分布式系统面临的问题,就是配置问题。
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。比如某些配置文件中的内容大部分都是相同的,只有个别的配置项不同。就拿数据库配置来说吧,如果每个微服务使用的技术栈都是相同的,则每个微服务中关于数据库的配置几乎都是相同的,有时候主机迁移了,我们希望一次修改,处处生效。
当下我们每一个微服务自己带着一个application.yml,上百个配置文件的管理非常繁琐。
我们可用将通用的全局配置信息,直接注册进Consul,从Consul获取,既然要从Consul获取就得遵循Consul的配置规则要求。
我们从官方文档中可以看到具体需要怎么做
官网中说,使用YAML格式来存储属性会更方便,可以通过设置spring.cloud.consul.config.format
属性为YAML
或PROPERTIES
来选择使用哪种格式,如果你已经设置了spring.cloud.bootstrap.enabled=true
或spring.config.use-legacy-processing=true
,或者包含了spring-cloud-starter-bootstrap
依赖,那么配置值应该放在bootstrap.yml
而不是application.yml
中。
consul中配置属性的存储位置,yaml必须设置在consul中的适当键下,类似于这样:
config/testApp,dev/data
config/testApp/data
config/application,dev/data
config/application/data
5.1、配置bootstrap.yml
那么就先添加依赖,consul的服务配置
<!--SpringCloud consul config-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
需要新建配置文件bootstrap.yml,啥是bootstrap.yml?
- applicaiton.yml是用户级的资源配置项;
- bootstrap.yml是系统级的,优先级更加高;
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context
的父上下文。初始化的时候,Bootstrap Context
负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment
。
Bootstrap
属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap context
和Application Context
有着不同的约定,所以新增了一个bootstrap.yml
文件,保证Bootstrap Context
和Application Context
配置的分离。**application.yml文件改为bootstrap.yml,这是很关键的或者两者共存。**因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml。
那么接下来就开始写一下,bootstrap.yml文件
spring:
application:
name: cloud-payment-service
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
config:
profile-separator: '-' #官网上默认的是逗号,这不符合我们的习惯,我们使用-号来做分割
format: YAML
# config/cloud-payment-service/data
# /cloud-payment-service-dev/data
# /cloud-payment-service-prod/data
我们抽掉了和服务相关的配置项,那原来的application.yml就可以删除对应的部分了,同时我们要配置多环境配置加载内容dev,当然,不写就是默认的配置。
server:
port: 8001
# ==========applicationName + druid-mysql8 driver===================
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123456
profiles:
active: dev # 多环境配置加载内容dev/prod,不写就是默认default配置
# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.zm.cloud.entities
configuration:
map-underscore-to-camel-case: true
5.2、consul服务器key/value配置填写
参考规则
# config/cloud-payment-service/data
# /cloud-payment-service-dev/data
# /cloud-payment-service-prod/data
根据规则,创建config文件夹以/
结尾
进去创建
然后就有config文件夹了
我们进入config文件夹,再分别创建以下几个文件:
- cloud-payment-service;
- cloud-payment-service-dev;
- cloud-payment-service-prod;
然后在每一个目录下添加data数据。
创建完成后就可以在controller中写个测试,能从consul中拿到上面的data数据就可以了。
现在是dev环境
改一下
profiles:
active: prod # 多环境配置加载内容dev/prod,不写就是默认default配置
再重启查看环境改变
再改一下,default就是不写。
5.3、动态刷新
我们目前在consul的dev配置分支修改环境的时候刷新马上访问结果,就会看到它实时的进行了更新。
我们修改版本为2,刷新一下再看就变成了2版本
按理讲,你可能会说,我都没有显示的配置consul进行动态刷新,它怎么就自己刷新了?这就consul内置的一些机制和功能了。
Consul通过不断地轮询其HTTP API来检测相关的配置目录是否发生了变化。当检测到配置目录发生变化时,Consul会自动触发配置的刷新操作。这种轮询的间隔时间通常是可配置的,例如,通过spring.cloud.consul.config.watch.wait-time
参数可以设置默认的轮询间隔为1000毫秒。一般没有特别的需求是不需要更改的,我们来看一下官网怎么说的。
使用spring.cloud.consul.config.watch.delay
参数就是1秒,这也是默认值。
同时,还有一个wait-time
属性,它设置调用consul的接口方法时会阻塞的指定时间。默认值是55秒,但这个时间需要小于60秒。如果在阻塞过程中有配置修改,则立马返回;否则,要等到阻塞时间结束。
我们可以显示的配置动态刷新,需要在主启动类中打上动态刷新的注解。
@MapperScan("com.zm.cloud.mapper")
@SpringBootApplication
@EnableDiscoveryClient
@RefreshScope //动态刷新
public class Main8001 {
public static void main(String[] args) {
SpringApplication.run(Main8001.class,args);
}
}
然后可以设置指定时间在bootstrap配置文件中,这里就设置3秒了。
spring:
application:
name: cloud-payment-service
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
config:
profile-separator: '-' #官网上默认的是逗号,这不符合我们的习惯,我们使用-号来做分割
format: YAML
watch:
wait-time: 3
我们修改这个时间只是演示作用,实际没啥特殊需要就不要改。
还有,这个时候如果你把consul给关闭了,那么你创建的config文件以及下面的所有配置都会清空的,之前在consul配置的都白配置了,所以我们需要解决一个consul配置持久化问题。
5.4、Consul配置持久化
我们在consul的下载目录中,和consul.exe同级,分别创建一个名为data和myconfig文件夹。
然后启动的时候就可以选择这两个目录启动。
consul agent -server -bootstrap-expect 1 -ui -node=consul-dev -data-dir=D:\Consul\consul_1.18.0_windows_amd64\data -config-dir=D:\Consul\consul_1.18.0_windows_amd64\myconfig -client=0.0.0.0 -bind=127.0.0.1
我们把之前的consul关闭,再使用以上命令启动,就会发现我们设置的config文件都还在,我们再想新增内容它也会保存下来,下次启动的时候选择data路径数据就会都在。
这样做只不过是我们自己手动指定目录的手动持久化,还有一种是自动的,需要我们在consul.exe同级目录下创建一个consul_start.bat文件,内容就是这样写,里面的data路径要写自己的:
@echo.service startup......
@echo off
@sc create Consul binpath= "D:\Consul\consul_1.18.0_windows_amd64\consul.exe agent -server -ui -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect 1 -data-dir D:\Consul\consul_1.18.0_windows_amd64\data"
@net start Consul
@sc config Consul start= AUTO
@echo.Consul starting success!
@pause
解释:
sc create :创建windows服务的命令
Consul :是我们的服务名
binPath:程序的一些路径和别的配置
-client:改成自己的ip,本地就是0.0.0.0,如果是云服务器就写云服务器的公网IP
-data-dir:数据持久化的数据存放在哪
使用管理员运行,它就会创建一个系统服务,开机自启,其实就是把我们手动持久化启动consul的命令写到windows服务中,这样就不需要我们每一次都手动启动consul,一劳永逸的方法,有感兴趣的同学可以尝试,这里我就不再演示了。