SpringCloud
- 总结
- 什么是Spring Cloud?
- eureka注册表(zookeper,eureka,nacos、consul、etcd)
- SpringCloud技术组成
- eureka搭建
- eureka和zookeeper区别
- 远程调用(RestTemplate)
- RestTemplate和Dubbo远程调用的区别
- Ribbon
- Hystrix(断容器)
- Hystrix故障监控
- Hystrix Dashboard(监控)
- Feign(Hystrix,远程调用(RestTemplate),Ribbon,)
- RabbitMQ消息服务器(rabbitmq,activemq,rocketmq,tubemq)
- RabbitMQ使用场景
- RabbitMQ安装启动命令
- RabbitMQ消息持久化
- 拼多多商城整合RabbitMQ
- Zuul(API网关,Hystrix,Ribbon)
- Zuul 和 Feign的区别
- Config(配置中心)
- 链路跟踪
- Docker
总结
- 1个中心: eureka 注册中心
- 2个基本点: Ribbon,Hystrix
- 3个工具: Feign 集成,Zuul 网关,Config 配置中心
- 4个监控: Hystrix Dashboard,turbine,sleuth,zipkin
什么是Spring Cloud?
- SpringCloud是基于SpringBoot的一整套实现微服务的框架。它提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。最重要的是,基于SpringBoot,会让开发微服务架构非常方便。
- Spring cloud是一个工具集,集成多个工具集,来解决微服务中的各种问题
eureka注册表(zookeper,eureka,nacos、consul、etcd)
所有微服务都需要注册到注册中心,通过注册表互相发现,服务之间调用
- 注册: 服务提供者启动时,向 eureka 一次次反复注册,直到注册成功为止
- 拉取注册表: 服务发现者 每30秒拉取一次注册表(刷新注册表)
- 心跳: 每30秒发送一次心跳数据,eureka 连续 3次收不到 一个服务的心跳,会删除这个服务
- 自我保护模式: 一般由于网络不稳定或中断,15分钟内,85%以上服务器出现心跳异常,则会自动进入保护模式,在保护模式,所有服务都 不删除,网络恢复后,可以自动退出保护模式,恢复正常。(zookeper 没有保护模式,3次心跳后会自动删除服务)
SpringCloud技术组成
eureka: 微服务治理,服务注册和发现
ribbon: 负载均衡,请求重试
hystrix: 断路器,服务降级、熔断
feign: ribbon + hystrix 集成,并提供声明式客户端
zuul: API 网关,提供微服务的统一入口,并提供统一的权限验证
config: 配置中心
bus: 消息总线,配置刷新
sleuth+zipkin: 链路跟踪
hystrix dashboard 和 turbine: hystrix 数据监控
eureka搭建
- 启动类注解: @EnableEurekaServer
- application.yml中做好配置
- 引入依赖(eureka)
eureka和zookeeper区别
eureka: 强调AP(可用性),集群结构:对等结构,互相注册,互相拉取
zookeeper: 强调CP(一致性),集群结构:主从结构
远程调用(RestTemplate)
说明:SpringBoot 提供的
类似于HttpClient,可以发送 http 请求,并处理响应。RestTemplate 简化了 RestAPI 调用,只需要使用它的一个方法,就可以完成请求,响应,json 转换
方法:
getForObject(url,转换类型.class,提交的参数)
postForObject(url,协议体数据,转换的类型.class)
RestTemplate和Dubbo远程调用的区别
RestTemplate:Rest API ,http 调用,效率低,是一整套的,解决了熔断,配置等
Dubbo:RPC调用,java的序列化,效率高,但只是一个远程调用
Ribbon
Ribbon负载均衡
作用: 负载均衡,请求重试
Ribbo 对 RestTemplate 做了封装,增强了RestTemPlate的功能。利用AOP进行的增强
- 获得地址
- 轮询一个服务的主机地址列表
- RestTemPlate负载执行最终调用
Ribbon重试
说明:一种容错机制,当调用远程服务失败,可以自动重试调用
- 添加重试依赖
- 配置重试参数(yml中配置):
MaxAutoRetries–单台服务器的重试次数
MaxAutoRetriesNextServer:更换服务器的次数 - 配置连接超时(java代码证设置):
ConnectTimeout:与远程服务建立网络连接的超时时间
ReadTimeout:连接已建立,请求已发送,等待响应的超时时间
Hystrix(断容器)
系统容错工具
比如 A 服务调用远程 B 服务
- 降级: 调用远程服务失败(宕机,500,超时),可以降级执行当前服务中心的一段代码,向客户端返回结果(B 服务可以根据业务需求来返回)
- 熔断: 当访问量过大,出现大量的失败,可以做过热保护,A 与 B 断开远程服务,不再调用,通过 降级 向客户端返回结果
作用: 出现缓存穿透进行限流,防止故障传播,雪崩效应
Hystrix降级搭建
说明:hystrix 默认降级超时时间是 1 秒,可以自行配置,超时要大于 ribbon总的重试时间,否则 ribbon 重试可能无效。
- hystrix 添加依赖
- 启动类添加注解**@EnableCircuitBreaker** 启动 hystrix
- 在方法上添加降级代码:
//当调用远程服务失败,跳转到指定的方法执行降级代码
@HystrixCommand(fallbackMethod = "getItemsFB") //"getItemsFB"方法名,可以自定义,再写该方法执行的结果代码
Hystrix熔断(降级配置默认有熔断)
断路器打开的条件:条件都是可以配置的
- 10秒内20次请求(必须首先满足),并且 50%失败
断路器打开后,所有请求直接执行降级代码,断路器打开后几秒,会进入半开状态,客户端调用会尝试向后台服务发送一次调用,如果调用失败,断路器可以自动关闭,恢复正常,如果任然失败,继续保持打开状态几秒…
Hystrix故障监控
Hystrix 利用 Actuator工具,来暴露 Hystrix 的故障日志
Actuator
springboot 提供的日志监控工具,可以暴露项目中多种监控信息
- 健康状态
- 系统环境变量
- spring容器中所有的对象
- spring mvc映射的所有路径
Actuator配置
- 添加依赖
- yml配置暴露监控数据
# 配置暴露监控数据
management: # m.e.w.e.i=health 只暴露健康状态
endpoints: # m.e.w.e.i=["health", "beans", "mappings"] 暴露指定的多个监控
web: # m.e.w.e.i="*" 暴露所有的监控
exposure:
include: "*"
Hystrix Dashboard(监控)
仪表盘项目是一个完全独立的项目,与其他项目都无关,也不用向注册表注册
- 添加hystrix dashboard 依赖
- 在启动类上添加 @EnableHystrixDashboard
- yml-允许对哪台服务器开启监控
Feign(Hystrix,远程调用(RestTemplate),Ribbon,)
Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。
集成工具
- 声明式客户端:只需要声明一个抽象接口,就可以通过接口做远程调用,不需要再使用 RestTemplate 来调用,默认不启用 Hystrix,推荐不使用,需要自己配置
// 调用远程的商品服务,获取订单的商品列表
// 通过注解,配置:
// 1. 调用哪个服务
// 2. 调用服务的哪个路径
// 3. 向路径提交什么参数数据
@FeignClient(name="item-service")
public interface ItemClient {
@GetMapping("/{orderId}")
JsonResult<List<Item>> getItems(@PathVariable String orderId);
}
- Hystrix 降级和熔断
- ribbon 负载均衡和重试
#调整 ribbo的重试参数,针对所有服务的通用配置
# Feign 集成了ribbon,重试默认配置如下
ribbon:
MaxAutoRetries: 0
MaxAutoRetriesNexServer: 1
RabbitMQ消息服务器(rabbitmq,activemq,rocketmq,tubemq)
RabbitMQ概念
RabbitMQ是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ配置的转发机制接收服务端发来的消息。RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置
RabbitMQ角色
**生产者(provider):**消息的创建者,负责创建和推送数据到消息服务
**消费者(consumer):**消息的接收方,用于处理数据和确认消息
**代理:**就是 RabbitMQ 本身,用于扮演快递角色,本身不产生消息
rabbitmq有哪些组件以及发送流程?
- ConnectionFactory(连接管理器):应用程序与RabbitMQ之间建立连接的管理器
- Channel(信道):消息推送使用的通道
- Exchange(交换器):用于接收、分配消息
- Queue(队列):用于存储生产者的消息
- RoutingKey(路由键):用于把生产者的数据分配到交换器上
- BindKey(绑定键):用于把交换器的消息绑定到队列上
- 生产者把生产的小心通过channel发送到Exchange上,Exchange通过绑定的router
key来选择Queue,消费者监听到Queue上有新的消息,就消费调此消息;
RabbitMQ六种工作模式
简单模式(direct),工作模式(direct),发布和订阅模式(Fanout),路由模式(driect),主题模式(topic),RPC模式
- 简单模式(默认direct交换机):
生产者代码:
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
ConnectionFactory f=new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672); //默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Connection conn=f.newConnection();
Channel c=conn.createChannel(); //创建通信通道
//定义队列让服务器创建队列 helloworld
//队列的参数:helloworld,是否是持久队列,是否排他(独占)队列,是否自动删除
c.queueDeclare(
"helloworld", //队列名字
false, //是否持久数据
false, //可以共享的队列
false, //不自动删除
null);
//消息发送到 helloworld队列
//1.默认的交换机(讲第三个模式时再解释)
//3.其他的消息属性配置,如果没有给null值
c.basicPublish("",
"helloworld",
null,
"Hello world!".getBytes());
System.out.println("消息已经发送");
}
}
消费者代码:
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f=new ConnectionFactory();
f.setHost("192.168.64.140");
f.setUsername("admin");
f.setPassword("admin");
Channel channel=f.newConnection().createChannel();
//创建 helloworld队列(队列如果已经存在,不会重复创建)
channel.queueDeclare("helloworld", false,false,false,null);
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
byte[] a= message.getBody(); //取出消息
String msg=new String(a);
System.out.println("收到:"+msg);
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//消费数据,等待从队列接收数据
//第二个参数:由服务器对消息进行自动确认
channel.basicConsume("helloworld", true,deliverCallback,cancelCallback);
}
}
- 工作模式(默认direct交换机):
生产者代码:
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory c=new ConnectionFactory();
c.setHost("192.168.64.140");
c.setPort(5672);
c.setUsername("admin");
c.setPassword("admin");
Channel conn=c.newConnection().createChannel();
//定义队列
//定义队列让服务器创建队列 helloworld
//队列的参数:helloworld,是否是持久队列,是否排他(独占)队列,是否自动删除
conn.queueDeclare(
"task_queue",
true, //数据是否持久化
false,
false,
null);
//发送消息
//循环输入消息发送
//输入消息:ger.err.e....er....er
//消费者收到消息后,遍历
while (true){
System.out.println("输入消息:");
String msg=new Scanner(System.in).nextLine();
conn.basicPublish("",
"task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
msg.getBytes());
}
}
}
消费者代码:
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接服务器
ConnectionFactory c=new ConnectionFactory();
c.setHost("192.168.64.140");
c.setPort(5672);
c.setUsername("admin");
c.setPassword("admin");
Channel conn=c.newConnection().createChannel();
//定义队列
conn.queueDeclare("task_queue",
true, //是否持久数据
false, //可以共享的队列
false, //不自动删除
null);
//消费数据
DeliverCallback deliverCallback= new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//遍历消息字符串,每个点字符都暂停1秒
String msg=new String(message.getBody());
System.out.println("收到:"+msg);
for (int i=0;i<msg.length();i++){
if ('.'== msg.charAt(i)){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
}
}
}
//向服务器发送消息的回执
//conn.basicAck(回执(long类型的编码),是否一次确认多条消息);
conn.basicAck(message.getEnvelope().getDeliveryTag(),false); //false只确认一条消息
System.out.println("消息处理结果");
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
};
//必须在手动ACK模式下才有效
conn.basicQos(1); //合理的分发消息
//其中 false 是手动确认,有回值,消息才会从缓存删除
conn.basicConsume("task_queue",false, deliverCallback, cancelCallback);
}
}
- Fanout( 发布和订阅模式):
- Direct(路由模式):
- Topic(主题模式):
生产者代码:
public class producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory c=new ConnectionFactory();
c.setHost("192.168.64.140");
c.setPort(5672);
c.setUsername("admin");
c.setPassword("admin");
Channel channel=c.newConnection().createChannel();
//定义交换机
channel.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
//发送消息,需要携带路由键(关键字)
while (true){
System.out.println("输入消息:");
String msg=new Scanner(System.in).nextLine();
System.out.println("输入路由键:");
String key=new Scanner(System.in).nextLine();
//第二个参数是路由键
channel.basicPublish(
"topic_logs", //交换机名字
key, //定义路由键关键字
null,
msg.getBytes()); //需要将发送的消息转换为 Bytes数组
}
}
}
消费者代码:
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory c=new ConnectionFactory();
c.setHost("192.168.64.140");
c.setPort(5672);
c.setUsername("admin");
c.setPassword("admin");
Channel channel=c.newConnection().createChannel();
//定义随机队列 ,定义交换机,绑定关键词
String queue=channel.queueDeclare().getQueue(); //得到队列名字
channel.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC); //交换机类型
System.out.println("输入绑定键,用空格隔开");
//绑定关键字
String s=new Scanner(System.in).nextLine();
String[] a= s.split("\\s+");
for (String key:a){
channel.queueBind(queue, "topic_logs", key);
}
//正常的从队列当中消费数据
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//取出消息数据
String msg=new String(message.getBody());
String Key=message.getEnvelope().getRoutingKey();
System.out.println("收到:"+Key+"-"+msg);
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//其中 true 是自动确认,有回值,消息才会从缓存删除
channel.basicConsume(queue, true,deliverCallback,cancelCallback);
}
}
- RPC 异步调用模式:
RabbitMQ怎么保证消息的稳定性
- 消息持久化: 持久化必须满足三个条件:Exchange(互换) 设置持久化,Queue(队列)() 设置持久化,Message(信息)持久化发送
- **ACK确认机制:**就是消费端消费完成要通知服务端,服务端才把消息从内存删除,一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case
- 设置集群镜像
- 消息补偿机制
RabbitMQ 服务解耦
RabbitMQ 来解决服务器之间的解耦的情况,中转,传递消息,A服务只需要向消息服务器发送消息,而不用考虑谁需要这些数据;下游服务如果需要数据,自行从消息服务器订阅消息,不再需要数据时则取消订阅即可
RabbitMQ 流量峰值
流量峰值: 某一时刻的请求达到高峰
可以使用 RabbitMQ来进行流量削峰,高峰情况下,瞬间出现的大量请求数据,先发送到消息队列服务器,排队等待被处理,而我们的应用,可以慢慢的从消息队列接收请求数据进行处理,这样把数据处理时间拉长,以减轻瞬时压力
RabbitMQ 异步调用
RabbitMQ消息队列,订单数据可以发送到消息队列服务器,那么调用链路也就可以到此结束,订单系统则可以立即得到响应,整条链路的响应时间只有200毫秒左右。
RabbitMQ使用场景
- 单发送单接收: 简单的发送与接收,没有特别的处理
- 单发送多接收:
- Publish / Subscribe: 发布、订阅模式,发送端发送广播消息,多个接收端接收
- Routing (按路线发送接收)
RabbitMQ安装启动命令
说明:RabbitMQ 默认端口为 5672,服务器访问端口为 15672(http://192.168.64.140:15672)
- systemctl enable rabbitmq-server: 设置服务,开机自动启动
- systemctl start rabbitmq-server: 启动服务
- rabbitmq-plugins enable rabbitmq_management: 开启管理界面插件
- firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --reload: 防火墙打开15672,管理端口 - systemctl restart rabbitmq-server: 重启 RabbitMQ
- rabbitmqctl add_user admin admin: 添加用户
- rabbitmqctl set_user_tags admin administrator: 新用户设置用户为超级管理员
- *rpm -ivh .rpm: 切换到 rabbimq 目录进行安装
RabbitMQ消息持久化
防止服务器崩溃,重启造成消息丢失,需要把消息保存到磁盘当中
队列持久化
xxx.queueDeclare
消息持久化
拼多多商城整合RabbitMQ
- pom.xml添加依赖
- 在启动类上添加 Bean 实例
@Bean
public Queue getQueue() {
Queue q = new Queue("orderQueue", true);
return q;
}
Zuul(API网关,Hystrix,Ribbon)
- 微服务系统统一的调用入口
- 集成了 Ribbon,Hystrix
- 统一的权限校验
配置
- zuul依赖,eureka client依赖
- yml
- 启动类注解 @EnableZuulProxy
统一权限校验
通过 zuul 过滤器,可以判断用户是否有权限访问后台服务,如果没有权限可以阻止用户继续访问
添加过滤器
- 继承 ZuulProxy
- 添加 @Component
Zuul集成Ribbo
默认启动 负载均衡,默认不启动 重试
Zuul集成Hystrix(降级,熔断)
- 默认有Hystrix,不需要配置,
- 添加降级代码:实现 FallbackProvider接口,在子类中实现降级代码,还需要添加 @Component
Zuul 和 Feign的区别
- 都集成了 Hystrix,ribbon,都可以调用后台服务,Zuul 是一个独立的服务,不涉及到业务
- Zuul 部署在所有微服务项目之前,Feign 部署在微服务内部,服务和服务之间调用
- Feign 不推荐使用 Hystrix(一般在微服务前面进行降级和熔断,容易造成混乱),Zuul不推荐使用重试(会造成后台服务的压力,尽量不部署在最前面,越往后越好),实际开发中 Hystrix 一般写在 Zuul当中。
Config(配置中心)
将所有的配置文件(yml)都放在本地仓库(Gitee)
配置:
- 添加依赖 Config Server
- @EnableConfigServer
配置中心客户端
- 添加 config client依赖
- 新建配置文件:bootstrap.yml,添加配置
链路跟踪
spring cloud sleuth 可以跟踪调用链路,分析链路中每个节点的执行情况,一条调用链路中可能调用多个微服务,任何一个微服务不可用都可能造整个调用过程失败,
sleuth,Zipkin
生成链路监控日志,通过 RabbitMQ(解耦) 消息中间介发送给 Zipkin 进行图标模式
生成链路跟踪日志工具,例如:A–>B–>C–>D服务都会产生一个随机的服务 ID
Docker
镜像
镜像命令
docker run: 启动容器
docker images: 查看镜像列表
docker rmi xxx: 删除镜像,可以空格隔开一次性删除多个,也可以通过镜像的 id值删除
docker rmi -f xxx: 存在容器,不能直接删除,可以加 -f 强制删除
docker pull xxx: 下载镜像(后面加镜像名和标签)
容器
容器命令
docker history xxx: 查看镜像中设置的默认应用
docker ps -a: 查看所有容器,包括已关闭的容器
docker ps: 查看正在运行的容器
docker stop xxx: 停止,也可以是 ID
docker start xxx: 重启容器
docker rm xxx: 删除容器
docker rm -f xxx: 强制删除正在运行的容器
docker container prune: 清理所有已经退出的容器
docker run -d --name xxx tomcat: -d 是后台运行,不占用控制台,–name 是给容器命名
docker logs xxx: 查看容器日志
docker run -it --rm tomcat top: 退出后,容器自动关闭,自动删除
docker exec -it xxx pwd: 进入容器,在容器内执行命令