目录
5.3、在启动类添加@EnableEurekaServer注解
6.2.3、在当前服务应⽤的启动类添加 @EnableEurekaClient 注解
7.1.3、在启动类添加 @EnableDiscoveryClient 注解
7.2.2、在Service中注⼊RestTemplate对象调⽤服务
10.2.4 服务提供者和服务消费者连接到注册中⼼都要帐号和密码
16.1.2、Spring Cloud Stream标准流程套路
1、单体应⽤存在的问题
⼀个成功的应⽤必然有⼀个趋势:⽤户量会不断增加、项⽬的业务也会不断的扩展
⽤户量的增加会带来⾼并发的问题,⾼并发问题解决⽅案:
- 应⽤服务器 --> 单体优化 --> 集群(负载均衡、分布式并发)
- 数据库服务器 --> 数据库优化 --> 缓存redis --> 分布式数据库
项⽬业务的扩展,也会带来⼀些问题:
- 项⽬结构越来越臃肿(项⽬结构和代码复杂、项⽬体积逐渐变得庞⼤)
- 项⽬结构和代码复杂导致项⽬不易维护和⼆次开发、扩展和更新就会变得困难
- 项⽬体积逐渐变得庞⼤导致启动时间越来越⻓、⽣产⼒⼤受限制
- 单体应⽤中任何⼀个模块的任何⼀个bug都会导致整个系统不可⽤(单点故障)
- 复杂的单体项⽬也会带来持续部署的障碍
- 单体项⽬使得采⽤新的技术和框架会变得困难
2、微服务架构
2.1、微服务架构概念
微服务架构是⼀种架构概念,就是将⼀个单体应⽤中的每个功能分解到各个离散的服务中以实现对单体应⽤的解耦,并提供更加灵活的服务⽀持
![](https://i-blog.csdnimg.cn/blog_migrate/8074c0a5ef0c3bd780115fd19c101311.png)
2.2、微服务架构优点
- 解决了单体项⽬的复杂性问题
- 每个服务都可以由单独的团队进⾏开发
-
每个服务都可以使⽤单独的技术栈进⾏开发
-
每个服务都是独⽴的进⾏部署和维护
-
每个服务都可以独⽴进⾏扩展
2.3、微服务架构缺点
- 微服务架构本身就是⼀个缺点:如何把握“微”的粒度;
-
微服务架构是⼀个分布式系统,虽然单个服务变得简单了,但是服务之间存在相互的调⽤,整个服务架构的系统变得复杂了;
-
微服务架构需要依赖分布式数据库架构;
-
微服务的单元测试及调⽤变得⽐单体更为复杂;
-
部署基于微服务架构的应⽤程序变得⾮常复杂;
-
进⾏微服务架构的应⽤程序开发的技术成本变得更⾼。
3、微服务架构开发需要解决的问题
在微服务架构开发的系统中必然存在很多个服务,服务之间需要相互感知对⽅的存在,需要进⾏服务间的调⽤,该如何实现呢? —— 进⾏微服务架构开发需要解决的问题:
- 如此多的服务,服务间如何相互发现?
- 服务与服务之间该如何通信?
- 如果某个服务挂了,该如何处理?
- 前端访问多个不同的服务时该如何统⼀访问路径呢?
3.1、服务之间如何相互发现?
微服务架构 —— 每个服务只处理⼀件事情 / ⼀个步骤,在⼀个复杂的业务中必然会存在服务间的相互调⽤,服务想要相互调⽤就需要先发现对⽅。
![](https://i-blog.csdnimg.cn/blog_migrate/d43ba4866d2b236918a4bc193aea5040.png)
- 服务注册与发现中⼼也是⼀台独⽴服务器
- 服务提供者在服务注册与发现中⼼进⾏注册
- 服务注册与发现中⼼进⾏服务记录,并与服务提供者保持⼼跳
- 服务消费者通过服务注册与发现中⼼进⾏服务查询(服务发现)
- 服务注册与发现中⼼返回可⽤的服务的服务器地址列表
- 服务消费者通过负载均衡访问服务提供者
3.2、服务之间如何进⾏通信?
服务消费者在调⽤服务提供者时,⾸先需要通过 服务注册与发现中⼼ 进⾏服务服务查询,返回服务列表给服务消费者, 服务消费者 通过 LoadBalance 调⽤ 服务提供者 , 那么他们之间是如何通信呢? —— 数据传输规则服务与服务间的通信⽅式有 2 种:同步调⽤ 和 异步调⽤
3.2.1、同步调⽤
REST(SpringCloud Netflix,SpringCloud Alibaba)
-
基于 HTTP 协议的请求和响应
-
更容易实现、技术更灵活
-
⽀持多语⾔、同时可以实现跨客户端
-
适⽤⾯很⼴
- 基于⽹络层协议通信
- 传输效率⾼
- 安全性更⾼
- 如果有统⼀的开发规划或者框架,开发效率是⽐较⾼的
3.2.2、异步调⽤
服务间的异步通信通常是通过消息队列实现的
3.3、服务挂了该如何解决?
3.3.1、服务故障雪崩
3.2.2、如何解决服务故障雪崩
服务集群——尽量保证每个服务可⽤
服务降级与熔断——避免请求阻塞造成正常的服务出现故障
3.4、客户端如何统⼀访问多个接⼝服务?
4、微服务架构框架
4.1、主流的微服务架构框架
- Dubbo(阿⾥、开源apache):2012年推出、2014年停更、2015年⼜继续更新
- Dubbox(当当⽹基于Dubbo的更新)
- jd-hydra(京东基于Dubbo的更新)
- SpringCloud Netflix (2016年)/ SpringCloud Alibaba
- ServiceComb(CSE)华为 2017年
4.2、SpringCloud简介
Spring Cloud 是⼀个基于 SpringBoot 实现的微服务架构应⽤开发框架,它为我们进⾏微服务架构应⽤开发提供了服务注册与发现、熔断器、⽹关路由、配置管理、负载均衡、消息总线、数据监控等⼀系列⼯具。
Spring Cloud⽐较成熟的两个体系:
- Spring Cloud Netflix
- Spring Cloud Alibaba
4.3、Spring Cloud核⼼组件
Spring Cloud Netflix :
-
Eureka 服务注册与发现中⼼,⽤于服务治理
-
Ribbon 服务访问组件、进⾏服务调⽤,实现了负载均衡
-
Hystrix 熔断器,服务容错管理
-
Feign 服务访问组件(对 Ribbon 和 HyStrix 的封装)
-
zuul ⽹关组件
4.4、SpringCloud版本介绍
SpringCloud版本 : A-H,2020.0.2
SpringCloud的版本对SpringBoot版本时有依赖的
- A ---- 1.2
- B ---- 1.3
- C ---- 1.4
- D-E ---- 1.5
- F-G-H ---- 2.x
5、搭建服务注册与发现中⼼
使⽤Spring Cloud Netflix 中的 Eureka 搭建服务注册与发现中⼼
5.1、创建SpringBoot应⽤,添加依赖
-
spring web
-
eureka server
5.2、配置服务注册与发现中⼼
## 设置服务注册与发现中⼼的端⼝
server:
port: 8761
## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的
## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名
spring:
application:
name: service-eureka
eureka:
client:
## ip 就是服务注册中⼼服务器的ip
## port 就是服务注册与发现中⼼设置的port
service-url:
defaultZone: http://192.168.54.59:8761/eureka
## 设置服务注册与发现中⼼是否为为集群搭建(如果为集群模式,多个eureka节点之间
需要相互注册)
register-with-eureka: false
## 设置服务注册与发现中是否作为服务进⾏注册
fetch-registry: false
5.3、在启动类添加@EnableEurekaServer注解
5.4、运⾏及访问
6、服务注册
创建保存订单的服务(order-add)注册到服务注册与发现中⼼
6.1、创建SpringBoot应⽤
创建spring boot应⽤,完成功能开发
6.2、注册服务
将能够完成特定业务的SpringBoot应⽤作为服务提供者,注册到服务注册与发现中⼼
6.2.1、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
6.2.2、配置application.yml
## 当前服务的port
server:
port: 9001
## 当前应⽤名会作为服务唯⼀标识注册到eureka
spring:
application:
name: order-add
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_2010_sc?characterEncoding=utf-8
username: root
password: admin123
mybatis:
mapper-locations: classpath:mappers/*
type-aliases-package: com.qfedu.order.beans
## 配置Eureka服务注册与发现中⼼的地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
6.2.3、在当前服务应⽤的启动类添加 @EnableEurekaClient 注解
7、服务发现-Ribbon
服务消费者( api-order-add )通过 eureka 查找服务提供者( order-add ) , 通过服务调⽤组件调⽤提供者
- eureka server
- ribbon
7.1、基础配置
Ribbon客户端已经停更进维啦
7.1.1、创建SpringBoot应⽤,添加依赖
-
eureka server
-
ribbon
7.1.2、配置application.yml
server:
port: 8001
spring:
application:
name: api-order-add
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
7.1.3、在启动类添加 @EnableDiscoveryClient 注解
7.2、服务调⽤
7.2.1、配置RestTemplate
@Configuration
public class AppConfig {
@LoadBalanced //启⽤Ribbon(负载均衡)
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
7.2.2、在Service中注⼊RestTemplate对象调⽤服务
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private RestTemplate restTemplate;
@Override
public ResultVO saveOrder(Order order) {
//1. 调⽤ order-add服务进⾏保存
ResultVO vo = restTemplate.postForObject("http://orderadd/order/add", order,
ResultVO.class);
//2. 调⽤ orderitem-add 保存订单快照
//3. 调⽤ stock-update 修改商品库存
//4. 调⽤ shopcart-del 删除购物⻋记录
return null;
}
}
7.3、案例流程图
7.4、Ribbon服务调⽤说明
@LoadBalanced 注解是 Ribbon 的⼊⼝,在 RestTemplate 对象上添加此注解之后,再使⽤ RestTemplate 发送 REST 请求的时候,就可以通过 Ribbon 根据服务名称从 Eureka 中查找服务对应的访问地址列表,再根据负载均衡策略(默认轮询)选择其中的⼀个,然后完成服务的调⽤
- 获取服务列表
- 根据负载均衡策略选择服务
- 完成服务调⽤
8、基于Ribbon进⾏服务调⽤的参数传递
8.1、RestTemplate发送调⽤请求的⽅法
SpringCloud 的服务调⽤是基于 REST 的,因此当服务提供者规定了请求的⽅式,服务消费者必须发送对应⽅式的请求才能完成服务的调⽤, RestTemplate 提供了多个⽅法⽤于发送不同形式的请求
//post⽅式请求
restTemplate.postForObject();
//get⽅式请求
restTemplate.getForObject();
//delete⽅式请求
restTemplate.delete();
//put⽅式请求
restTemplate.put();
8.2、put/post请求传参
服务消费者请求传参
//参数1:访问服务的url
//参数2:传递的对象参数
//参数3:指定服务提供者返回的数据类型
ResultVO vo = restTemplate.postForObject("http://order-add/order/add",
order, ResultVO.class);
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order){
return orderService.saveOrder(order);
}
8.3、get请求传参
服务消费者请求传参
String userId = order.getUserId();
ResultVO vo = restTemplate.getForObject("http://order-add/order/add?
userId="+userId, ResultVO.class);
@GetMapping("/add")
public ResultVO addOrder(Order order){
return orderService.saveOrder(order);
}
@GetMapping("/add")
public ResultVO addOrder(String userId){
//return orderService.saveOrder(order);
}
9、服务发现-Feign
9.1、基础配置
9.1.1、创建SpringBoot应⽤,添加依赖
- eureka server
- spring web
- OpenFeign
9.1.2、配置application.yml
server:
port: 8002
spring:
application:
name: api-order-add-feign
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
9.1.3、在启动类添加注解
@SpringBootApplication
@EnableDiscoveryClient //声明为服务消费者
@EnableFeignClients //声明启⽤feign客户端
public class ApiOrderAddFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddFeignApplication.class, args);
}
}
9.2 服务调⽤
使⽤Feign进⾏服务调⽤的时候,需要⼿动创建⼀个服务访问客户端(接⼝)
9.2.1 创建Feign客户端
@FeignClient("order-add")
public interface OrderAddClient {
@PostMapping("order/add")
public ResultVO addOrder(Order order);
}
9.2.2 使⽤Feign客户端调⽤服务
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private OrderAddClient orderAddClient;
@Override
public ResultVO saveOrder(Order order) {
//1. 调⽤ order-add服务进⾏保存
ResultVO vo = orderAddClient.addOrder(order);
//2. 调⽤ orderitem-add 保存订单快照
//3. 调⽤ stock-update 修改商品库存
//4. 调⽤ shopcart-del 删除购物⻋记录
return vo;
}
}
9.3 Feign传参
9.3.1 POST请求
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order){
System.out.println("-------------------order-add");
System.out.println(order);
return orderService.saveOrder(order);
}
服务消费者(Feign客户端)
@FeignClient("order-add")
public interface OrderAddClient {
@PostMapping("order/add")
public ResultVO addOrder(Order order);
}
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order,String str){
System.out.println("-------------------order-add");
System.out.println(order);
System.out.println(str);
return orderService.saveOrder(order);
}
服务消费者(Feign客户端)
//1.对⽤POST请求调⽤服务,Feign客户端的⽅法参数默认为body传值(body只能有⼀个
值)
//2.如果有多个参数,则需要通过@RequestParam声明参数为请求⾏传值
@PostMapping("order/add")
public ResultVO addOrder(Order order,@RequestParam("str") String str);
9.3.2 Get请求
Get 请求调⽤服务,只能通过 url 传参在 Feign 客户端的⽅法中,如果不指定参数的传值⽅式,则默认为 body 传参, Get 请求也不例外;因此对于 get 请求传递参数,必须通过 @RequestParam 注解声明
服务提供者
@GetMapping("/get")
public Order addOrder(String orderId){
return new Order();
}
服务消费者(Feign客户端)
@GetMapping("order/get")
public Order getOrder(@RequestParam("orderId") String orderId);
10、服务注册与发现中⼼的可靠性和安全性
10.1 可靠性
在微服务架构系统中,服务消费者是通过服务注册与发现中⼼发现服务、调⽤服务的,服务注册与发现中⼼服务器⼀旦挂掉,将会导致整个微服务架构系统的崩溃,如何保证Eureka 的可靠性呢?使⽤eureka 集群
Eureka集群搭建
相互注册、相互发现
## 设置服务注册与发现中⼼的端⼝
server:
port: 8761
## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的
## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名
spring:
application:
name: service-eureka
eureka:
client:
## 设置服务注册与发现中⼼是否为集群搭建
register-with-eureka: true
## 设置服务注册与发现中是否作为服务进⾏注册
fetch-registry: true
## ip 就是服务注册中⼼服务器的ip
## port 就是服务注册与发现中⼼设置的port
service-url:
defaultZone: http://192.168.54.10:8761/eureka
10.2 安全性
当完成 Eureka 的搭建之后,只要知道 ip 和 port 就可以随意的注册服务、调⽤服务,这是不安全的,我们可以通过设置帐号和密码来限制服务的注册及发现。在eureka 中整合 Spring Security 安全框架实现帐号和密码验证
10.2.1 添加SpringSecurity的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
10.2.2 设置访问eureka的帐号和密码
spring:
security:
user:
name: zhangsan
password: 123456
10.2.3 配置Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
//设置当前服务器的所有请求都要使⽤spring security的认证
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
}
10.2.4 服务提供者和服务消费者连接到注册中⼼都要帐号和密码
eureka:
client:
service-url:
defaultZone: http://zhangsan:123456@localhost:8761/eureka
11、熔断器-Hystrix
服务故障的雪崩效应:当 A 服务调⽤ B 服务时,由于 B 服务的故障导致 A 服务处于阻塞状态,当量的请求可能会导致 A 服务因资源耗尽⽽出现故障。
为了解决服务故障的雪崩效应,出现了熔断器模型。
11.1 熔断器介绍
熔断器作⽤:
-
服务降级 :⽤户请求 A 服务, A 服务调⽤ B 服务,当 B 服务出现故障或者在特定的时间段内不能给 A 服务响应,为了避免 A 服务因等待 B 服务⽽产⽣阻塞, A 服务就不等 B 服务的结果了,直接给⽤户⼀个降级响应
-
服务熔断 :⽤户请求 A 服务, A 服务调⽤ B 服务,当 B 服务出现故障的频率过⾼达到特定阈 值( 5s 20 次)时,当⽤户再请求 A 服务时, A 服务将不再调⽤ B 服务,直接给⽤户⼀个降 级响应
11.2 熔断器的原理
11.3 基于Ribbon服务调⽤的熔断器使⽤
11.3.1 服务消费者的 服务降级
添加熔断器依赖 hystrix
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在启动类添加 @EnableHystrix 注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class ApiOrderAddApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddApplication.class, args);
}
}
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallbackSaveOrder",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds
",value="3000")
})
public ResultVO saveOrder(Order order) {
//1. 调⽤ order-add服务进⾏保存
//参数1:访问服务的url
//参数2:传递的对象参数
//参数3:指定服务提供者返回的数据类型
ResultVO vo = restTemplate.postForObject("http://order-add/order/add",
order, ResultVO.class);
System.out.println(vo);
//2. 调⽤ orderitem-add 保存订单快照
//3. 调⽤ stock-update 修改商品库存
//4. 调⽤ shopcart-del 删除购物⻋记录
return vo;
}
/**
* 降级⽅法:与业务⽅法拥有相同的参数和返回值
* @return
*/
public ResultVO fallbackSaveOrder(Order order){
return ResultVO.fail("⽹络异常,请重试!",null);
}
}
11.3.2 服务提供者的 服务降级
- 配置步骤⼀致
- 服务提供者接⼝降级
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@HystrixCommand(fallbackMethod = "fallbackAddOrder",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
@PostMapping("/add")
public ResultVO addOrder(@RequestBody Order order){
System.out.println("-------------------order-add");
System.out.println(order);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return orderService.saveOrder(order);
}
public ResultVO fallbackAddOrder(@RequestBody Order order){
System.out.println("-------------------order-add--fallback");
return ResultVO.fail("订单保存失败!",null);
}
}
11.3.3 服务熔断配置
- 熔断器状态:闭合、打开、半开
- 服务熔断配置
@HystrixCommand(fallbackMethod =
"fallbackSaveOrder", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMillise
conds",value="3000"),
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
//启⽤服务熔断
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", v
alue="10000"),//时间
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", valu
e="10"),//请求次数
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", va
lue="50"),//服务错误率
})
public ResultVO saveOrder(Order order) {
//1. 调⽤ order-add服务进⾏保存
ResultVO vo = restTemplate.postForObject("http://orderadd/order/add", order, ResultVO.class);
System.out.println(vo);
//2. 调⽤ orderitem-add 保存订单快照
//3. 调⽤ stock-update 修改商品库存
//4. 调⽤ shopcart-del 删除购物⻋记录
return vo;
}
/**
* 降级⽅法:与业务⽅法拥有相同的参数和返回值
*
* @return
*/
public ResultVO fallbackSaveOrder(Order order) {
return ResultVO.fail("⽹络异常,请重试!", null);
}
服务熔断:当⽤户请求服务 A ,服务 A 调⽤服务 B 时,如果服务 B 的故障率达到特定的阈值时,熔断器就会被打开⼀个时间周期(默认 5s ,可⾃定义),在这个时间周期内如果⽤户请求服务 A ,服务 A 将不再调⽤服务 B ,⽽是直接响应降级服务。
11.4 基于Feign服务调⽤的熔断器使⽤
Feign是基于Ribbon和Hystrix的封装
11.4.1 Feign中的熔断器使⽤
添加依赖
SpringBoot 2.3.11 Spring Cloud H
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR11</spring-cloud.version>
</properties>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在application.yml启⽤熔断器机制
feign:
hystrix:
enabled: true
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class ApiOrderAddFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddFeignApplication.class,args);
}
}
FeignClient 的服务降级类: 1. 必须实现 Feign 客户端接⼝, 2. 必须交给 Spring 容器管理
@Component
public class OrderAddClientFallback implements OrderAddClient {
public ResultVO addOrder(Order order, String str) {
System.out.println("-------addOrder的降级服务");
return ResultVO.fail("fail",null);
}
public Order getOrder(String orderId) {
System.out.println("-------getOrder的降级服务");
return new Order();
}
}
在Feign客户端指定降级处理类
@FeignClient(value = "order-add", fallback = OrderAddClientFallback.class)
public interface OrderAddClient {
//1.对⽤POST请求调⽤服务,Feign客户端的⽅法参数默认为body传值(body只能有⼀个值)
//2.如果有多个参数,则需要通过@RequestParam声明参数为请求⾏传值
@PostMapping("order/add")
public ResultVO addOrder(Order order, @RequestParam("str") String str);
@GetMapping("order/get")
public Order getOrder(@RequestParam("orderId") String orderId);
}
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private OrderAddClient orderAddClient;
//当我们创建Feign客户端的降级类并交给Spring管理后 在Spring容器中就会出现
两个OrderAddClient对象
@Override
public ResultVO saveOrder(Order order) {
//1. 调⽤ order-add服务进⾏保存
ResultVO vo = orderAddClient.addOrder(order,"测试字符串");
Order order1 = orderAddClient.getOrder("订单编号");
System.out.println(order1);
//2. 调⽤ orderitem-add 保存订单快照
//3. 调⽤ stock-update 修改商品库存
//4. 调⽤ shopcart-del 删除购物⻋记录
return vo;
}
}
11.4.2 Ribbon 参数配置
ribbon:
## Ribbon建⽴连接最⼤等待时间
ConnectTimeout: 1000
## 在当前服务提供者尝试连接次数
MaxAutoRetries: 2
## 与服务提供者通信时间
ReadTimeout: 5000
## 设置熔断器服务降级时间 (默认 1000)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 8000
11.5 熔断器仪表盘监控
如果服务器的并发压⼒过⼤、服务器⽆法正常响应,则熔断器状态变为 open 属于正常的情况;但是如果⼀个熔断器⼀直处于 open 状态、或者说服务器提供者没有访问压⼒的情况下熔断器依然为 open 状态,说明熔断器的状态就不属于正常情况了。如何了解熔断器的⼯作状态呢 ?熔断器仪表盘
11.5.1 搭建熔断器仪表盘
创建SpringBoot项⽬,添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
配置仪表盘的port和appName
server:
port: 9999
spring:
application:
name: hystrix-dashboard
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class,args);
}
}
访问 http://localhost:9999/hystrix
11.5.2 配置使⽤了熔断器的服务可被监控
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置
@Configuration
public class DashBoardConfig {
@Bean
public ServletRegistrationBean getServletRegistrationBean(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setName("HystrixMetricsStreamServlet");
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
return registrationBean;
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/d6288339f3e2e0567ab2b9d0c81865f7.png)
12、服务链路追踪
12.1、服务追踪说明
微服务架构是通过业务来划分服务的,使⽤REST 调⽤。对外暴露的⼀个接⼝,可能需要很多个服务协同才能完成这个接⼝功能,如果链路上任何⼀个服务出现问题或者⽹络超时,都会形成导致接⼝调⽤失败。
![](https://i-blog.csdnimg.cn/blog_migrate/375febe767dc1de36861410e82ff84da.png)
12.2、Zipkin
-
ZipKin 是⼀个开放源代码的分布式跟踪系统,由 Twitter 公司开源,它致⼒于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。它的理论模型来⾃于 Google Dapper 论⽂。
-
每个服务向 ZipKin 报告计时数据, ZipKin 会根据调⽤关系通过 ZipKin UI ⽣成依赖关系 图,显示了多少跟踪请求通过每个服务,该系统让开发者可通过⼀个 Web 前端轻松的收集和分析数据,例如⽤户每次请求服务的处理时间等,可⽅便的监测系统中存在的瓶颈。
12.3、搭建zipkin服务器
- 创建SpringBoot项⽬(版本2.1.x)
- 添加依赖
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>2.11.10</version>
</dependency>
<!--zipkin界⾯-->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.11.10</version>
</dependency>
@SpringBootApplication
@EnableZipkinServer
public class ZipkinApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinApplication.class, args);
}
}
spring:
application:
name: zipkin
server:
port: 9411
management:
endpoints.web.exposure.include: '*'
metrics.web.server.auto-time-requests: false
13.4、服务中Sleuth配置
在服务应⽤中添加Sleuth依赖
<!-- spring-cloud-sleuth-zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
spring:
application:
name: goods-provider
zipkin:
enabled: true
base-url: http://localhost:9411
//调用几次生成一次调用图,0.1为10次生成一次
sleuth:
sampler:
probability: 0.1
12.5、zipkin服务数据存储
创建数据库数据表
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT "Span.timestamp(): epoch micros used
for endTs query and to implement TTL",
`duration` BIGINT COMMENT "Span.duration(): micros used for
minDuration and maxDuration query"
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id`, `id`) COMMENT
"ignore insert on duplicate";
ALTER TABLE zipkin_spans ADD INDEX(`trace_id`, `id`) COMMENT "for
joining with zipkin_annotations";
ALTER TABLE zipkin_spans ADD INDEX(`trace_id`) COMMENT "for
getTracesByIds";
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT "for getTraces
and getSpanNames";
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT "for
getTraces ordering and range";
CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id` BIGINT NOT NULL COMMENT "coincides with
zipkin_spans.trace_id",
`span_id` BIGINT NOT NULL COMMENT "coincides with
zipkin_spans.id",
`a_key` VARCHAR(255) NOT NULL COMMENT "BinaryAnnotation.key or
Annotation.value if type == -1",
`a_value` BLOB COMMENT "BinaryAnnotation.value(), which must be
smaller than 64KB",
`a_type` INT NOT NULL COMMENT "BinaryAnnotation.type() or -1 if
Annotation",
`a_timestamp` BIGINT COMMENT "Used to implement TTL;
Annotation.timestamp or zipkin_spans.timestamp",
`endpoint_ipv4` INT COMMENT "Null when
Binary/Annotation.endpoint is null",
`endpoint_ipv6` BINARY(16) COMMENT "Null when
Binary/Annotation.endpoint is null, or no IPv6 address",
`endpoint_port` SMALLINT COMMENT "Null when
Binary/Annotation.endpoint is null",
`endpoint_service_name` VARCHAR(255) COMMENT "Null when
Binary/Annotation.endpoint is null"
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id`,
`span_id`, `a_key`, `a_timestamp`) COMMENT "Ignore insert on
duplicate";
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`)
COMMENT "for joining with zipkin_spans";
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`) COMMENT "for
getTraces/ByIds";
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`)
COMMENT "for getTraces and getServiceNames";
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT "for
getTraces";
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT "for
getTraces";
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`,
`child`);
<!-- zipkin-storage-mysql-v1 -->
<dependency>
<groupId>io.zipkin.zipkin2</groupId>
<artifactId>zipkin-storage-mysql-v1</artifactId>
<version>2.11.12</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
spring:
application:
name: zipkin
datasource:
username: root
password: admin123
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/zipkin
zipkin:
storage:
type: mysql
13、微服务架构总结
14、分布式配置中⼼
在使⽤微服务架构开发的项⽬中,每个服务都有⾃⼰的配置⽂件( application.yml ) , 如果将每个服务的配置⽂件直接写在对应的服务中,存在以下问题:1. 服务开发完成之后,需要打包部署,配置⽂件也会打包在 jar ⽂件中,不便于项⽬部署之后的配置修改(在源码中修改 —— 重新打包 —— 重新上传 —— 重新运⾏)2. 微服务架构中服务很多,配置⽂件也很多,分散在不同服务中不便于配置的管理3. 如果想要对服务进⾏集群部署,需要打包多个 jar ⽂件,上传,运⾏
14.1、分布式配置中⼼介绍
14.2、分布式配置中⼼搭建
步骤:
- 创建⼀个Git远程仓库,⽤来存放配置⽂件
- 搭建分布式配置中⼼服务器(Spring Cloud Config)Config server:连接到配置⽂件的Git仓库,注册到eureka
- 修改每个服务,删除application.yml中的所有配置,连接到分布式配置中⼼
14.2.1、创建Git远程仓库
-
在本地 D 盘创建 blog-config ⽬录,作为本地存放配置⽂件的⽬录,在⽬录中创建 files⽬录
-
使⽤ idea 打开 blog-config ⽬录
![](https://i-blog.csdnimg.cn/blog_migrate/c42a25c1d0c977dc24c90cbbc1a0739b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/07c6d06bbcdeeca0d014c859b0a8f1e9.png)
将 blog-config 创建成git仓库(本地仓库)
![](https://i-blog.csdnimg.cn/blog_migrate/4c3df645e54604cb73ec93691fb4ac9c.png)
14.2.2、搭建分布式配置中⼼服务器
创建SpringBoot应⽤,添加依赖
配置application.yml
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/tang-fuqin/blog-config.git
search-paths: files
username: 18278492591
password: 5201314tfq
eureka:
client:
serviceUrl:
defaultZone: http://zhangsan:123456@localhost:8761/eureka
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/b64f0211217564041e256f725df34f24.png)
14.2.3、配置服务,通过分布式配置中⼼加载配置⽂件
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
spring:
cloud:
config:
uri: http://localhost:8888
name: api-order-submit
label: master
14.3 避免每次更新配置都要重启客户端微服务
1、在客户端中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、修改YML,暴露监控端口,添加以下
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
3、在controller类上添加注解@RefreshScope
4、需要运维人员发送Post请求刷新客户端(cmd):curl -X POST "http://localhost:3355/actuator/refresh"
此方法只能一次刷新一个微服务,若有上百个微服务就要刷新上百次,所以spring cloud bus消息总线就是解决一次刷新所有的问题
15、SpringCloud Bus消息总线
15.1 概述
分布式自动刷新配置功能,Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。
Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,
它整合了Java的事件处理机制和消息中间件的功能。
Spring Clud Bus目前支持RabbitMQ和Kafka。
能干嘛?
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。
为何被称为总线?
什么是总线?
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
基本原理
ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。
15.2 RabbitMQ环境配置
1、安装Erlang,下载地址:http://erlang.org/download/otp_win64_21.3.exe
2、安装RabbitMQ,下载地址:https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe
3、进入RabbitMQ安装目录下的sbin目录,输入以下命令启动管理功能:rabbitmq-plugins enable rabbitmq_management
可视化插件
4、访问地址查看是否安装成功:http://localhost:15672/
输入账号密码并登录:guest guest
15.3、SpringCloud Bus动态刷新全局广播
演示广播效果,增加复杂度,给其中一个客户端添加一个集群
新建:cloud-config-client-3366
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mscloud03</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-3366</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
server:
port: 3366
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 #配置中心地址
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @auther zzyy
* @create 2020-02-08 11:09
*/
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3366
{
public static void main(String[] args)
{
SpringApplication.run(ConfigClientMain3366.class,args);
}
}
package com.atguigu.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @auther zzyy
* @create 2020-02-08 11:11
*/
@RestController
@RefreshScope
public class ConfigClientController
{
@Value("${server.port}")
private String serverPort;
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String configInfo()
{
return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo;
}
}
15.3.1、设计思想
1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
图二的架构显然更加适合,图一不适合的原因如下:
- 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
- 破坏了微服务各节点的对等性。
- 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
15.3.2、给服务端配置中心服务端添加消息总线支持
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: git@github.com:zzyybs/springcloud-config.git #GitHub上面的git仓库名字
####搜索目录
search-paths:
- springcloud-config
####读取分支
label: master
#rabbitmq相关配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
##rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
yml文件即添加rabbitmq相关配置,暴露bus刷新配置的端点
15.3.3、给两个客户端添加消息总线支持
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
给yml添加配置:rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取
uri: http://localhost:3344 #配置中心地址k
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*" # 'refresh'
15.3.5、测试
1、运维工程师:
1.1、修改Github上配置文件增加版本号
1.2、给服务端发送POST请求:curl -X POST "http://localhost:3344/actuator/bus-refresh"
一次发送,处处生效
15.4、SpringCloud Bus动态刷新定点通知
1、不想全部通知,只想定点通知:只通知3355,不通知3366
2、简单一句话 :指定具体某一个实例生效而不是全部
公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并
通过destination参数类指定需要更新配置的服务或实例
3、案例:我们这里以刷新运行在3355端口上的config-client为例
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
通知总结All
16、SpringCloud Stream 消息驱动
16.1、消息驱动概述
什么是SpringCloudStream
官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。
应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream中binder对象交互。
通过我们配置来binding(绑定) ,而 Spring Cloud Stream 的 binder对象负责与消息中间件交互。
所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
目前仅支持RabbitMQ、Kafka。Spring Cloud Stream中文指导手册:Spring Cloud Stream中文指导手册
16.1.1、设计思想
标准MQ
生产者/消费者之间靠消息媒介传递信息内容:Message
消息必须走特定的通道:消息通道MessageChannel
消息通道里的消息如何被消费呢,谁负责收发处理:消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅
为什么用Cloud Stream
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区,
这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。
(当一个平台同时使用到java后端和大数据分析时,所使用的消息中间件往往是不一样的)
stream凭什么可以统一底层差异?
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder:
Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。
Stream中的消息通信方式遵循了发布-订阅模式:
Topic主题进行广播,在RabbitMQ就是Exchange,在Kakfa中就是Topic
16.1.2、Spring Cloud Stream标准流程套路
Binder:很方便的连接中间件,屏蔽差异
Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。
16.1.3、编码API和常用注解
16.4、案例说明
工程中新建三个子模块
cloud-stream-rabbitmq-provider8801, 作为生产者进行发消息模块
cloud-stream-rabbitmq-consumer8802,作为消息接收模块
cloud-stream-rabbitmq-consumer8803 作为消息接收模块
16.4.1、消息驱动之生产者
新建Module:cloud-stream-rabbitmq-provider8801
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mscloud</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-stream-rabbitmq-provider8801</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<!--基础配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
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地址
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
/**
* @auther zzyy
* @create 2020-02-08 15:45
*/
@SpringBootApplication
public class StreamMQMain8801
{
public static void main(String[] args)
{
SpringApplication.run(StreamMQMain8801.class,args);
}
}
业务类
发送消息接口
package com.atguigu.springcloud.service;
/**
* @auther zzyy
* @create 2020-02-09 8:30
*/
public interface IMessageProvider
{
public String send() ;
}
发送消息接口实现类
package com.atguigu.springcloud.service.impl;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
import javax.annotation.Resource;
import org.springframework.cloud.stream.messaging.Source;
import java.util.UUID;
/**
* @auther zzyy
* @create 2020-02-09 8:31
*/
@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class MessageProviderImpl implements IMessageProvider
{
@Resource
private MessageChannel output; // 消息的发送管道
@Override
public String send()
{
String serial = UUID.randomUUID().toString();
this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
System.out.println("***serial: "+serial);
return serial;
}
}
Controller
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;
/**
* @auther zzyy
* @create 2020-02-08 15:47
*/
@RestController
public class SendMessageController
{
@Resource
private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage()
{
return messageProvider.send();
}
}
测试
启动7001eureka--》启动rabbitmq:rabbitmq-plugins enable rabbitmq_management,访问:http://localhost:15672/--》启动8801==》访问http://localhost:8801/sendMessage
16.4.2、消息驱动之消费者
新建Module:cloud-stream-rabbitmq-consumer8802
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mscloud</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-stream-rabbitmq-provider8801</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--基础配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
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地址
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @auther zzyy
* @create 2020-02-08 15:57
*/
@SpringBootApplication
public class StreamMQMain8802
{
public static void main(String[] args)
{
SpringApplication.run(StreamMQMain8802.class,args);
}
}
业务类
package com.atguigu.springcloud.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
/**
* @auther zzyy
* @create 2020-02-09 9:30
*/
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListener
{
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message)
{
System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
}
}
测试8801发送8802接收消息:http://localhost:8801/sendMessage
16.5、分组消费与持久化
依照8802,clone出来一份运行8803
启动:RabbitMQ==》7001服务注册==》8801消息生产==》8802消息消费==》8803消息消费
运行后有两个问题:有重复消费问题、消息持久化问题
消费
目前是8802/8803同时都收到了,存在重复消费问题
http://localhost:8801/sendMessage
8802
8803
如何解决
分组和持久化属性group
生产实际案例
比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,
那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。
这时我们就可以使用Stream中的消息分组来解决
注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
不同组是可以全面消费的(重复消费),
同一组内会发生竞争关系,只有其中一个可以消费。
分组
8802/8803都变成不同组,group两个不同
group: atguiguA、atguiguB
8802修改YML
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguA
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地址
8803修改YML
server:
port: 8803
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguB
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-8803.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
我们自己配置
分布式微服务应用为了实现高可用和负载均衡,实际上都会部署多个实例,本例阳哥启动了两个消费微服务(8802/8803)
多数情况,生产者发送消息给某个具体微服务时只希望被消费一次,按照上面我们启动两个应用的例子,虽然它们同属一个应用,
但是这个消息出现了被重复消费两次的情况。为了解决这个问题,在Spring Cloud Stream中提供了消费组的概念。
结论:还是重复消费
8802/8803实现了轮询分组,每次只有一个消费者
8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。
8802/8803都变成相同组,group两个相同:
group: atguiguA
8802修改YML
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguA
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地址
8803修改YML
server:
port: 8803
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguA
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-8803.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
结论:同一个组的多个微服务实例,每次只会有一个拿到
持久化
停止8802/8803并去除掉8802的分组group: atguiguA,8803的分组group: atguiguA没有去掉
8801先发送4条消息到rabbitmq,先启动8802,无分组属性配置,后台没有打出来消息
再启动8803,有分组属性配置,后台打出来了MQ上的消息