Spring Cloud是基于对Netflix开源组件进一步封装的一套云应用开发工具,可以用来开发各种微服务应用,它包含很多组件(或子项目),表12-1列出了一些主要组件及其功能说明。

 

 

在第8章的实例工程中已经使用了其中的配置管理、发现服务、监控服务、动态路由、断路器、负载均衡等功能。本章将从实现的角度探索配置管理、发现服务和负载均衡服务等的实现原理。

1配置管理实现原理

在第8章的实例中,我们知道,配置管理的在线更新功能使用事件总线,即spring-cloud-bus来发布状态变化,并且使用分布式消息来发布更新事件,而分布式消息最终使用了RabbitMQ来实现消息收发。

1.1在线更新流程

使用配置管理,实现在线更新一般遵循下列步骤:

1)更新Git仓库的配置文件。

2)以POST指令触发更新请求

3)配置管理服务器从Git仓库中读取配置文件,并将配置文件分发给各个客户端,同时在RabbitMQ中发布一个更新消息。

4)客户端订阅RabbitMQ消息,收到消息后执行更新。

在使用配置管理的演示实例中,使用如下POST指令来触发在线更新:curl -x POST http:l/localhost :8888/ bus/refresh

接收这个更新指令的实现方法如代码清单12-1所示,其中的publish将会发布一个更新事件,调用RabbitMQ进行消息发布,然后由客户端收到消息后执行更新。代码中定义了请求更新的链接refresh,并可使用destination来指定更新目标。

 

1.2更新消息的分发原理

配置管理服务器中的消息分发是从spring-cloud-bus 中调用spring-cloud-stream组件来实现的,而spring-cloud-stream使用RabbitMQ实现了分布式消息的分发。

RabbitMQ的消息服务一般需要创建一个交换机Exchange和一个队列Queue,然后将交换机和队列进行绑定。而在设计配置服务器时并没有做这方面的工作,所做的工作仅仅是配置引用spring-cloud-bus 的依赖和设置连接RabbitMQ服务器的参数而已。这个工作其实已经由spring-cloud-stream帮我们实现了。

从RabbitMessageChannelBinder 的源代码中可以看到这部分的实现原理,代码清单12-2是一个消息发布方的队列绑定的实现。其中exchangeName是一个交换机的名字,baseQueueName是一个队列的名字,并且从代码中也可以看出它使用了TopicExchange交换机,这是RabbitMQ中4种交换机(Fanout、Direct、Topic、Header)的其中一种,并且也可以看出代码中使用setRoutingKey将交换机和队列做了绑定。

 

现在我们更加明白,为什么使用Spring Boot可以那么简单,就是因为一些复杂的配置和方法都已经由Spring Boot 及其所调用的一些组件实现了。至于在使用RabbitMQ中进行消息发布的实现,最终是由RabbitTemplate执行doSend,将消息发布到RabbitMQ服务器上,如代码清单12-3所示。

 

使用配置管理服务的客户端都订阅了RabbitMQ服务器的消息,当收到更新消息时,即从配置管理服务器中取得更新文件,然后在本地上执行更新配置的流程。

有关消息的发布和订阅的实现方法,最后通过一个简单的实例,使用spring-cloud-stream,更加形象地说明这种分布式消息的发布和接收的原理。

2发现服务源代码剖析

使用发现服务时,只要简单地通过注解@ EnableEurekaServer来标注一个应用为发现服务器,通过注解@EnableDiscoveryClient来标注一个应用为发现服务的客户端,服务器就会实现服务注册的功能,客户端将会从服务器中取得已经注册的可用服务列表。

2.1服务端的服务注册功能

服务注册是发现服务的一个主要功能,可以从注解@EnableEurekaServer的定义中顺藤摸瓜地找到其实现的源代码,如代码清单12-4所示,其中@Import将导入一个发现服务的配置:EurekaServerConfiguration。

 

通过EurekaServerConfiguration,又引入了一些配置,如增加了监控器和过滤器的配置等,其中一个InstanceRegistry的配置将实现对客户端的注册,如代码清单12-5所示。

 

上面的InstanceRegistry调用了超类AbstractInstanceRegistry的register对客户端进行注册,然后将在线的客户端存入注册队列recentRegisteredQueue中,如代码清单12-6所示。

 

客户端在发现服务器中注册之后,就可以在发现服务器的控制台上,查看在线的客户端列表。这里有一个缺陷,如果客户端关闭了,在发现服务器的控制台中,还能查到这个客户端,只有重启发现服务器,才能更新这个客户端列表。

2.2客户端注册和提取服务列表

客户端除了自身在发现服务器上注册之外,它还要从服务器中取得已经注册的其他客户端,以得到一个可用的服务列表(其他注册的客户端)。这部分的核心源代码可以在com.netflix.discovery.DiscoveryClient中找到。

客户端自身注册的实现方法如代码清单12-7所示。这里,主要是将客户端的名称、IP地址和端口等信息通过一个instanceInfo对象发给发现服务器进行注册。

 

客户端执行注册使用计划任务的方式来实现,而客户端从发现服务器中更新其他在线的客户端列表,也使用了一个定时任务来管理。代码清单12-8使用一个定时任务TimerTask定时从发现服务器中取得其他在线的客户端列表,以备使用。

 

单纯的发现服务,并不能看出它有多大的用途,它只有与动态路由、负载均衡和监控服务等一起使用,才能发挥其强大的功能。

3负载均衡源代码剖析

当一个应用启用发现服务的功能之后,会默认启用Ribbon的负载均衡服务。Ribbon通过发现服务获取在线的客户端,为具有多个实例的客户端建立起负载均衡实例列表,然后通过一定的负载均衡算法,实现负载均衡的管理机制。

如代码清单12-9所示,Ribbon 默认结合使用Eureka 发现服务,启用负载均衡管理机制。当没有配置“ribbon.eureka.enabled”参数时,它默认被设定为true。

 

看看负载均衡服务是如何进行初始化的,就更清楚它的实现原理了。代码清单12-10是BaseLoadBalancer 的部分源代码。这里,程序加载了一些初始配置,如可用的负载均衡服务实例列表、监控计数和服务状态监听等,其中一个重要的设置,即设定默认的负载均衡规则RoundRobinRule,这是一个使用简单轮询算法的负载均衡规则。一个负载均衡服务的实现,就是通过一定的负载均衡算法,从可用的服务实例列表中,为请求者提供一个可用的服务。

 

再来看看RoundRobinRule的实现代码,如代码清单12-11所示。从中可以看出,它使用一个循环,从可用的服务列表中,按顺序选择一个可用的服务。其中,一个选择服务的请求不能超过10次无效的尝试,这仅仅是一个循环语句的安全设计而已,并不会影响一次选择查询。

从整体上来说,使用哪种负载均衡算法,对于整个负载均衡服务来说,影响并不是很大。除了RoundRobinRule,Ribbon还提供了其他一些负载均衡规则,如加权响应时间规则WeightedResponseTimeRule、区域感知规则ZoneAvoidanceRule、随机规则RandomRule等。

 

例如在第9章的演示中,当把其中的data服务配置为两个或两个以上的运行实例时,就会启动Ribbon的负载均衡机制,用来管理其他服务对data服务的访问。我们只是使用了默认的简单轮询负载均衡规则,即第一次调用将访问第一个服务实例,第二次调用将访问第二个服务实例,以此类推,当调用到服务列表的最后一个服务后再从头来过。

4分布式消息实现原理演示

使用RabbitMQ实现分布式消息分发,在分布式系统中具有很大的用途,为各个应用之间传递消息和数据提供了很大的方便,并且其松散耦合的结构和异步处理的机制不会影响系统的性能。

使用spring-cloud-stream可以非常简单地使用RabbitMQ的异步消息,Spring Cloud的配置管理中的分布式消息分发也是通过调用spring-cloud-stream组件来实现的。下面将创建一个消息生产者和一个消息消费者来演示消息分发的实现原理。

4.1 消息生产者

消息生产者的实现如代码清单12-12所示,这里主要创建了一个POST接口“/send”,以接收传入的Map对象作为参数,使用MessageChannel 的send方法,将消息发布到RabbitMQ的消息队列上。使用Map对象的目的,是保证消息生产者和消息消费者之间,可以使用相同的对象来存取消息的内容,这就要求双方必须事先约定Map对象的字段。

 

代码清单12-13是消息生产者这个工程的配置文件,其中stream配置的目的地为cloud-stream,客户端订阅时必须使用相同的目的地,注意这里绑定的方式是output,rabbitmq是连接消息服务器的一些参数配置。

 

4.2︰消息消费者

消息消费者的实现如代码清单12-14所示,这里从 SubscribableChannel(即 Sink.INPUT)订阅了通道的消息,它相当于一个***,当订阅的通道上有消息发布时,就将消息取回来,然后简单地在控制台上打印出来。这里使用的Map对象,跟消息生产者约定了两个使用字段,即 msg 和 name。

 

代码清单12-15是消息消费者工程的配置文件,它与消息生产者有相同的目的地,不同的是它的绑定方式是input,其中 rabbitmq的配置是相同的,只要连上RabbitMQ消息服务器即可。

 

这样,当两个工程都启动之后,输入下列POST指令,就可以将消息从消息生产者中发布出去。

curl -l -H "Content-type:application/json" -x POST -d' [ "msg" : "Hello" , " name" : "RabbitMQ"} ' http: l/localhost/ send

其中消息结构中的msg 和 name就是约定好的字段。这时,在消息消费者的控制台上可以看到接收到了如下信息:

Hello :RabbitMQ

从上面的演示可以看出,使用spring-cloud-stream来实现分布式消息的分发和接收都是非常简单的。上面实例工程的完整代码可以从GitHub中检出: https:/lgithub.coml/chenfromsz/spring-cloud-stream-demo.git.

5小结

本章分析了Spring Cloud一系列微服务中配置服务、发现服务和负载均衡服务的实现原理和部分核心源代码,并使用一个简单的实例,演示了使用分布式消息的简单实现方法,从中让我们更加清楚地认识到,Spring Boot及其一些相关的组件,已经尽量把一些可以实现和做到的功能,都帮我们实现了。所以,虽然使用Spring Boot 及其相关组件看起来非常简单,但实际上可以实现无比强大的功能,这就是Spring Boot 及其组件的神奇所在。