微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
我们每一个微服务自己带着一个application.yml,上百个配置文件的管理就非常痛苦,SpringCloud 提供了ConfigServer来解决这个问题。
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
SpringCloud Config分为服务端和客户端两部分。服务端也称为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密/信息等访问接口。
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器,默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
1.集中管理配置文件
2.不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
3.运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取自己的配置信息
4.当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
5.将配置信息以REST接口形式暴露,post、curl访问刷新均可…
由于SpringCloud Config默认使用Git来存储配置文件(也有其他方式,比如支持SVN和本地文件),但最推荐还是Git,而且使用的是http/https访问的形式。
用自己的账号在GitHub上新建一个名为springcloud-config的新Repository。
由上一步获得刚新建的git地址:xxxxx
本地硬盘目录上新建git创库并clone: git clone xxxx
此时,在之前本地创建的目录SpringCloud2020\springcloud-config下,有多个表示多个环境的配置文件,保存格式必须为UTF-8。如果需要修改,此处模拟运维人员操作git和gitee
git add .
git commit -m "init yml"
git push origin master
它即为Cloud的配置中心模块cloudConfig Center
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-config-center-3344artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
project>
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: xxxxxxxxxxxxxxxx #gitee上的仓库名 配置服务器为各个不同的微服务应用的所有环境提供了一个中心化的外部配置
#若仓库是私有的,需要配置用户名和密码
username: xxx
password: xxx
# 搜索目录
search-paths:
- springcloud-config
# 读取分支
label: master
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class);
}
}
1.启动微服务 3344
2.http://config-3344.com:3344/master/config-dev.yml
master分支:
http://config-3344.com:3344/master/config-dev.yml
http://config-3344.com:3344/master/config-test.yml
http://config-3344.com:3344/master/config-prod.yml
dev分支
http://config-3344.com:3344/dev/config-dev.yml
http://config-3344.com:3344/dev/config-test.yml
http://config-3344.com:3344/dev/config-prod.yml
http://config-3344.com:3344/config-dev.yml
http://config-3344.com:3344/config-test.yml
http://config-3344.com:3344/config-prod.yml
http://config-3344.com:3344/config-xxxx.yml (不存在的配置)
http://config-3344.com:3344/config-dev.yml/master
http://config-3344.com:3344/config-test.yml/master
http://config-3344.com:3344/config-prod.yml/master
label: 分支(branch)
name: 服务名
profiles: 环境(dev/test/prod)
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-config-client-3355artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
project>
config 客户端:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
config 服务端:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
bootstrap.yml是什么:
application.yml是用户级的资源配置项,bootstrap.yml是系统级的,优先级更高。
SpringCloud会创建一个"BootStrap Context",作为Spring应用的"Application Context"的父上下文。初始化的时候,“BootStrap Context” 负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的 “Environment”。
"BootStrap Context"属性有高优先级,默认情况下,它们不会被本地配置覆盖。“BootStrap Context” 和 "Application Context"有着不同的约定,所以增加了一个 ‘bootstrap.yml’ 文件,保证 ‘Bootstrap Context’ 和 ‘Application Context’ 配置的分离。
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,因为bootstrap.yml是比application.yml先加载。bootstrap.yml 优先级高于 application.yml。
bootstrap.yml 内容:
server:
port: 3355
spring:
application:
name: config-client
cloud:
config: #Config客户端配置
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述综合:master分支上config-dev.yml的配置文件被读取 http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
修改config-dev.yml配置并提交到gitee,比如加个变量age或者版本号version
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class);
}
}
package com.atguigu.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
启动Config配置中心3344微服务并自测
启动3355作为Client准备访问
直接访问配置中心:
访问client客户端:http://localhost:3355/configInfo
成功实现了客户端3355访问Spring Cloud Config3344通关gitee获取配置信息。
问题随之而来,分布式配置动态刷新问题:
Linux运维修改gitee上的配置文件内容
刷新3344,发现ConfigServer配置中心立即响应,立即变化
刷新3355,发现ConfigClient客户端没有响应,没有变化
3355没有变化,除非自己重启或者重新加载
难道每次运维修改配置文件,客户端都需要重启吗?不可能!
避免每次更新配置都要重启客户端微服务3355
需要引入actuator依赖(之前是引入了的)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
server:
port: 3355
spring:
application:
name: config-client
cloud:
config: #Config客户端配置
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述综合:master分支上config-dev.yml的配置文件被读取 http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
management: # 暴露监控端点
endpoints:
web:
exposure:
include: "*"
在Controller类上添加@RefreshScope注解,实现刷新功能
package com.atguigu.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
修改gitee上配置文件内容
再次访问3344 http://config-3344.com:3344/config-dev.yml
配置中心获取的配置文件仍然随着变化
访问Config 客户端 http://localhost:3355/configInfo
3355还是没有随着gitee配置文件变化而变化
需要运维人员发送post请求刷新3355:
1.必须是POST请求
2.curl -X POST “http://localhost:3355/actuator/refresh” (X 大写)
实现了客户端3355刷新到最新的配置内容,避免了服务重启。
假设有多个微服务客户端3355、3366、3377 …
每个微服务都要去手动执行一次post请求手动刷新吗?
可否广播,一次通知,处处生效?
下一章,消息总线 SpringCloud Bus
对上一讲的加深和扩充,实现分布式自动刷新配置功能。SpringCloud Bus配合SpringCloud Config使用可以实现配置的动态刷新。
是什么?
Bus支持两种消息代理:RabbitMQ 和 Kafka
SpringCloud Bus是用来将分布式系统节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。SpringCloud Bus目前支持RabbitMQ 和 Kafka
能干嘛?
SpringCloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以作为微服务间的通信通道。
为什么被称为总线?
什么是总线:在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为总线。在总线上的各个实例,都可以方便的广播一些需要让其他连接在该主题上的实例都知道的消息。
基本原理:ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,他会把这个消息放入topic,这样其他监听同一个Topic的服务就能得到通知,然后去更新自身的配置。
安装Erlang,下载地址:链接:https://pan.baidu.com/s/1uzYQJ3q-AYKimUXAHA8log
提取码:yyds
下载并安装。
进入RabbbitMQ安装目录的sbin目录下:
输入以下命令,启动管理功能:
rabbitmq-plugins enable rabbitmq_management
通过上图的RabbitMQ Service - start 启动RabbitMQ,访问地址查看是否安装成功:http://localhost:15672/
必须先具备良好的RabbitMQ环境,再添加一个3366 config client
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-config-client-3366artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
project>
server:
port: 3366
spring:
application:
name: config-client
cloud:
config: #Config客户端配置
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述综合:master分支上config-dev.yml的配置文件被读取 http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
management: # 暴露监控端点
endpoints:
web:
exposure:
include: "*"
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3366 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3366.class);
}
}
和3355基本一样
package com.atguigu.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@Value("${server.port}")
private String serverPort;
@GetMapping("/configInfo")
public String getConfigInfo() {
return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo;
}
}
1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
图1:
2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
图2:
图2的架构明显更加合适,图1不合适的原因:
1.打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
2.破坏了微服务各节点的对等性。
3.有一定局限性,例如,微服务迁移时,它的网络地址常常会发生变化,此时如果想做到自动刷新,那就会增加更多的修改。
服务端 3344 pom.xml 添加消息总线RabbitMQ支持
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
3344 application.yml
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://gitee.com/xxxxxxxxxx #gitee上的仓库名 配置服务器为各个不同的微服务应用的所有环境提供了一个中心化的外部配置
# 搜索目录
search-paths:
- springcloud-config
username: 13888888888
password: 66666666666
# 读取分支
label: master
rabbitmq: #rabbitMQ相关配置
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
## rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
客户端 3355 pom.xml 添加消息总线RabbitMQ支持
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
客户端 3355 bootstrap.yml 配置
server:
port: 3355
spring:
application:
name: config-client
cloud:
config: #Config客户端配置
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述综合:master分支上config-dev.yml的配置文件被读取 http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
# rabbitMQ相关配置 15672是web管理界面的端口 5672是MQ访问的端口
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
management: # 暴露监控端点
endpoints:
web:
exposure:
include: "*"
省略,与3.1.7添加配置相同
启动RabbitMQ、Eureka7001、7002,配置中心3344,配置客户端3355、3366
运维工程师:
1.修改Gitee上配置文件
2.发送POST请求:
curl -X POST "http://localhost:3344/actuator/bus-refresh"
一次发送,处处生效
配置中心:
http://config-3344.com:3344/config-dev.yml
config 客户端:
http://localhost:3355/configInfo
http://localhost:3366/configInfo
修改gitee上配置文件配置,分别刷新3344配置中心,3355、3366 客户端,此时只有3344配置中心的配置随着修改gitee上配置变化。
执行post请求:
再次刷新 3355、3366 客户端 时,配置已与gitee上配置一致,达到了一次修改,广播通知,处处生效的效果
不想通知全部客户端,只想定点通知。
比如:只通知3355,不通知3366
简单一句话,指定某具体的实例生效而不是全部。
公式:
http://localhost:3344(配置中心的端口)/actuator/bus-refresh/{destination}
/bus/refresh请求不再发送到具体的服务实例上,而是发给config server,并通过destination参数指定需要配置更新的服务或实例。
案例:我们这里以刷新运行在3355端口的config-client为例,只通知3355,不通知3366。
修改gitee上配置文件配置,执行post请求:
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
destination就是微服务名加端口号
此时分别刷新client3355、3366,只有3355的配置跟随gitee上配置变化了。
一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。
什么是SpringCloudStream:官方定义SpringCloud Stream是一个构建消息驱动微服务的框架。应用程序通过 inputs 或者 outputs 来与Spring Cloud Stream中binder对象交互。通过我们配置来binding(绑定),而SpringCloud Stream中的binder对象负责与消息中间件交互,所以,我们只需要搞清楚如何与SpringCloud Stream交互就可以方便的使用消息驱动的方式。
通过使用Spring Integration 来连接消息代理中间件以实现消息事件驱动。SpringCloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
目前仅支持RabbitMQ、Kafka。
中文指导手册:https://m.wang1314.com/doc/webapp/topic/20971999.html
生产者、消费者之间靠消息媒介传递信息内容:Message
消息必须走特定的通道:消息通道MessageChannel
消息通道里的消息如何被消费呢,谁负责收发处理:消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅。
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区,这些中间件的差异性给我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我们想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都需要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud stream给我们提供了一种解耦的方式。
在没有绑定器这个概念的情况下,我们的springboot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,他们的实现细节上会有较大的差异性,通过定义绑定器Binder作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
INPUT对应于消费者,OUTPUT对应于生产者
Topic主题进行广播,在RabbitMQ就是Exchange,在Kafka中就是Topic
Binder: 很方便的连接中间件,屏蔽差异
Channel: 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置。
Source和Sink: 简单可以理解为参照对象是SpringCloud Stream自身,从Stream发布消息就是输出,接收消息就是输入。
组成 | 说明 |
---|---|
Middleware | 中间件,目前只支持RabbitMQ和Kafka |
Binder | Binder是应用与中间件之间的封装,目前实现了Kafka和RabbitMQ的Binder。通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现 |
@Input | 注解标识输入通道,通过该输入通道接收到的消息进入应用程序 |
@Output | 注解标识输出通道,发布的消息将通过该通道离开应用程序 |
@StreamListener | 监听队列,用于消费者对队列的消息接收 |
@EnableBinding | 指信道channel和exchange绑定在一起 |
需要RabbitMQ环境以及OK,工程中新建三个子模块:
1.cloud-stream-rabbitmq-provider8801,作为生产者进行发消息
2.cloud-stream-rabbitmq-consumer8802,消息接收模块
3.cloud-stream-rabbitmq-consumer8803,消息接收模块
新建Module cloud-stream-rabbitmq-provider8801
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-stream-rabbitmq-provider8801artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
project>
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的RabbitMQ的服务信息
defaultRabbit: # 表示定义的名称,用于binding整合
type: rabbit # 消息组件类型
environment: #设置rabbitMQ的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置为:text/plain
binder: defaultRabbit # 设置要绑定的消息服务的具体类型
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30s)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5s的间隔 (默认是90s)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为ip地址
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class, args);
}
}
接口:
package com.atguigu.springcloud.service;
public interface IMessageProvider {
String send();
}
实现类:
package com.atguigu.springcloud.service.impl;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import javax.annotation.Resource;
import java.util.UUID;
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; //消息发送管道
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("***********serial: " +serial);
return null;
}
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage() {
return messageProvider.send();
}
}
启动Eureka7001、7002,启动rabbitMQ,启动8801,然后访问:/sendMessage
服务已经注册到Eureka中:
rabbitMQ:
控制台:
测试成功!
新建Module cloud-stream-rabbitmq-consumer8802
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-stream-rabbitmq-consumer8802artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
project>
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置表示要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于与binding整合
type: rabbit # 消息组件的类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名称是通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置为:text/plain
binder: defaultRabbit # 设置要绑定的消息服务的具体类型
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30s)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5s的间隔 (默认是90s)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为ip地址
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class, args);
}
}
package com.atguigu.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("消费者1号,----->收到的消息: "+message.getPayload()+ "\t port: " + serverPort );
}
}
启动Eureka7001、7002,RabbitMQ,消息生产者8801,消息消费者8820
Eureka服务注册信息:
访问http://localhost:8801/sendMessage 发送消息
8801消息生产者控制台信息:
8802消费者控制台信息:
依照8802,再创建一个消息消费者8803 cloud-stream-rabbitmq-consumer8803
启动:
RabbitMQ
Eureka 7001、7002 服务注册
8801 消息生产
8802 消息消费
8803 消息消费
1.重复消费问题
2.消息持久化问题
目前是8802/8803同时都收到了消息,存在重复消费的问题
如何解决:分组和持久化属性group
生产实际案例:
比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们需要避免这种情况。
这时我们就可以使用Stream中的消息分组来解决
注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。不同组是可以进行全面消费的(重复消费),同一个组内有竞争关系,只有其中一个可以消费。
8802与8803处于不同的组:
重复消费导致原因:微服务没有分组的话,默认每一微服务的组都不一样,组流水号都不一样,每一个组都会把消息消费一次,导致重复消费。
原理:微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内有竞争关系,只有其中一个可以消费。
group: atguiguA、atguiguB
8802修改yml:
8803修改yml:
重启8802、8803服务后:
此时,8001发送消息时,因为8002、8003处于不同的组,所以都会收到消息。
group: atguiguA
修改配置文件:
测试,此时8001发送消息:
8002收到的消息:
8003收到的消息:
结论:同一个组的多个微服务实例,每次只会有一个实例拿到消息。
通过上述内容,解决了重复消费的问题,再看看持久化。
停止8802/8803并去除8002的分组group: atguiguA,8803的分组group: atguiguA 没有去掉。
8801先发送4条消息到rabbitmq:
先启动8802,无分组属性配置,后台没有打印出来消息:
结果:没有!
发生了消息丢失!
总结:自定义组能够持久化,默认组不能持久化。
为什么会出现这个技术?需要解决哪些问题?
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或者错误都会引起整个请求最后的失败。
是什么:
SpringCloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持zipkin。
SpringCloud 从F版起已经不需要自己构建Zipkin Server了,只需要调用jar包即可。
下载 zipkin-server-2.12.9-exec.jar:
链接:https://pan.baidu.com/s/12f4EfxOHUhPbVb_-kW8zVQ
提取码:yyds
下载完成后,运行jar:
java -jar zipkin-server-2.12.9-exec.jar
http://localhost:9411/zipkin
完整的调用链路:表示一条请求链路,一条链路通过TraceId唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来。
一条链路通过Trace id唯一标识,Span 标识发起的请求信息,各span通过parent id关联起来。
整个链路的依赖关系如下:
名词解释:
Trace: 类似于树结构的Span集合,表示一条调用链路,存在唯一标识。
span: 表示调用链路来源,通俗的理解span就是一次请求信息。
pom.xml ,添加如下依赖:
application.yml,添加如下配置:
pom.xml添加如下依赖:
application.yml 修改:
业务类 OrderController修改,添加以下代码:
依次启动eureka7001、7002,8001,80,然后80调用8001测试:http://localhost/consumer/payment/zipkin
打开 http://localhost:9411/zipkin ,出现以下界面:
依赖关系:
官网:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
主要功能:
服务限流降级:默认支持 WebServlet、WebFlux、OpenFeign、RestTemplate、Spring Cloud Gateway、Zuul、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
引入依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.7.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
怎么玩:
Sentinel: 阿里巴巴开源产品,把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Nacos: 阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
RocketMQ: Apache RocketMQ 基于Java的高性能、高吞吐量的分布式消息和流计算平台。
Dubbo: Apache Dubbo 是一款高性能的RPC框架。
Seata: 阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
Alibaba Cloud OSS : 阿里云对象存储服务 (Object Storage Service,简称OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务,可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,支持周期性的任务与固定时间点触发任务。
Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
官网:https://spring.io/projects/spring-cloud-alibaba#overview
英文:
1. https://github.com/alibaba/spring-cloud-alibaba
2. https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
中文:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos: Dynamic Naming and Configuration Service
Nacos 就是注册中心 + 配置中心的组合,Nacos 等价于 Eureka + Config + Bus
替代 Eureka 做服务注册中心,替代 Config 做服务配置中心
https://github.com/alibaba/nacos
https://nacos.io/zh-cn/
官网文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
服务注册与发现框架 | CAP模型 | 控制台管理 | 社区活跃度 |
---|---|---|---|
Eureka | AP | 支持 | 低(2.x 闭源) |
Zookeeper | CP | 不支持 | 中 |
Consul | CP | 支持 | 高 |
Nacos | AP | 支持 | 高 |
据说Nacos在阿里巴巴内部有超过10万的实例运行,已经过了类似双十一等各种大型流量的考验。
本地Java+Maven环境已ok
先从官网下载Nacos
文档1
文档2
新建Module cloudalibaba-provider-payment9001
父pom.xml (SpringCloud2020 的pom):
本模块pom:
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloudalibaba-provider-payment9001artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置nacos地址
management:
endpoints:
web:
exposure:
include: '*'
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class, args);
}
}
package com.atguigu.springcloud.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id) {
return "nacos registry, serverPort: " + serverPort + "\t id: " + id;
}
}
访问 http://localhost:9001/payment/nacos/1
nacos控制台:
nacos服务注册中心 + 服务提供者都ok了
为了下一章节演示nacos的负载均衡,参照9001新建9002:
新建 cloudalibaba-provider-payment9002,或者取巧不想新建重复体力劳动,通过虚拟映射直接拷贝虚拟端口映射:
启动后:
新建Module cloudalibaba-consumer-nacos-order83
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloudalibaba-consumer-nacos-order83artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.examplegroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
# 消费者将要去访问的微服务名称(注册进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain83.class, args);
}
}
用到了Ribbon,需要写一个配置类:
package com.atguigu.springcloud.alibaba.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 ApplicationContextConfig {
@Bean
@LoadBalanced//restTemplate只管调用,本身不具备负载均衡功能,要用@LoadBalanced注解赋予restTemplate负载均衡的功能
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
controller:
package com.atguigu.springcloud.alibaba.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Integer id) {
return restTemplate.getForObject(serverURL + "/payment/nacos/" + id ,String.class);
}
}
启动Nacos,payment9001、9002,order83
9001、9002 轮询负载
Nacos支持AP和CP模式的切换:
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
何时选择何种模式:一般来说,如果不需要存储服务级别的信息且服务实例是通过Nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如spring cloud和Dubbo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么CP是必须的,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
切换模式命令:
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP
新建Module cloudalibaba-config-nacos-client3377
<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">
<parent>
<artifactId>cloud2020artifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloudalibaba-config-nacos-client3377artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
配置两个yml文件:Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application。
bootstrap.yml:
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务注册中心地址
config:
server-addr: localhost:8848 # nacos作为配置中心地址
file-extension: yaml # 指定yaml格式的配置
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yml
application.yml:
spring:
profiles:
active: dev # 表示开发环境
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377 {
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}
controller:
package com.atguigu.springcloud.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope //支持Nacos的动态刷新功能 通过springcloud原生注解@RefreshScope实现配置自动更新
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
Nacos中的匹配规则:
理论:
Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则
官网:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
实操:
设置DataId,公式 ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
根据公式得到的DataId:nacos-config-client-dev.yaml(注意是yaml,不是yml,否则启动服务会报错)
启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件
运行cloud-config-nacos-client3377的主启动类
调用接口查看配置信息: http://localhost:3377/config/info
修改Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新。
问题一:
实际开发中,通常一个系统会准备
dev开发环境
test测试环境
prod生产环境
如何保证指定环境启动时服务能够正确读取到Nacos上相应的配置文件呢?
问题二:
一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…
那么怎么对这些微服务配置进行管理呢?
命名空间:
Namespace + Group + Data ID 三者关系?为什么要这么设计?
是什么:
类似 Java里面的package名 + 类名,最外层namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
三者情况:
默认情况:
Namespace = public,Group = DEFAULT_GROUP,默认Cluster是DEFAULT
Nacos默认的命名空间是public,Namespace主要来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个NameSpace,不同的NameSpace之间是隔离的。
Group默认是DEFAULT_GROUP,Group可以把多个不同的微服务划分到同一个组里去。
Service就是微服务,一个service可以包含多个Cluster(集群),Nacos默认的Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。比方说容灾,将service微服务分别部署在杭州和广州机房,这时就可以给杭州机房的service微服务起一个集群名(HZ),给广州机房的service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后就是Instance,就是微服务的实例。
指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
默认空间+默认分组+新建dev和test两个DataID:
同一个group的俩个DataID:
通过spring.profile.active属性就能进行多环境下配置文件的读取:
测试:
通过Group实现环境区分,新建相同名字不同Group的配置文件:
测试:
在bootstrap.yml中将group改为DEV_GROUP后:
新建dev/test的NameSpace
回到服务管理——服务列表查看:
https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
https://nacos.io/zh-cn/docs/deployment.html
官网集群架构图:
官网架构图翻译:
说明:
默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。
Nacos支持三种部署模式
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=nacos_devtest
db.password=youdontknow
再以单机模式启动nacos,nacos所有写嵌入式数据库的数据都写到了mysql
Nacos默认自带的是嵌入式数据库derby,derby到mysql切换配置步骤:
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=nacos_devtest
db.password=youdontknow
注:nacos1.1.4不支持mysql8,因为里面的驱动是mysql5的驱动,所以需要下载源码更换mysql驱动重新编译。
重新编译教程:https://blog.csdn.net/m0_46157986/article/details/111084850
预计需要,1个Nginx+3个nacos注册中心+1个mysql
Nacos下载Linux版本:
确保在环境中安装使用:
- 64bit OS Linux/Unix/Mac,推荐使用Linux
- 64bit JDK 1.8+
- Maven 3.2.x+
- 3个或3个以上Nacos节点才能构成集群
下载nacos-server-1.1.4.tar.gz 解压即可
SQL脚本在哪:
在Linux的mysql中,执行SQL脚本。
梳理出3台nacos集群的不同服务端口号:
3333 4444 5555
复制出cluster.conf
内容:
这个IP不一定是127.0.0.1,必须是Linux命令hostname -I 能识别的ip
平时单机版的nacos启动,都是./startup.sh即可,但是集群启动,我们希望可以类似其他的shell命令,传递不同的端口号启动不同的nacos实例。
命令:./startup.sh -p 3333 表示启动端口号为3333的nacos服务器实例,和上一步cluster.conf配置的一致。
脚本修改内容
修改前:
./startup.sh -p 3333
nginx安装:https://www.cnblogs.com/zym-k/p/14967540.html
修改nginx的配置文件
按照指定启动:
/usr/local/nginx/sbin目录下启动nginx
./nginx -c /usr/local/nginx/conf/nginx.conf
保证mysql、nginx启动,启动nacos3333、4444、5555
启动时可以打开nacos的日志,查看是否启动报错:tail -f /mynacos/logs
此处若返回的不是3,先关闭虚拟机,将内存增大为4G。
测试通过nginx访问nacos: http://192.168.234.135:1111/nacos/#/login
注意需要关闭linux防火墙或者开启1111端口!
Linux防火墙相关命令:https://www.cnblogs.com/niuben/p/13168708.html
集群信息: