SpringCloud微服务技术栈.黑马跟学
Posted 心向阳光的天域
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud微服务技术栈.黑马跟学相关的知识,希望对你有一定的参考价值。
SpringCloud微服务技术栈.黑马跟学 十二
今日目标
服务异步通信-高级篇
消息队列在使用过程中,面临着很多实际问题需要思考:
1.消息可靠性
消息从发送,到消费者接收,会经理多个过程:
其中的每一步都可能导致消息丢失,常见的丢失原因包括:
- 发送时丢失:
- 生产者发送的消息未送达exchange
- 消息到达exchange后未到达queue
- MQ宕机,queue将消息丢失
- consumer接收到消息后未消费就宕机
针对这些问题,RabbitMQ分别给出了解决方案:
- 生产者确认机制
- mq持久化
- 消费者确认机制
- 失败重试机制
下面我们就通过案例来演示每一个步骤。
首先,导入课前资料提供的demo工程:
项目结构如下:
用docker启动即可
docker start mq
要创建一个队列起名simple.queue
然后在交换机中把amq.topic交换机,和上面创建的队列simple.queue绑定,我们手动配置
进入amq.topic交换机后,绑定队列
绑定后如图:
1.1.生产者消息确认
RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
返回结果有两种方式:
- publisher-confirm,发送者确认
- 消息成功投递到交换机,返回ack
- 消息未投递到交换机,返回nack
- publisher-return,发送者回执
- 消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。
注意:
1.1.1.修改配置
首先,修改publisher服务中的application.yml文件,添加下面的内容:
spring:
rabbitmq:
publisher-confirm-type: correlated
publisher-returns: true
template:
mandatory: true
说明:
publish-confirm-type
:开启publisher-confirm,这里支持两种类型:simple
:同步等待confirm结果,直到超时correlated
⭐:异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback
publish-returns
:开启publish-return功能,同样是基于callback机制,不过是定义ReturnCallbacktemplate.mandatory
:定义消息路由失败时的策略。true,则调用ReturnCallback;false:则直接丢弃消息
1.1.2.定义Return回调
每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目加载时配置:
修改publisher服务,添加一个:
package cn.itcast.mq.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
// 获取RabbitTemplate
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
// 设置ReturnCallback
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) ->
// 投递失败,记录日志
log.info("消息发送失败,应答码,原因,交换机,路由键,消息",
replyCode, replyText, exchange, routingKey, message.toString());
// 如果有业务需要,可以重发消息
);
1.1.3.定义ConfirmCallback
ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功或失败的逻辑不一定相同。
在publisher服务的cn.itcast.mq.spring.SpringAmqpTest类中,定义一个单元测试方法:
public void testSendMessage2SimpleQueue() throws InterruptedException
// 1.消息体
String message = "hello, spring amqp!";
// 2.全局唯一的消息ID,需要封装到CorrelationData中
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
// 3.添加callback
correlationData.getFuture().addCallback(
result ->
if(result.isAck())
// 3.1.ack,消息成功
log.debug("消息发送成功, ID:", correlationData.getId());
else
// 3.2.nack,消息失败
log.error("消息发送失败, ID:, 原因",correlationData.getId(), result.getReason());
,
ex -> log.error("消息发送异常, ID:, 原因",correlationData.getId(),ex.getMessage())
);
// 4.发送消息
rabbitTemplate.convertAndSend("task.direct", "task", message, correlationData);
// 休眠一会儿,等待ack回执
Thread.sleep(2000);
全部配置完后,运行测试类SpringAmqpTest.java,这说明消息发送成功
然后呢,我们来一个消息发送失败的情况,我们故意填错交换机的名字
调用后,后台打印日志如下:
然后我们尝试填错,routingKey看一下
报错信息如下:
之后我们恢复代码,都保证正确即可
总结:
SpringAMQP中处理消息确认的几种情况:
● publisher-comfirm:
- 消息成功发送到exchange,返回ack
- 消息发送失败,没有到达交换机,返回nack
- 消息发送过程中出现异常,没有收到回执
● 消息成功发送到exchange, 但没有路由到queue,
- 调用ReturnCallback
1.2.消息持久化
生产者确认可以确保消息投递到RabbitMQ的队列中,但是消息发送到RabbitMQ以后,如果突然宕机,也可能导致消息丢失。
要想确保消息在RabbitMQ中安全保存,必须开启消息持久化机制。
- 交换机持久化
- 队列持久化
- 消息持久化
1.2.1.交换机持久化
RabbitMQ中交换机默认是非持久化的,mq重启后就丢失。
我们通过命令
重启mq
docker restart mq
然后查看队列、交换机的情况,比如我们创建的是持久化队列
SpringAMQP中可以通过代码指定交换机持久化:
@Bean
public DirectExchange simpleExchange()
// 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
return new DirectExchange("simple.direct", true, false);
事实上,默认情况下,由SpringAMQP声明的交换机都是持久化的。
可以在RabbitMQ控制台看到持久化的交换机都会带上D
的标示:
1.2.2.队列持久化
RabbitMQ中队列默认是非持久化的,mq重启后就丢失。
SpringAMQP中可以通过代码指定交换机持久化:
我们可以先去mq图形化界面把simple.queue删除
@Bean
public Queue simpleQueue()
// 使用QueueBuilder构建队列,durable就是持久化的
return QueueBuilder.durable("simple.queue").build();
事实上,默认情况下,由SpringAMQP声明的队列都是持久化的。
可以在RabbitMQ控制台看到持久化的队列都会带上D
的标示:
这些做完后,我们启动ConsumerApplication.java,然后查看mq的图形化界面
交换机是持久的
队列是持久的
1.2.3.消息持久化
首先把consumer服务停了,不要消费我们的消息
我们在mq的图形化界面,点击simple.queue队列,然后编辑消息,点击发送
查看有1条消息
然后我们重启docker中的mq
docker restart mq
然后再回来看mq的图形化界面,发现队列还在,但是消息没了
利用SpringAMQP发送消息时,可以设置消息的属性(MessageProperties),指定delivery-mode:
- 1:非持久化
- 2:持久化
用java代码指定:
默认情况下,SpringAMQP发出的任何消息都是持久化的,不用特意指定。
运行测试类SpringAmqpTest.java之后,查看mq的图形化界面
查看一下具体消息
然后我们重启一下docker的mq容器
docker restart mq
注意:AMQP中创建的交换机、队列、消息默认都是持久的
交换机:
队列:
消息:
1.3.消费者消息确认
RabbitMQ是阅后即焚机制,RabbitMQ确认消息被消费者消费后会立刻删除。
而RabbitMQ是通过消费者回执来确认消费者是否成功处理消息的:消费者获取消息后,应该向RabbitMQ发送ACK回执,表明自己已经处理消息。
设想这样的场景:
- 1)RabbitMQ投递消息给消费者
- 2)消费者获取消息后,返回ACK给RabbitMQ
- 3)RabbitMQ删除消息
- 4)消费者宕机,消息尚未处理
这样,消息就丢失了。因此消费者返回ACK的时机非常重要。
而SpringAMQP则允许配置三种确认模式:
- manual:手动ack,需要在业务代码结束后,调用api发送ack。
- auto⭐:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack。
- none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除
由此可知:
- none模式下,消息投递是不可靠的,可能丢失
- auto模式类似事务机制,出现异常时返回nack,消息回滚到mq;没有异常,返回ack
- manual:自己根据业务情况,判断什么时候该ack
一般,我们都是使用默认的auto即可。
1.3.1.演示none模式
修改consumer服务的application.yml文件,添加下面内容:
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: none # 关闭ack
修改consumer服务的SpringRabbitListener类中的方法,模拟一个消息处理异常:
修改SpringRabbitListener.java
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg)
log.info("消费者接收到simple.queue的消息:【】", msg);
// 模拟异常
System.out.println(1 / 0);
log.debug("消息处理完成!");
测试可以发现,当消息处理抛异常时,消息依然被RabbitMQ删除了。
dubug启动Consumer
发现消息还没接收呢,直接就没了
也就是说,消费者虽然接收到了消息,但是假如消费者还没有读取,发生了报错或者宕机,这个消息就会丢失
1.3.2.演示auto模式
再次把确认机制修改为auto:
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: auto # 关闭ack
我们去mq的图形化界面创建消息
发送后,我们看到图形化界面中有1条消息
IDEA后台因为我们认为写了1/0的错误算数运算,导致IDEA不停重发请求重试消息的推送,这显然也不符合我们的要求
在异常位置打断点,再次发送消息,程序卡在断点时,可以发现此时消息状态为unack(未确定状态):
抛出异常后,因为Spring会自动返回nack,所以消息恢复至Ready状态,并且没有被RabbitMQ删除:
1.4.消费失败重试机制
当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力:
怎么办呢?
1.4.1.本地重试
我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列。
修改consumer服务的application.yml文件,添加内容:
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true # 开启消费者失败重试
initial-interval: 1000 # 初始的失败等待时长为1秒
multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
max-attempts: 4 # 最大重试次数
stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
修改SpringRabbitListener.java
修改为日志打印的形式
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg)
log.debug("消费者接收到simple.queue的消息:【" + msg + "】");
System.out.println(1 / 0);
log.info("消费者处理消息成功!");
重启consumer服务,重复之前的测试。可以发现:
- 在重试4次后,SpringAMQP会抛出异常
AmqpRejectAndDontRequeueException,说明本地重试触发了
- 查看RabbitMQ控制台,发现消息被删除了,说明最后SpringAMQP返回的是ack,mq删除消息了
结论:
- 开启本地重试时,消息处理过程中抛出异常,不会requeue到队列,而是在消费者本地重试
- 重试达到最大次数后,Spring会返回ack,消息会被丢弃
1.4.2.失败策略
在之前的测试中,达到最大重试次数后,消息会被丢弃,这是由Spring内部机制决定的。
在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecovery接口来处理,它包含三种不同的实现:
-
RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
-
ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
-
RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机⭐
比较优雅的一种处理方案是RepublishMessageRecoverer,失败后将消息投递到一个指定的,专门存放异常消息的队列,后续由人工集中处理。
1)在consumer服务中定义处理失败消息的交换机和队列
@Bean
public DirectExchange errorMessageExchange()
return new DirectExchange("error.direct");
@Bean
public Queue errorQueue()
return new Queue("error.queue", true);
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange)
return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
2)定义一个RepublishMessageRecoverer,关联队列和交换机
@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate)
return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
完整代码:
package cn.itcast.mq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.context.annotation.Bean;
@Configuration
public class ErrorMessageConfig
@Bean
public DirectExchange errorMessageExchange()
return new DirectExchange("error.direct");
@Bean
public Queue errorQueue()
return new Queue("error.queue", true);
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange)
return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate)
return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
以上配置完之后,我们再重复步骤发送消息
发送后我们看到失败交换机有了
队列也有了
看一下IDEA的后台
看一下error.queue中的消息,很清晰把错误栈都输出了
1.5.总结
如何确保RabbitMQ消息的可靠性?
- 开启生产者确认机制,确保生产者的消息能到达队列
- 开启持久化功能,确保消息未消费前在队列中不会丢失
- 开启消费者确认机制为auto,由spring确认消息处理成功后完成ack
- 开启消费者失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理
2.死信交换机
2.1.初识死信交换机
2.1.1.什么是死信交换机
什么是死信?
当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter):
- 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false
- 消息是一个过期消息,超时无人消费
- 要投递的队列消息满了,无法投递
如果这个包含死信的队列配置了dead-letter-exchange
属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,检查DLX)。
如图,一个消息被消费者拒绝了,变成了死信:
因为simple.queue绑定了死信交换机 dl.direct,因此死信会投递给这个交换机:
如果这个死信交换机也绑定了一个队列,则消息最终会进入这个存放死信的队列:
另外,队列将死信投递给死信交换机时,必须知道两个信息:
- 死信交换机名称
- 死信交换机与死信队列绑定的RoutingKey
这样才能确保投递的消息能到达死信交换机,并且正确的路由到死信队列。
2.1.2.利用死信交换机接收死信(拓展)
在失败重试策略中,默认的RejectAndDontRequeueRecoverer会在本地重试次数耗尽后,发送reject给RabbitMQ,消息变成死信,被丢弃。
我们可以给simple.queue添加一个死信交换机,给死信交换机绑定一个队列。这样消息变成死信后也不会丢弃,而是最终投递到死信交换机,路由到与死信交换机绑定的队列。
我们在consumer服务中,定义一组死信交换机、死信队列:
// 声明普通的 simple.queue队列,并且为其指定死信交换机:dl.direct
@Bean
public Queue simpleQueue2()
return QueueBuilder.durable("simple.queue") // 指定队列名称,并持久化
.deadLetterExchange("dl.direct") // 指定死信交换机
.build();
// 声明死信交换机 dl.direct
@Bean
public DirectExchange dlExchange()
return new DirectExchange("dl.direct", true, false);
// 声明存储死信的队列 dl.queue
@Bean
public Queue dlQueue()
return new Queue("dl.queue", true);
// 将死信队列 与 死信交换机绑定
@Bean
public Binding dlBinding()
return BindingBuilder.bind(dlQueue()).to(dlExchange()).with("simple");
2.1.3.总结
什么样的消息会成为死信?
- 消息被消费者reject或者返回nack
- 消息超时未消费
- 队列满了
死信交换机的使用场景是什么?
- 如果队列绑定了死信交换机,死信会投递到死信交换机;
- 可以利用死信交换机收集所有消费者处理失败的消息(死信),交由人工处理,进一步提高消息队列的可靠性。
2.2.TTL
一个队列中的消息如果超时未消费,则会变为死信,超时分为两种情况:
- 消息所在的队列设置了超时时间
- 消息本身设置了超时时间
2.2.1.接收超时死信的死信交换机
在consumer服务的SpringRabbitListener中,定义一个新的消费者,并且声明 死信交换机、死信队列:
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "dl.ttl.queue", durable = "true"),
exchange
SpringCloud概述及微服务技术栈的使用
1、SpringCloud的简介
SpringCloud是一系列框架的有序集合。它利用SpringBoot的开发便利性巧妙地简化了分布式系统基础设置的开发,如服务发现与注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署。SpringCloud并没有重复制造轮子,它只是将目前各家公司开发比较成熟、经得起考研的服务框架组合起来,通过SpringBoot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者流下了一套简单易懂、易部署和易维护的分布式系统开发工具。
1.1、SpringCloud中的五大核心组件
Spring Cloud的本质是在SpringBoot的基础上,增加了一堆微服务相关的规范,并对应用上下文(ApplicationContext)进行功能增强,既然SpringCloud是规范,那么就需要去实现,目前Spring Cloud规范已有Spring官方,Spring Cloud Netflix,Spring Cloud Alibaba等是实现。 通过组件化的方式,Spring Cloud将这些实现整合到一起构成全家桶式的微服务技术栈。
SpringCloud Netflix组件
组件名称 作用 Eureka 服务注册中心 Ribbon 客户端负载均衡 Feign 声明式服务端调用(基于Ribbon,将调用方式RestTemplate,改为service接口调用) Hystrix 客户端容错报保护(熔断降级服务) Zuul API服务网关
Spring Cloud Alibaba组件
组件名称 作用 Nacos 服务注册中心 Sentinel 客户端容错保护
Spring Cloud原生及其他组件
组件 作用 Consul(Eureka替代者) 服务注册中心 Config 分布式配置中心 Gateway(Zuul替代者) API服务网关 Sleuth 分布式链路追踪
1.2、SpringCloud的架构
从上图可以看出SpringCloud各个组件的相互配合,合作支持了一套完整的微服务架构。
- 注册中心: 负责服务的注册与发现,很好的将个服务连接起来
- 断路器: 负责监控服务之间的调用情况,连续多次的失败,将进行熔断降级保护
- API网关: 负责转发所有对外的请求和服务
- 配置中心: 提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
- 链路追踪技术: 可以将所有的数据记录下来,方便我们进行后续分析
- 各个组件又提供了功能完善的dashboard监控平台,可以当便的监控各组件的运行状况
1.3、微服务与微服务架构
微服务:
强调的是服务的大小,它关注的是某个一个点,是具体解决某一个问题/提供落地式对应服务的一个服务应用,狭义的看,可以看做是IDEA中的一个个微服务工程或者Moudle模块。IDEA工具里面使用Maven开发的一个个独立的Moudel,它具体是使用SpringBoot开发的一个小模块,专业的事情交给专业的模块来做,一个模块就做一件事情,强调的是一个个个体,每个个体完成一个具体的任务或者功能。
微服务架构:
一种新的架构形式,Martin Fowler于2014年提出。
微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间相互协调互相配合,为用户提供最终价值,每个服务运行在其独立的进程中,服务与服务之间采用轻量级的通信机制(如HTTP协议)互相协作,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具(如maven)对其进行统一构建。
1.4、微服务技术栈概括
微服务技术条目 技术支持 服务开发 Spring、SpringBoot、SpringMVC 服务配置与管理 Netflix公司的Archaius、阿里的Diamond等 服务注册与发现 Eureka、Consul、Zookeeper 服务调用 Rset、RPC、gRPC 服务熔断器 Hystrix、Envoy等 负载均衡 Ribbon、Nginx等 服务接口调用(客户端调用服务的简化工具) Feign等 消息队列 RabbitMQ、ActiveMQ、Kafka等 服务配置中心管理 SpringCLoudConfig、Chef等 服务路由(API网关) Zuul等 服务监控 Zabbix、Nagios、Metrics、Specatator等 全链路追踪 Zipkin、Brave、Dapper等 数据流操作开发包 SpringCloud Stream(封装与Redis、Rabbit、Kafka等发送接收消息) 时间消息总栈 SpringCloud Bus 服务部署 Docker、OpenStack、Kuberneters等
1.5、为什么选择SpringCloud作为微服务架构?
选型一依据
- 整体解决方案和框架成熟度
- 社区热度
- 可维护性
- 学习曲线
当前各大IT公司用的微服务架构有哪些?
- 阿里:dubbo+HFS
- 京东:JFS
- 新浪:Motan
- 当当网:DubboX
1.6、SpringCloud与SpringBoot的关系
- SpringBoot专注于快速方便的开发出当个个体微服务
- SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务,整合并管理起来,为各个微服务之间提供:配置管理、服务发现、断路器、路由、代理、事件总栈、决策竞选、分布式会话等等集成服务
- SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系
1.7、Dubbo和SpringCloud技术选型
1、分布式+服务治理Dubbo
- 目前成熟的互联网架构,应用服务化拆分+消息中间件
2、Dubbo与SpringCloud对比
可以看一下社区活跃度:Dubbo和SpringCloud
Dubbo SpringCloud 服务注册中心 Zookeeper Spring Cloud Netflix Eureka 服务调用方式 RPC REST API 服务监控 Dubbo-monitor SpringBoot Admin 断路器 不完善 SpringCloud Netflix Hystrix 服务网关 无 Spring Cloud Netflix Zuul 分布式配置 无 Spring Cloud Config 服务追踪 无 Spring Cloud Sleuth 消息总栈 无 Spring Cloud Bus 数据流 无 Spring Cloud Stream 批量任务 无 Spring Cloud Task
两者最大的区别在于通信方式:
SpringCloud抛弃了Dubbo的RPC通信,采用的是基于轻量级HTTP协议的REST API方式。
严格来说,这两种方式各有优劣,在性能上RPC要优于REST,但是在灵活度上REST相比RPC是更灵活,服务提供方和调用方只需要一致契约,不存在代码级别的强依赖,这个优点在当下强调快速演化的微服务环境下,显得更加合适。
二者解决的问题域不同:Dubbo的定位是一款RPC框架,而SpringCloud的目标是微服务架构下的一站式解决方案。
1.8、SpringCloud可以做什么?
- Distributed/Versioned configuration 分布式/版本控制系统
- Service registration and discovery 服务注册与发现
- Routing 路由
- Service-to-service calls 服务到服务之间的调用
- Load balancing 负载均衡策略
- Circuit Breakers 断路器
- Distributed messaging 分布式消息管理
1.9、SpringCloud官网下载
SpringCloud官网
SpringCloud没有采用数字编号的方式命名版本号,而是采用了伦敦地铁站的名称,同时根据字母表的顺序来对应版本时间顺序:
- 最早的Realse版本:Angel,
- 第二个Realse版本:Brixton,
- 然后依次是Camden、Dalston、Edgware,
- 目前最新的是Hoxton SR4 CURRENT GA通用稳定版。
1.10、SpringCloud版本选择
大版本说明
SpringBoot SpringCloud 关系 1.2X Angel版本 兼容SpringBoot1.2X 1.3X Brixton版本(布里克斯顿) 兼容SpringBoot1.3X,也兼容SpringBoot1.4X 1.4X Camden版本(卡姆登) 兼容SpringBoot1.4X,也兼容SpringBoot1.5X 1.5X Dalston版本(多尔斯顿) 兼容SpringBoot1.5X,不兼容SpringBoot2.0X 1.5X Edgware版本(埃奇韦尔) 兼容SpringBoot1.5X,不兼容SpringBoot2.0X 2.0X Finchley版本(芬奇利) 不兼容SpringBoot1.5X ,兼容SpringBoot2.0X 2.1X Greenwich版本(格林威治)
实际开发版本关系
spring-boot-starter-parent spring-cloud-dependencies 版本号 发布日期 版本号 发布日期 1.5.2.RELEASE 2017-03 Dalston.RC1 2017-x 1.5.9.RELEASE 2017-11 Edgware.RELEASE 2017-11 1.5.16.RELEASE 2018-04 Edgware.SR5 2018-10 1.5.20.RELEASE 2018-09 Edgware.SR5 2018-10 2.0.2.RELEASE 2018-05 Fomchiey.BULD-SNAPSHOT 2018-x 2.0.6.RELEASE 2018-10 Fomchiey-SR2 2018-10 2.1.4.RELEASE 2019-04 Greenwich.SR1 2019-03
2、基于Eureka注册中心的案例搭建与分析
SpringCloud系列(一)、服务注册中心Eureka基础【详细教程】
3、Eureka的替换方案Consul
Eureka的闭源影响
在Euraka的GitHub上,宣布Eureka 2.x闭源。近这意味着如果开发者继续使用作为 2.x 分支上现有工作repo 一部分发布的代码库和工件,风险则将自负。
Eureka的替换方案如下:
- ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
- Consul是近几年比较流行的服务发现工具,工作中用到,简单了解一下。consul的三个主要应用场景:服务发现、服务隔离、服务配置。
- Nacos是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
3.1、Consul概述
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,
- 内置了服务注册与发现框 架
- 分布一致性协议实现
- 健康检查
- Key/Value 存储
- 多数据中心方案
不再需要依赖其它工具(比如 ZooKeeper 等),使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。
3.2、Consul的优势
- 使用Raft算法来保证一致性,比复杂的Paxoa算法更直接,相比较而言,Zookeeper采用的是Paxos,而etcd使用的则是Raft
- 支持多数据中心,内外网的服务采用不同的端口监听。多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟,分片等情况,zookeeper和etcd均不提供多数据中心功能的支持
- 支持健康检测,etcd不提供此功能
- 支持HTTP和DNS协议接口。 zookeeper的继承较为复杂,etcd只支持http协议
- 官方提供web管理界面,etcd无此功能
- 综合比较,Consul作为服务注册和配置管理,比较值得关注和研究
Consul的特征:
- 服务发现
- 多数据中心
- key/value存储
- 健康检测
3.3、Consul与Eureka的区别
Consul具有强一致性(CP):
- 服务注册相比Eureka会稍慢一些,因为Consul的Raft协议要求必须过半数的节点都写入成功才认为注册成功
- Leader挂点后,重新选举期间整个Consul不可用,保证了强一致性,但牺牲了可用性
Eureka保证高可用性和最终一致性(AP):
- 服务注册相对要快,因为不需要等注册信息replicate到其他节点上,也不保证注册信息是否replicate成功
- 当数据出现不一致时,虽然A、B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时,如果请求A查不到,但请求B可以查到(但内容不一定一致),如此保证了高可用性,但是牺牲了一致性
开发语言和使用:
- Eureka就是个servlet程序,跑在servlet容器中
- Consul则是go编写而成,安装启动即可
3.4、Consul的下载与安装
访问 Consul 官网下载 Consul 的最新版本,Consul 需要单独安装,我这里是consul1.5x。根据不同的系统类型选择不同的安装包,从下图也可以看出 Consul 支持所有主流系统。
在Linux虚拟机在中安装Consul服务:
## 从官网下载最新版本的Consul服务
wget https://releases.hashicorp.com/consul/1.5.3/consul_1.5.3_linux_amd64.zip
##使用unzip命令解压
unzip consul_1.5.3_linux_amd64.zip
##将解压好的consul可执行命令拷贝到/usr/local/bin目录下
cp consul /usr/local/bin
##测试一下
consul
启动Consul服务:
##已开发者模式快速启动,-client指定客户端可以访问的ip地址
[root@node01 ~]# consul agent -dev -client=0.0.0.0
==> Starting Consul agent...
Version: 'v1.5.3'
Node ID: '49ed9aa0-380b-3772-a0b6-b0c6ad561dc5'
Node name: 'node01'
Datacenter: 'dc1' (Segment: '<all>')
Server: true (Bootstrap: false)
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false,
Auto-Encrypt-TLS: false
启动成功之后访问: http://IP地址:8500
,可以看到 Consul 的管理界面:
我们此处暂时先使用windows下的版本启动Consul服务:从官网下载windows版本的zip,解压后免安装:
输入启动Consu命令:
#-client=0.0.0.0 是为了开放所有ip访问
consul agent -dev -client=0.0.0.0
启动之后,访问地址栏:localhost:8500
3.5、Consul的K/V存储
可以参照Consul提供的KV存储的 API完成基于Consul的数据存储
含义 请求路径 请求方式 查看key v1/kv/:key GET 保存或更新 v1/kv/:key PUT 删除 v1/kv/:key DELETE
- key值中可以带/, 可以看做是不同的目录结构。
- value的值经过了base64_encode加密,获取到数据后base64_decode解密才能获取到原始值。数据不能大于512Kb
- 不同数据中心的kv存储系统是独立的,使用dc=?参数指定。
3.6、基于Consul的服务注册案例
工程配置仍然和Eureka保持一致(可做参考):
SpringCloud系列(一)、服务注册中心Eureka基础【详细教程】
ebuy-consul-parent(父模块)
---ebuy-consul-product(商品微服务)
---ebuy-consul-order(订单微服务)
修改商品和订单微服务模块的pom文件:
<!--SpringCloud提供的基于Consul的服务发现-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--actuator用于心跳检查-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置服务注册
ebuy-consul-product的application.yml
server:
port: 9011 #端口号
spring:
application:
name: ebuy-product #商品模块服务名称
datasource:
username: root #数据库用户名
password: root #数据库密码
driver-class-name: com.mysql.jdbc.Driver #mysql加载驱动
url: jdbc:mysql://localhost:3306/ebuy?useUnicode=true&characterEncoding=utf8
cloud:
consul:
host: 127.0.0.1 #指定consul服务地址
port: 8500 #指定consul服务端口号
discovery:
register: true #是否注册
instance-id: ${spring.application.name}-1 #指定实例id名
server-name: ${spring.application.name} #服务实例名称
port: ${server.port} #服务实例端口号
health-check-path: /actuator/health #健康检测路径
health-check-interval: 15s #指定健康检测时间间隔
prefer-ip-address: true #开启ip地址注册
ip-address: ${spring.cloud.client.ip-address} #实例请求ip
#mybatis相关配置
mybatis:
type-aliases-package: com.ebuy.product.pojo #mybatis简化pojo实体类别名
mapper-locations: com/ebuy/product/mapper/*.xml #mapper映射文件路径
#打印日志
logging:
level:
com.ebuy: DEBUG #日志级别
ebuy-consul-order的application.yml
server:
port: 9013 #端口号
address: 127.0.0.1
tomcat:
max-threads: 10 #最大线程数(默认为200台)
spring:
application:
name: ebuy-order #服务名
cloud:
consul:
host: 127.0.0.1 #指定consul服务地址
port: 8500 #指定consul服务端口号
discovery:
register: true #是否注册
instance-id: ${spring.application.name}-1 #指定实例id名
server-name: ${spring.application.name} #服务实例名称
port: ${server.port} #服务实例端口号
health-check-path: /actuator/health #健康检测路径
health-check-interval: 15s #指定健康检测时间间隔
prefer-ip-address: true #开启ip地址注册
ip-address: ${spring.cloud.client.ip-address} #实例请求ip
health-check-url: http://${server.address}:${server.port}/**/health
health-check-critical-timeout: 30s #check失败后,多少秒剔除该服务
#打印日志
logging:
level:
com.ebuy: DEBUG
其中 spring.cloud.consul
中添加consul的相关配置:
- host:表示Consul的Server的请求地址
- port:表示Consul的Server的端口
- discovery:服务注册与发现的相关配置
- instance-id : 实例的唯一id(推荐必填),spring cloud官网文档的推荐,为了保证生成一个唯一的id ,也可以换成
${spring.application.name}:${spring.cloud.client.ipAddress}
- prefer-ip-address:开启ip地址注册
- ip-address:当前微服务的请求ip
启动两个微服务:查看Consul监控中心
基于微服务的发现:
由于SpringCloud对Consul进行了封装。对于在消费者端获取服务提供者信息和Eureka是一致的。同样使用 DiscoveryClient
完成调用获取微服务实例信息,其余用法基本都和Eureka保持一致。
4、Ribbon:基于客户端服务调用(负载均衡)
经过以上的学习,已经实现了服务的注册和服务发现。当启动某个服务的时候,可以通过HTTP的形式将信息注册到注册中心,并且可以通过SpringCloud提供的工具获取注册中心的服务列表。但是服务之间的调用还存在很多的问题,如何更加方便的调用微服务,多个微服务的提供者如何选择,如何负载均衡等。
4.1、什么是Ribbon?
Ribbon是Netflix发布的一个负载均衡器,有助于控制HTTP和TCP客户端行为,在SpringCloud中,Eureka一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从Eureka或者Consul中读取到的服务信息,在调用服务节点提供的服务时,会合理的进行负载,默认为轮询策略。
在SpringCloud中可以将注册信息和Ribbon配合使用,Ribbon自动的从注册中心获取服务提供者的列表信息,并基于内置的负载均衡算法,请求服务。
4.2、Ribbon的主要作用
客户端服务调用:
- 基于Ribbon实现服务调用,是通过拉取到的所有服务列表组成(服务名:请求路径)的一种映射关系,借助于RestTemplate最终实现调用。
负载均衡:
- 当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址。
4.3、Ribbon的关键组件
- ServerList:可以响应客户端的特定服务的服务器列表。
- ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。
- ServerListUpdater:用于执行动态服务器列表更新。
- Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
- Ping:客户端用于快速检查服务器当时是否处于活动状态。
- LoadBalancer:负载均衡器,负责负载均衡调度的管理。
4.4、工程改造
上述讲解了Consul替代Eureka,此处我们暂时先将注册中心改为Eureka注册,并配置两台注册中心集群:
application.yml(将8000注册到9000)
,互相注册,application.yml(将9000注册到8000)
即可
server:
port: 8000 #端口号
spring:
application:
name: eureka-server #eurekaServer服务名
eureka:
#instance:
#hostname: 127.0.0.1 #服务器ip地址
client:
register-with-eureka: true #是否将自己注册到注册中心
#fetch-registry: false #是否从注册中心获取服务列表
serviceUrl: #配置暴露给Eureka Client的请求地址
defaultZone: http://127.0.0.1:9000/eureka/
server:
enable-self-preservation: false #关闭自我保护机制(一旦发现有网络不稳定的服务,直接剔除)
eviction-interval-timer-in-ms: 4000 #剔除时间间隔,单位:毫秒
#wait-time-in-ms-when-sync-empty: 5
服务提供者和消费者:修改application.yml文件中注册中心配置:
server:
port: 90XX #端口号
address: 127.0.0.1
tomcat:
max-threads: 10 #最大线程数(默认为200台)
spring:
application:
name: ebuy-XXXX #服务名
# cloud:
# consul:
# host: 127.0.0.1 #指定consul服务地址
# port: 8500 #指定consul服务端口号
# discovery:
# register: true #是否注册
# instance-id: ${spring.application.name}-1 #指定实例id名
# server-name: ${spring.application.name} #服务实例名称
# port: ${server.port} #服务实例端口号
# health-check-path: /actuator/health #健康检测路径
# health-check-interval: 15s #指定健康检测时间间隔
# prefer-ip-address: true #开启ip地址注册
# ip-address: ${spring.cloud.client.ip-address} #实例请求ip
# health-check-url: http://${server.address}:${server.port}/**/health
# health-check-critical-timeout: 30s #check失败后,多少秒剔除该服务
#使用eureka注册中心
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka/,http://127.0.0.1:9000/eureka/
instance:
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true #使用ip地址注册(在注册中心显示名字以ip地址显示)
lease-expiration-duration-in-seconds: 10 #eureka client发送心跳给eureka server服务端后,续约到期时间(默认为90秒)
lease-renewal-interval-in-seconds: 5 #发送心跳续约时间间隔
#打印日志
logging:
level:
com.ebuy: DEBUG
4.5、服务调用Ribbon高级,什么是负载均衡?
在搭建网站时,如果节点的web服务性能和可靠性都无法达到要求,或者是在使用外网服务时,经常担心被人攻击,一不小心就会有打开外网端口的情况,通常这个时候加入负载均衡就能有效的解决服务访问问题。
负载均衡是一种基础的网络服务,其原理是通过运行在前面的负载均衡服务,按照指定的负载均衡算法,将流量分配到后端服务集群上,从而为系统提供并行扩展的能力。
负载均衡的应用场景包括流量包、转发规则以及后端服务,由于服务有内外网个例,健康检查等功能,能够有效提高系统的安全性和可靠性。
4.6、客户端负载均衡和服务端负载均衡
客户端负载均衡:
- 客户端从注册中心会获取到一个服务提供者的服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡,即在客户端就进行负载均衡算法分配。
服务端负载均衡:
- 先发送请求到负载均衡服务器或软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行服务在均衡算法分配
4.7、基于Ribbon实现负载均衡
首先要搭载多态服务器,上述已经搭建好Eureka注册中心的集群,然后再搭建两台ebuy-product和一台ebuy-order即可,如下:
1、服务提供者ebuy-product
服务提供者:修改ebuy-product模块下的ProductController#findById()方法:
@RestController
@RequestMapping("/product")
public class ProductController {
/**
* 回去客户端ip地址
*/
@Value("${spring.cloud.client.ip-address}")
private String ip;
/**
* 获取客户端的端口号
*/
@Value("${server.port}")
private String port;
@Autowired
private EasybuyProductService productService;
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public EasybuyProduct findById(@PathVariable Long id) {
EasybuyProduct product = productService.selectByPrimaryKey(id);
product.setEpDescription("调用ebuy-product服务,ip:"+ip+",服务提供者端口:"+port);
return product;
}
}
ebuy-product服务提供者启动两台:9011,9012
ebuy-order服务消费者启动一台:9013
2、服务消费者ebuy-order
然后在ebuy-order的EbuyOrderApplication启动类处,创建RestTemplate方法,并添加@LoadBalanced注解
实现与Ribbon搭配的负载均衡:
@SpringBootApplication
@EnableEurekaClient //开启Eureka客户端服务注册
@EnableDiscoveryClient //开启服务发现
public class EbuyOrderApplication {
/**
* @Bean 配置RestTemplate交给spring管理
* @LoadBalanced 实现负载均衡(Ribbon原理)
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(EbuyOrderApplication.class, args);
}
}
在ebuy-order服务模块的OrderController下添加下单方法:
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
public EasybuyProduct findById(@PathVariable Long id) {
EasybuyProduct easybuyProduct=new EasybuyProduct();
//easybuyProduct=restTemplate.getForObject("http://127.0.0.1:9011/product/"+id,EasybuyProduct.class);<以上是关于SpringCloud微服务技术栈.黑马跟学的主要内容,如果未能解决你的问题,请参考以下文章