在微服务架构的系统中, 我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来, 由于该主题中产生的消息会被所有实例监听和消费, 所以我们称它为消息总线。
在总线上的各个实例都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息, 例如配置信息的变更或者其他一些管理操作等。
通过使用 Spring Cloud Bus 可以非常容易地搭建起消息总线, 同时实现了一些消息总线中的常用功能。
比如,配合Spring Cloud Config 实现微服务应用配置信息的动态更新等。
消息代理 (Message Broker) 是一种消息验证、 传输、 路由的架构模式。 它在应用程序之间起到通信调度并最小化应用之间的依赖的作用, 使得应用程序可以高效地解耦通信过程。
消息代理是一个中间件产品, 它的核心是一个消息的路由程序, 用来实现接收和分发消息, 并根据设定好的消息处理流来转发给正确的应用。
它包括独立的通信和消息传递协议, 能够实现组织内部和组织间的网络通信。 设计代理的目的就是为了能够从应用程序中传入消息, 并执行一些特别的操作, 下面这些是在企业应用中, 我们经常需要使用消息代
理的场景:
将消息路由到一个或多个目的地
消息转化为其他的表现方式
执行消息的聚集、 消息的分解, 并将结果发送到它们的目的地, 然后重新组合响应返回给消息用户
调用Web服务来检索数据
响应事件或错误
使用发布-订阅模式来提供内容或基于主题的消息路由
当前版本的Spring Cloud Bus仅支待两款中间件产品: RabbitMQ和Kafka。
AMQP是Advanced Message Queuing Protocol的简称,它是一个面向消息中间件的开放式标准应用层协议。AMQP定义了这些特性:
消息方向
消息队列
消息路由(包括:点到点和发布-订阅模式)
可靠性
安全性
RabbitMQ就是以AMQP协议实现的一种中间件产品,它可以支持多种操作系统,多种编程语言,几乎可以覆盖所有主流的企业级技术平台。
安装略
新建一个Spring Boot工程,命名为:“rabbitmq-hello”。
在pom.xml中引入如下依赖内容,其中spring-boot-starter-amqp用于支持RabbitMQ。
org.springframework.boot
spring-boot-starter-amqp
在application.properties中配置关于RabbitMQ的连接和用户信息
spring.application.name=rabbitmq-hello
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=spring
spring.rabbitmq.password=123456
创建消息生产者Sender。
通过注入AmqpTemplate接口的实例来实现消息的发送,AmqpTemplate接口定义了一套针对AMQP协议的基础操作。
在Spring Boot中会根据配置来注入其具体实现。在该生产者,我们会产生一个字符串,并发送到名为hello的队列中。
@Component
public class Sender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send() {
String context = "hello " + new Date();
System.out.println("Sender : " + context);
this.rabbitTemplate.convertAndSend("hello", context);
}
}
创建消息消费者Receiver。
通过@RabbitListener注解定义该类对hello队列的监听,并用@RabbitHandler注解来指定对消息的处理方法。
所以,该消费者实现了对hello队列的消费,消费操作为输出消息的字符串内容。
@Component
@RabbitListener(queues = "hello")
public class Receiver {
@RabbitHandler
public void process(String hello) {
System.out.println("Receiver : " + hello);
}
}
创建RabbitMQ的配置类RabbitConfig,用来配置队列、交换器、路由等高级信息。
@Configuration
public class RabbitConfig {
@Bean
public Queue helloQueue() {
return new Queue("hello");
}
}
创建应用主类:
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
创建单元测试类,用来调用消息生产:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = HelloApplication.class)
public class HelloApplicationTests {
@Autowired
private Sender sender;
@Test
public void hello() throws Exception {
sender.send();
}
}
尝试运行
准备工作:不做新的应用,用到上一章中已经实现的关于Spring Cloud Config的几个工程。
config-repo:定义在Git仓库中的一个目录,其中存储了多环境配置文件,配置文件中有一个from参数。
config-server-eureka:配置了Git仓库,并注册到了Eureka的服务端。
config-client-eureka:通过Eureka发现Config Server的客户端,用来访问配置服务器以获取配置信息。该应用中提供了一个/from接口,它会获取properties中的from属性返回。
扩展config-client-eureka应用
修改pom.xml增加spring-cloud-starter-bus-amqp模块(注意spring-boot-starter-actuator模块也是必须的)。
org.springframework.cloud
spring-cloud-starter-bus-amqp
在配置文件中增加关于RabbitMQ的连接和用户信息
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=springcloud
spring.rabbitmq.password=123456
启动config-server-eureka,再启动两个config-client-eureka(分别在不同的端口上,比如7002、7003),我们可以在config-client-eureka中的控制台中看到如下内容,在启动时候,客户端程序多了一个/bus/refresh请求。
先访问两个config-client-eureka的/from请求,会返回当前properties中的from属性。
接着,我们修改properties中的from属性值,并发送POST请求到其中的一个/bus/refresh。
最后,再分别访问启动的两个config-client-eureka的/from请求,此时这两个请求都会返回最新的properties中的from属性。
到这里已经能够通过Spring Cloud Bus来实时更新总线上的属性配置了。
通过使用Spring Cloud Bus与Spring Cloud Config的整合,并以RabbitMQ作为消息代理,实现了应用配置的动态更新。
有些特殊场景下(比如:灰度发布),我们希望可以刷新微服务中某个具体实例的配置。
/bus/refresh接口还提供了destination参数,用来定位具体要刷新的应用程序。
比如,我们可以请求/bus/refresh?destination=customers:9000,此时总线上的各应用实例会根据destination属性的值来判断是否为自己的实例名,若符合才进行配置刷新,若不符合就忽略该消息。
destination参数除了可以定位具体的实例之外,还可以用来定位具体的服务。定位服务的原理是通过使用Spring的PathMatecher(路径匹配)来实现,
比如:/bus/refresh?destination=customers:**,该请求会触发customers服务的所有实例进行刷新。
Spring Cloud Bus的/bus/refresh接口提供了针对服务和实例进行配置更新的参数,那么我们的架构也相应的可以做出一些调整。
在之前的架构中,服务的配置更新需要通过向具体服务中的某个实例发送请求,再触发对整个服务集群的配置更新。
这样指定的应用实例就会不同于集群中的其他应用实例。
我们要尽可能的让服务集群中的各个节点是对等的。
做了这些改动:
1、在Config Server中也引入Spring Cloud Bus,将配置服务端也加入到消息总线中来。
2、/bus/refresh请求不再发送到具体服务实例上,而是发送给Config Server,并通过destination参数来指定需要更新配置的服务或实例。
通过上面的改动,服务实例就不需要再承担触发配置更新的职责。同时,对于Git的触发等配置都只需要针对Config Server即可,从而简化了集群上的一些维护工作。
Kafka是一个由LinkedIn开发的分布式消息系统,它于2011年初开源,现在由著名的Apache基金会维护与开发。Kafka使用Scala实现,被用作LinkedIn的活动流和运营数据处理的管道,现在也被诸多互联网企业广泛地用作为数据流管道和消息系统。
Kafka是基于消息发布/订阅模式实现的消息系统,其主要设计目标如下:
消息持久化:以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间复杂度的访问性能。
高吞吐:在廉价的商用机器上也能支持单机每秒100K条以上的吞吐量
分布式:支持消息分区以及分布式消费,并保证分区内的消息顺序
跨平台:支持不同技术平台的客户端(如:Java、PHP、Python等)
实时性:支持实时数据处理和离线数据处理
伸缩性:支持水平扩展
Kafka中涉及的一些基本概念:
Broker:Kafka集群包含一个或多个服务器,这些服务器被称为Broker。
Topic:逻辑上同Rabbit的Queue队列相似,每条发布到Kafka集群的消息都必须有一个Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个Broker上,但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)
Partition:Partition是物理概念上的分区,为了提供系统吞吐率,在物理上每个Topic会分成一个或多个Partition,每个Partition对应一个文件夹(存储对应分区的消息内容和索引文件)。
Producer:消息生产者,负责生产消息并发送到Kafka Broker。
Consumer:消息消费者,向Kafka Broker读取消息并处理的客户端。
Consumer Group:每个Consumer属于一个特定的组(可为每个Consumer指定属于一个组,若不指定则属于默认组),组可以用来实现一条消息被组内多个成员消费等功能。
环境安装
我们需要从官网上下载安装介质。
解压,Kafka的设计中依赖了ZooKeeper,根据实际的系统来设置环境变量。
启动ZooKeeper和Kafka来进行消息的生产和消费。
启动测试
启动ZooKeeper,执行命令:zookeeper-server-start config/zookeeper.properties
启动Kafka,执行命令:kafka-server-start config/server.properties
创建Topic,执行命令:kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test,通过该命令,创建一个名为“test”的Topic,该Topic包含一个分区一个Replica。在创建完成后,可以使用kafka-topics --list --zookeeper localhost:2181命令来查看当前的Topic。
创建消息生产者,执行命令:kafka-console-producer --broker-list localhost:9092 --topic test。
创建消息消费者,执行命令:kafka-console-consumer --zookeeper localhost:2181 --topic test --from-beginning。
要使用Kafka来实现消息总线时,只需要把spring-cloud-starter-bus-amqp替换成spring-cloud-starter-bus-kafka模块,在pom.xml的dependenies节点中进行修改
org.springframework.cloud
spring-cloud-starter-bus-kafka
如果在启动Kafka时均采用了默认配置,那么不需要再做任何其他配置就能在本地实现从RabbitMQ到Kafka的切换。
实际应用中,Kafka和ZooKeeper一般都会独立部署,所以在应用中都需要来为Kafka和ZooKeeper配置一些连接信息等。
具体见第十章