官网地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/
(1)目前微服务存在的问题
微服务意味着要将单体应用中的业务拆分成一个个子服务,因此系统中会出现大量的服务。所以一套集中式的、动态的配置管理设施是必不可少的,如果不进行集中配置,我们会拥有数量过于庞大的yml配置文件。 SpringCloud提供了ConfigServer来解决这个问题。
(2)是什么?
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置,我们可以把共同的配置移到配置中心,这样每个微服务就只需要保留自己私有的配置就行了。
(3)怎么玩?
SpringCloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
(4)能干嘛?
由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式
(1)在你的git上建新建一个仓库并clone到本地
(2)新建Module模块cloud-config-center-3344它即为Cloud的配置中心模块cloudConfig Center
(3)改pom
多了一个config-server,其余照旧
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-clientartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-coreartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jersey.contribsgroupId>
<artifactId>jersey-apache-client4artifactId>
exclusion>
exclusions>
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>
(4)写yml
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: [email protected]:zzyybs/springcloud-config.git #GitHub上面的git仓库名字
####搜索目录
search-paths:
- springcloud-config
####读取分支
label: master
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
uri:哪个地址
search-paths:那个仓库
label:哪个分支
(5)主启动
加上注解:@EnableConfigServer
(6)修改windows下的hosts文件,增加映射,模拟运维工程师
127.0.0.1 config-3344.com
(7)测试通过Config微服务是否可以从GitHub上获取配置内容
启动7001,3344,浏览器输入http://config-3344.com:3344/master/config-dev.yml
注意:这里的如果连接git失败,可能是需要在配置文件中加上你的用户名密码之类的,自己百度一下错误就行了。
总结:
下图是官网推荐的集中访问配置文件的方式,在这个案例中我们使用的是第三种
(1)新建cloud-config-client-3355
(2)改pom
客户端的config后面就没有server了
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-clientartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-coreartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jersey.contribsgroupId>
<artifactId>jersey-apache-client4artifactId>
exclusion>
exclusions>
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>
(3)写yml,bootstrap.yml
applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context
的父上下文。初始化的时候,Bootstrap Context
负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment
。
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,
因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址k
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
(4)3355主启动类
注意加上@EnableEurekaClient
(5)业务类
@RestController
public class ConfigClientController
{
@Value("${config.info}")//获取配置中心中的数据
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo()
{
return configInfo;
}
}
(6)测试
启动7001,3344,3355
浏览器输入:http://localhost:3355/configInfo
测试成功!
成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息
但是问题又来了:
模拟Linux运维修改GitHub上的配置文件内容做调整,刷新3344,发现ConfigServer配置中心立刻响应,刷新3355,发现ConfigClient客户端没有任何响应,3355没有变化除非自己重启或者重新加载,难到每次运维修改配置文件,客户端都需要重启??噩梦
接下来,我们修改3355,使其能不用重启就能更新配置
(1)引入监控模块
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
(2)修改YML,暴露监控端口
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
(3)@RefreshScope业务类Controller修改
(4)注意,要刷新的话,必须发送post请求来手动刷新
需要运维人员发送Post请求刷新3355
然后我们再刷新3355,发现已经变成了最新的配置,那么这样我们就达到了手动刷新的效果~
在我们的上一章中,我们只达到了手动刷新,但是如果有多个微服务,我们可不可以广播通知呢?或者如果我们想定点通知,该怎么办呢?这个时候就要用到我们的bus总线了,bus总线是对config的一个加强,我们需要实现对配置的自动刷新
(1)是什么?
Bus支持两种消息代理:RabbitMQ 和 Kafka
(2)能干嘛?
Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。
ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置,从而实现自动刷新配置。
(3)如何理解总线?
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
使用的是centos,linux环境,详情见我的rabbitmq学习笔记
https://blog.csdn.net/qq_28356977/article/details/126313264?spm=1001.2014.3001.5501
我们启动rabbitmq,并且能够访问到rabbitmq的web监管页面就可以进行下一步了~
(1)首先模仿3355,新建一个3366来模拟多个服务
(2)思想一:利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
(3)思想二:利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
(4)两种思想比较
显然第二种更合适,下面是第一种的缺点
所以我们使用第二种设计思想
(1)给config-server3344配置中心服务端添加消息总线支持
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
(2)修改3344的yml
注意,rabbitmq是在spring:下的,注意位置!
#rabbitmq相关配置
rabbitmq:
host: 你的ip
port: 5672
username:
password:
##rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
(3)给cloud-config-client-3355客户端添加消息总线支持
还是这两个依赖~
!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
(4)修改3355的yml
rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host:
port: 5672
username:
password:
3366和3355是一样的,就不演示了~
(5)接下来我们模拟运维工程师,修改版本号,进行测试
server3344:http://config-3344.com:3344/config-dev.yml
client3355:http://localhost:3355/configInfo
client3366:http://localhost:3366/configInfo
在上一章中,我们发送一次post,只能修改一个客户端的配置,但这次,我们一次发送,处处修改,测试成功~
如果我只想通知3355,不想通知3366呢?
一句话:指定具体某一个实例生效而不是全部
公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
测试:
在我们上面的例子中,如果我们只通知3355那么我们的curl应该这么写:
curl -X POST “http://localhost:3344/actuator/bus-refresh/config-client:3355”
by the way:config-client是我们yml中配置的微服务名称
官网:https://spring.io/projects/spring-cloud-stream
API:https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/
a)是什么?
目前的项目中,主要有:ActiveMQ,rabbitmq,rocketmq,kafka四种消息中间件,并且一个项目如果存在多种,那么我们就需要一种新技术,让我们不再关注mq的细节,我们只需要一种适配绑定的方式,自动的给我们在各种mq中切换。
一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
应用程序通过 消费者或者 生产者来与 Spring Cloud Stream中binder对象交互。
通过我们配置来binding(绑定) ,而 Spring Cloud Stream 的 binder对象负责与消息中间件交互。
所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
目前仅支持RabbitMQ、Kafka。
b)设计思想
Stream中的消息通信方式遵循了发布-订阅模式,Topic主题进行广播,在RabbitMQ就是Exchange,在Kakfa中就是Topic。
先来回顾一下最原始的mq
那么当我们的项目中,有多个中间件时,stream就来了
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,
由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性
通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
在开始下面的案例之前,确保自己rabbitmq的环境已经ok~
(1)新建cloud-stream-rabbitmq-provider8801
新东西spring-cloud-starter-stream-rabbit
(2)改pom
<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>
<exclusions>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-clientartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-coreartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jersey.contribsgroupId>
<artifactId>jersey-apache-client4artifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
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>
(3)写yml
这里我使用的是linux下的rabbitmq,不同的环境配置可能会有少许不同,至于为什么我配置类两个rabbitmq的连接,因为不这么配置运行会抛异常,但不影响结果
server:
port: 8801
spring:
application:
name: cloud-stream-provider
rabbitmq:
host:
port: 5672
username:
password:
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host:
port: 5672
username:
password:
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
(4)主启动
(5)业务类
业务类的作用,每次发送一个对应的请求,就发送一个序列号的消息
service层
public interface IMessageService {
public String send() ;
}
他的实现类,注意,这个service是和我们的mq打交道的,所以不需要使用@Service
@EnableBinding(Source.class)//定义消息的推送管道,就是我们上面流程图上的Source
public class IMessageServiceImpl implements IMessageService {
@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;
}
}
controller
@RestController
public class SendMessageController{
@Resource
private IMessageService iMessageService;
@GetMapping("/sendMessage")
public String sendMessage(){
return iMessageService.send();
}
}
(6)测试
启动7001,启动rabbitmq,启动8801
发送请求,我们发现控制台输出了我们发送到流水号
并且在web监管界面上,找到了我们配置文件中destination:定义的studyExchange 这个交换机
在上面的生产中,我们没有使用任何rabbitmq的API,就实现了生产者向mq中发消息,接下来我们来看看消费者
(1)新建模块cloud-stream-rabbitmq-consumer8802
(2)改pom
<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>
<exclusions>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-clientartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-coreartifactId>
exclusion>
<exclusion>
<groupId>com.sun.jersey.contribsgroupId>
<artifactId>jersey-apache-client4artifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
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>
(3)写yml
区别就在与bindings后面的output变成了input
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
rabbitmq:
host:
port: 5672
username:
password:
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host:
port: 5672
username:
password:
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
(4)主启动
(5)业务类
@Component
@EnableBinding(Sink.class)//注意是stream.messaging下的包,别导错了,这个sink和我们之前的source是差不多的,一个生产者一个消费者
public class ReceiveMessageListener {
@Value("${server.port}")
private String port;
@StreamListener(Sink.INPUT)//表示消费者
//发送的是string,所以接受也用string
public void input(Message<String> message){
//发送用withPayload,接受用getPayload
System.out.println("消费者1号----->接受到的消息:"+message.getPayload()+"\t"+"port:"+port);
}
}
(6)测试
我们接着上面,启动8802消费者,接受8801发送到消息
我们发现8802成功收到了8801发送的消息,测试成功~
(1)我们再新建一个消费者cloud-stream-rabbitmq-consumer8803
(2)测试
运行后,发现一个问题
1、重复消费:消息同时被两个消费者消费了
我们需要解决这个问题,下图是一个实际的例子
(3)解决重复消费
原理:微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
我们先来看看怎么分组
修改yml即可,其实很简单
我们给8802和8803分成了两个组,再打开我们的web界面,因为是不同组,所以还是可以重复消费
当我们把组名改的一样是,8802和8803就变成了竞争关系,消息变成了轮询消费
添加了分组以后,其实就实现了消息的持久化,下面我们做一个测试
原因:8802启动后相当于订阅了一个新的队列所以导致没有消息、而8803则是订阅了之前的那个队列、而之前的那个队列没有被删除所以是有消息的
官网:https://spring.io/projects/spring-cloud-sleuth
(1)要解决的问题
当链路变多的时候,我们就需要监控链路了,Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin
SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可
下载:https://repo1.maven.org/maven2/io/zipkin/java/zipkin-server/
我们只需要在这个jar包的目录中,cmd输入指令java -jar zipkin-server-版本号-exec.jar即安装完成
接下来我们在浏览器中输入:http://localhost:9411/zipkin/,即可浏览web界面
下面是运行流程以及一些术语的解释
首先,先修改服务提供者的pom,这里以我们最早的8001为例
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
@GetMapping("/payment/zipkin")
public String paymentZipkin()
{
return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
}
接下来,修改消费者80,pom和yml都是一样的,加上一个controller方法
// ====================> zipkin+sleuth
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin()
{
String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
return result;
}
接下来以此启动7001,8001,80,然后80调用8001几次测试下,然后就可以在web界面查看调用情况啦
我们点击记录,就能查看到调用关系