对于很多微服务模块都需要的通用组件,我们可以把这些通用的提取出来作为一个通用组件包。
这个包是不作为一个微服务的,因此不需要启动类。
新建通用组件模块Consul简介
概念
Consul is a multi-networking tool that offers a fully-featured service mesh solution. It solves the networking and security challenges of operating microservices and cloud infrastructure in multi-cloud and hybrid cloud environments. This documentation describes Consul concepts, the problems it solves, and contains quick-start tutorials for using Consul.
简单来说,consul是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。
在SringCloud中提供了spring consul。
consul官网
consul的用途
- 服务发现:提供HTTP和DNS两种发现方式。
- 健康监测:支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控。
- KV存储:Key、Value的存储方式。
- 多数据中心:Consul支持多数据中心。
- 可视化Web界面
consul的下载
consul的下载
根据windows32位操作系统选择386版本,64位操作系统选择AMD64版本
怎么使用consul
SpringCloud Consul文档
consul核心功能:
- 服务发现
- 配置管理
consul的安装配置
consul的下载
下载完成后,进入解压缩目录,命令行界面输入 consul -version
即可查看consul信息。
使用 consul agent -dev
启动consul,访问localhost:8500若能查看到如下则安装代表安装完成。
服务注册与发现
注册8001服务进consul
引入依赖
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
修改yml
server:
port: 8001
# ==========applicationName + druid-mysql8 driver===================
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123456
# 关于consul的配置
cloud:
consul:
host: localhost
port: 8500
# 指定服务的名称
discovery:
service-name: ${spring.application.name}
# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.cloud.entities
configuration:
map-underscore-to-camel-case: true
在主启动类上使用@EnableDiscoveryClient注解,启动服务发现功能
package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper")
@EnableDiscoveryClient
public class Main8001 {
public static void main(String[] args)
{
SpringApplication.run(Main8001.class);
}
}
启动8001服务,在consul的ui界面就会发现8001服务已经注册进consul了
注册80服务进consul
引入依赖
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
修改yml
server:
port: 80
spring:
application:
name: cloud-consumer-order
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true #优先使用服务ip进行注册
在主启动类上使用@EnableDiscoveryClient注解,启动服务发现功能
package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class Main80 {
public static void main(String[] args)
{
SpringApplication.run(Main80.class);
}
}
修改controller
在之前的80消费微服务中,我们调用8001提供者微服务的url是采用硬编码格式,但8001已经注册进服务注册中心,因此使用服务注册中心的微服务名称代替硬编码即可,后续即使我们修改了端口,但微服务的名称是没有改变的,不需要我们每次都修改80中的url。
// 硬编码格式
//private static final String PaymentSrv_URL="http://localhost:8001";
//使用服务注册中心上的微服务名称
private static final String PaymentSrv_URL="http://cloud-payment-service";
启动后发现consul的ui界面已经成功注册80消费微服务
开启负载均衡
已经把消费和提供服务注册,调用消费服务会报如下错误
Caused by: java.net.UnknownHostException: cloud-payment-service
在未注册到 Consul 之前,服务消费者通常通过硬编码的方式指定服务提供者的 URL(例如 http://192.168.1.100:8080),因此可以直接找到并访问固定的 IP 地址。
而在注册到 Consul 后,服务消费者通过服务名称(例如 cloud-payment-service)访问服务,注册中心返回的是服务实例的地址列表,而不是具体的单个 IP 地址。对于微服务集群,通常一个服务名称对应多个实例,因此仅通过服务名称无法确定具体调用哪个实例。
为了实现动态实例选择,Consul 提供了负载均衡支持。通过在服务消费者的 RestTemplate 上添加 @LoadBalanced 注解,启用客户端负载均衡功能,使其能够从服务实例列表中动态选择一个可用实例。
package com.atguigu.cloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
// 使用@LoadBalanced注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
重新启动消费微服务测试后,可以正常调用提供微服务
consul与其它注册中心的异同
什么是CAP
C: Consistency (强一致性)
A: Availability (可用性)
P: Partition tolerance (分区容错性)
经典CAP图
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
常见三个注册中心的异同
组件名 | 语言 | CAP | 服务健康检查 | 对外暴露接口 | Spring Cloud 集成 |
---|---|---|---|---|---|
Eureka | Java | AP | 可配支持 | HTTP | 已集成 |
Consul | Go | CP | 支持 | HTTP/DNS | 已集成 |
Zookeeper | Java | CP | 支持 | 客户端 | 已集成 |
AP
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
CP
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性,Consul 遵循CAP原理中的CP原则,保证了强一致性和分区容错性,且使用的是Raft算法,比zookeeper使用的Paxos算法更加简单。虽然保证了强一致性,但是可用性就相应下降了,例如服务注册的时间会稍长一些,因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 ;在leader挂掉了之后,重新选举出leader之前会导致Consul 服务不可用。结论:违背了可用性A的要求,只满足一致性和分区容错,即CP
服务的配置与刷新
分布式系统的配置问题
在分布式系统中,一个单体应用中的业务通常会被修改为很多子服务,系统中会存在大量的服务。
比如,许多子服务连接的是同一台电脑上的数据库,如果数据库进行了主机迁移,那么每一个子服务都需要修改一次配置文件,过于繁琐。
所以需要一套集中式,动态的配置管理设置。
SpringCloud 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
applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高
bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.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存储所有配置信息或者两者共存,application.yml存放单个微服务所需配置,bootstrap.yml村全局配置
因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml
使用consul作为服务注册中心的时候,微服务项目启动需要依赖于consul,因此要先启动consul再启动微服务项目,在启动微服务项目之前,consul会将通用的配置信息发送给给微服务项目,因此会出现两种配置,consul上的全局配置,微服务上的配置。显然consul上的系统级全局配置优先级更高。
在这里我们使用两者共存的方式,spring的配置放到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
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:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123456
profiles:
active: dev # 多环境下激活环境,不写默认配置
# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.cloud.entities
configuration:
map-underscore-to-camel-case: true
consul服务器配置key/value配置填写
上面的官网配置规则已经给出,但官网给出的单词之间是以逗号分割的,由于我们在bootstrap.yml中配置了以 - 分割,所以命名规则可以是
cloud-payment-service-dev这种微服务名称-环境这种形式。
首先在consul的控制台的Key/Value界面创建 config文件夹
之后分别创建cloud-payment-service,cloud-payment-service-dev,cloud-payment-service-prod三个文件夹,分别代表不同环境下生效的配置文件夹,分别在三个文件夹中创建data文件
data文件即是我们放在consul上的配置文件,需要时yaml的格式。
例如,
atguigu:
info: dev config
就可以在bootstrap.yml中获取到这个值
atguigu:
info: ${atguigu.info}
测试
@Value("${atguigu.info}")
private String infoByConsul;
@GetMapping("/pay/get/info")
private String getInfoByConsul(){
System.out.println(infoByConsul);
return infoByConsul;
}
bootstrapt首先从consul中获取atguigu.info,然后使用@Value注入infoByConsul,打印infoByConsul
在控制台中可以看到dev config被正确打印出来,在application.yml中可以切换生效的环境,从而使用不同的配置。
动态刷新
consul上的配置更新,需要微服务也更新相关配置。
在主启动类上使用@RefreshScope开启动态刷新。
开启动态刷新后,默认会等待一会刷新,可以在bootstrapt中配置spring.cloud.sonsul.config.watch.wait-time设置刷新时间,以秒为单位
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: '-' # default value is ",",we update '-',否则就会是config/cloud,payment,service/data这种形式
format: YAML
# 一秒刷新
watch:
wait-time: 1
atguigu:
info: ${atguigu.info}
# config/cloud-payment-service/data
# /cloud-payment-service-dev/data
# /cloud-payment-service-prod/data
经过测试,我的刷新仍然没有生效。
因为@Value注解Spring容器启动的时候只会解析一次,但在方法中会每次动态解析。
@Value("${atguigu.info}")
private String infoByConsul;
@GetMapping("/pay/get/info")
private String getInfoByConsul(@Value("${atguigu.info}") String info){
System.out.println(info);
System.out.println(infoByConsul);
return infoByConsul;
}
Consul的配置持久化
要想实现关闭windows或关机后consul的数据仍然存在,那么必须将consul的数据存储到一个文件夹中。
新建文件夹
在consul的解压目录新建 mydata文件夹
启动脚本
在consul的解压目录下新建 consul_start.bat
写入以下内容,脚本中的文件夹部分更改为自己的
@echo.服务启动......
@echo off
@sc create Consul binpath= "D:\consul\consul.exe agent -server -ui -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect 1 -data-dir D:\consul\mydata "
@net start Consul
@sc config Consul start= AUTO
@echo.Consul start is OK......success
@pause
以管理员形式启动脚本
如果显示Consul服务已经存在,那么可以sc delete Consul删除服务后,重新管理员启动脚本即可
将其余微服务的通用组件复制粘贴到通用组件模块即可,引入相关依赖。
我这里主要有实体类,全局异常处理类,以及返回结果类
引入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloud-api-commons</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--SpringBoot通用依赖模块-->
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>
打包
为了让其余微服务可以获取到此模块的通用组件,需要打包为jar包
删除消费和订单微服务中通用组件并改造pom
将消费和订单微服务中的订单和消费通用组件删除后,分别在两个微服务的pom文件中加入并刷新
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
启动微服务
启动消费和订单微服务,发现仍然可以正常启动
那么提取通用组件模块功能就完成了