⼀、单体应用存在的问题

1.1 锋迷商城项目架构

为了解决高并发问题,我们的Tomcat采用了集群部署,但是每个Tomcat节点上不是的依然是单体项目(虽然
是前后端分离,但是后端采用的是单体开发——所有的接口都在同⼀个项目中)

15、SpringCloud - 图1

1.2 单体项目存在的问题

⼀个成功的应用必然有⼀个趋势:用户量会不断增加、项目的业务也会不断的扩展

用户量的增加会带来高并发的问题,高并发问题解决方案:

  • 应用服务器 —> 单体优化 —> 集群(负载均衡、分布式并发)
  • 数据库服务器 —> 数据库优化 —> 缓存redis —> 分布式数据库

项目业务的扩展,也会带来⼀些问题:

  • 项目结构越来越臃肿(项目结构和代码复杂、项目体积逐渐变得庞⼤)
  • 项目结构和代码复杂导致项目不易维护和⼆次开发、扩展和更新就会变得困难
  • 项目体积逐渐变得庞大导致启动时间越来越长、生产力大受限制
  • 单体应用中任何⼀个模块的任何⼀个bug都会导致整个系统不可用(单点故障)
  • 复杂的单体项目也会带来持续部署的障碍
  • 单体项目使得采用新的技术和框架会变得困难

⼆、微服务架构

2.1 微服务架构概念

微服务架构是⼀种架构概念,就是将⼀个单体应用中的每个功能分解到各个离散的服务中以实现对单体应用
的解耦,并提供更加灵活的服务支持

从单体到微服务

15、SpringCloud - 图2

2.2 微服务架构优点

  • 解决了单体项⽬的复杂性问题
  • 每个服务都可以由单独的团队进⾏开发
  • 每个服务都可以使⽤单独的技术栈进⾏开发
  • 每个服务都是独⽴的进⾏部署和维护
  • 每个服务都可以独⽴进⾏扩展

2.3 微服务架构缺点

  • 微服务架构本身就是⼀个缺点:如何把握“微”的粒度;
  • 微服务架构是⼀个分布式系统,虽然单个服务变得简单了,但是服务之间存在相互的调⽤,整个服务架构的
  • 系统变得复杂了;
  • 微服务架构需要依赖分布式数据库架构;
  • 微服务的单元测试及调⽤变得⽐单体更为复杂;
  • 部署基于微服务架构的应⽤程序变得⾮常复杂;
  • 进⾏微服务架构的应⽤程序开发的技术成本变得更⾼。

三、微服务架构开发需要解决的问题

在微服务架构开发的系统中必然存在很多个服务,服务之间需要相互感知对方的存在,需要进⾏服务间的调
⽤,该如何实现呢? —— 进⾏微服务架构开发需要解决的问题:

通过服务注册与发现中心实现服务间的相互发现

  1. 如此多的服务,服务间如何相互发现?
  2. 服务与服务之间该如何通信?
  3. 如果某个服务挂了,该如何处理?
  4. 前端访问多个不同的服务时该如何统⼀访问路径呢?

3.1 服务之间如何相互发现?

微服务架构——每个服务只处理⼀件事情/⼀个步骤,在⼀个复杂的业务中必然会存在服务间的相互调用,服
务想要相互调用就需要先发现对⽅。

通过服务注册与发现中心实现服务间的相互发现
image.png
  • 服务注册与发现中心也是⼀台独立服务器
  • 服务提供者在服务注册与发现中心进行注册
  • 服务注册与发现中心进行服务记录,并与服务提供者保持心跳
  • 服务消费者通过服务注册与发现中心进行服务查询(服务发现)
  • 服务注册与发现中心返回可用的服务的服务器地址列表
  • 服务消费者通过负载均衡访问服务提供者

3.2 服务之间如何进行通信?

服务消费者在调用服务提供者时,首先需要通过服务注册与发现中心进行服务服务查询,返回服务列表给服
务消费者, 服务消费者 通过LoadBalance调用 服务提供者 , 那么他们之间是如何通信呢? —— 数据传输规则

服务与服务间的通信方式有2种:同步调用 和 异步调用

3.2.1 同步调用

  • REST(SpringCloud Netflix, SpringCloud Alibaba)
    • 基于HTTP协议的请求和响应
    • 更容易实现、技术更灵活
    • ⽀持多语⾔、同时可以实现跨客户端
    • 适⽤⾯很⼴
  • RPC(Dubbo)
    • 基于⽹络层协议通信
    • 传输效率⾼
    • 安全性更⾼
    • 如果有统⼀的开发规划或者框架,开发效率是⽐较⾼的

3.2.2 异步调用

  • 服务间的异步通信通常是通过消息队列实现的

3.3 服务挂了该如何解决?

3.3.1 服务故障雪崩

image.png

3.3.2 如何解决服务故障雪崩

  • 服务集群——尽量保证每个服务可⽤
  • 服务降级与熔断——避免请求阻塞造成正常的服务出现故障

3.4 客户端如何统一 访问多个接口服务?

通过路由网关实现接口的统一访问
image.png

四、微服务架构框架

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 网关组件
  • Spring Cloud Config 配置管理的组件—分布式配置中心
  • Spring Cloud Bus 消息总线
  • Spring Cloud Consul 服务注册与发现中心(功能类似eureka)

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

五、搭建服务注册与发现中心

使⽤Spring Cloud Netflix 中的 Eureka 搭建服务注册与发现中心

5.1 创建SpringBoot应⽤,添加依赖

  • spring web
  • eureka server

5.2 配置服务注册与发现中心

  1. ## 设置服务注册与发现中⼼的端⼝
  2. server:
  3. port: 8761
  4. ## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的
  5. ## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名
  6. spring:
  7. application:
  8. name: service-eureka
  9. eureka:
  10. client:
  11. ## ip 就是服务注册中⼼服务器的ip
  12. ## port 就是服务注册与发现中⼼设置的port
  13. service-url:
  14. defaultZone: http://192.168.54.59:8761/eureka
  15. ## 设置服务注册与发现中⼼是否为为集群搭建(如果为集群模式,多个eureka节点之间需要相互注册)
  16. register-with-eureka: false
  17. ## 设置服务注册与发现中是否作为服务进⾏注册
  18. fetch-registry: false

5.3 在启动类添加 @EnableEurekaServer 注解

  1. @SpringBootApplication
  2. @EnableEurekaServer
  3. public class ServiceEurekaApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ServiceEurekaApplication.class, args);
  6. }
  7. }

5.4 运行及访问

image.png

六、服务注册

创建保存订单的服务(order-add)注册到服务注册与发现中⼼

6.1 创建SpringBoot应用

创建spring boot应用,完成功能开发

6.2 注册服务

将能够完成特定业务的SpringBoot应用作为服务提供者,注册到服务注册与发现中心

6.2.1 添加依赖

  • eureka-server [注意版本!]
  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  4. </dependency>

6.2.2 配置application.yml

  1. ## 当前服务的port
  2. server:
  3. port: 9001
  4. ## 当前应⽤名会作为服务唯⼀标识注册到eureka
  5. spring:
  6. application:
  7. name: order-add
  8. datasource:
  9. driver-class-name: com.mysql.jdbc.Driver
  10. url: jdbc:mysql://localhost:3306/db_2010_sc?characterEncoding=utf-8
  11. username: root
  12. password: admin123
  13. mybatis:
  14. mapper-locations: classpath:mappers/*
  15. type-aliases-package: com.qfedu.order.beans
  16. ## 配置Eureka服务注册与发现中⼼的地址
  17. eureka:
  18. client:
  19. service-url:
  20. defaultZone: http://localhost:8761/eureka

6.2.3 在当前服务应用的启动类添加 @EnableEurekaClient 注解

  1. @SpringBootApplication
  2. @MapperScan("com.qfedu.order.dao")
  3. @EnableEurekaClient
  4. public class OrderAddApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(OrderAddApplication.class, args);
  7. }
  8. }

七、服务发现-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

  1. server:
  2. port: 8001
  3. spring:
  4. application:
  5. name: api-order-add
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://localhost:8761/eureka

7.1.3 在启动类添加 @EnableDiscoveryClient注解

  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. public class ApiOrderAddApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ApiOrderAddApplication.class, args);
  6. }
  7. }

7.2 服务调用

7.2.1 配置RestTemplate

  1. @Configuration
  2. public class AppConfig {
  3. @LoadBalanced //启⽤Ribbon(负载均衡)
  4. @Bean
  5. public RestTemplate getRestTemplate(){
  6. return new RestTemplate();
  7. }
  8. }

7.2.2 在Service中注入RestTemplate对象调用服务

  1. @Service
  2. public class OrderAddServiceImpl implements OrderAddService {
  3. @Autowired
  4. private RestTemplate restTemplate;
  5. @Override
  6. public ResultVO saveOrder(Order order) {
  7. //1. 调⽤ order-add服务进⾏保存
  8. ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order,ResultVO.class);
  9. //2. 调⽤ orderitem-add 保存订单快照
  10. //3. 调⽤ stock-update 修改商品库存
  11. //4. 调⽤ shopcart-del 删除购物⻋记录
  12. return null;
  13. }
  14. }

7.3 案例流程图

服务注册与发现-案例流程图
image.png

7.4 Ribbon服务调用说明

@LoadBalanced注解是Ribbon的⼊⼝,在RestTemplate对象上添加此注解之后,再使用RestTemplate发送REST
请求的时候,就可以通过Ribbon根据服务名称从Eureka中查找服务对应的访问地址列表,再根据负载均衡策
略(默认轮询)选择其中的⼀个,然后完成服务的调用

  • 获取服务列表
  • 根据负载均衡策略选择服务
  • 完成服务调用

八、基于Ribbon进行服务调用的参数传递

8.1 RestTemplate发送调用请求的方法

SpringCloud的服务调⽤是基于REST的,因此当服务提供者规定了请求的方式,服务消费者必须发送对应方式
的请求才能完成服务的调⽤, RestTemplate提供了多个⽅法⽤于发送不同形式的请求

  1. //post⽅式请求
  2. restTemplate.postForObject();
  3. //get⽅式请求
  4. restTemplate.getForObject();
  5. //delete⽅式请求
  6. restTemplate.delete();
  7. //put⽅式请求
  8. restTemplate.put();

8.2 put/post请求传参

  • 服务消费者请求传参

    1. //参数1:访问服务的url
    2. //参数2:传递的对象参数
    3. //参数3:指定服务提供者返回的数据类型
    4. ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order,ResultVO.class);
  • 服务提供者接收参数

    1. @PostMapping("/add")
    2. public ResultVO addOrder(@RequestBody Order order){
    3. return orderService.saveOrder(order);
    4. }

8.3 get请求传参

  • 服务消费者请求传参

    1. String userId = order.getUserId();
    2. ResultVO vo = restTemplate.getForObject("http://order-add/order/add?userId="+userId,
    3. ResultVO.class);
  • 服务提供者接收参数

    1. @GetMapping("/add")
    2. public ResultVO addOrder(Order order){
    3. return orderService.saveOrder(order);
    4. }
    5. @GetMapping("/add")
    6. public ResultVO addOrder(String userId){
    7. //return orderService.saveOrder(order);
    8. }

九、服务发现-Feign

9.1 基础配置

9.1.1 创建SpringBoot应用,添加依赖

  • spring web
  • eureka server
  • OpenFeign

9.1.2 配置application.yml

  1. server:
  2. port: 8002
  3. spring:
  4. application:
  5. name: api-order-add-feign
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://localhost:8761/eureka

9.1.3 在启动类添加注解

  1. @SpringBootApplication
  2. @EnableDiscoveryClient //声明为服务消费者
  3. @EnableFeignClients //声明启⽤feign客户端
  4. public class ApiOrderAddFeignApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(ApiOrderAddFeignApplication.class, args);
  7. }
  8. }

9.2 服务调用

使⽤Feign进⾏服务调⽤的时候,需要⼿动创建⼀个服务访问客户端(接⼝)

9.2.1 创建Feign客户端

  1. @FeignClient("order-add")
  2. public interface OrderAddClient {
  3. @PostMapping("order/add")
  4. public ResultVO addOrder(Order order);
  5. }

9.2.2 使用Feign客户端调用服务

  1. @Service
  2. public class OrderAddServiceImpl implements OrderAddService {
  3. @Autowired
  4. private OrderAddClient orderAddClient;
  5. @Override
  6. public ResultVO saveOrder(Order order) {
  7. //1. 调⽤ order-add服务进⾏保存
  8. ResultVO vo = orderAddClient.addOrder(order);
  9. //2. 调⽤ orderitem-add 保存订单快照
  10. //3. 调⽤ stock-update 修改商品库存
  11. //4. 调⽤ shopcart-del 删除购物⻋记录
  12. return vo;
  13. }
  14. }

9.3 Feign传参

9.3.1 POST请求

  • 通过请求体传递对象

    • 服务提供者

      1. @PostMapping("/add")
      2. public ResultVO addOrder(@RequestBody Order order){
      3. System.out.println("-------------------order-add");
      4. System.out.println(order);
      5. return orderService.saveOrder(order);
      6. }
    • 服务消费者(Feign客户端)

      1. @FeignClient("order-add")
      2. public interface OrderAddClient {
      3. @PostMapping("order/add")
      4. public ResultVO addOrder(Order order);
      5. }
  • 通过请求行传参

    • 服务提供者

      1. @PostMapping("/add")
      2. public ResultVO addOrder(@RequestBody Order order,String str){
      3. System.out.println("-------------------order-add");
      4. System.out.println(order);
      5. System.out.println(str);
      6. return orderService.saveOrder(order);
      7. }
    • 服务消费者(Feign客户端)

      1. //1.对⽤POST请求调⽤服务, Feign客户端的⽅法参数默认为body传值(body只能有⼀个值)
      2. //2.如果有多个参数,则需要通过@RequestParam声明参数为请求⾏传值
      3. @PostMapping("order/add")
      4. public ResultVO addOrder(Order order,@RequestParam("str") String str);

9.3.2 Get请求

Get请求调用服务,只能通过url传参

在Feign客户端的方法中,如果不指定参数的传值⽅式,则默认为body传参, Get请求也不例外;因此对于get
请求传递参数,必须通过@RequestParam注解声明

  • 服务提供者

    1. @GetMapping("/get")
    2. public Order addOrder(String orderId){
    3. return new Order();
    4. }
  • 服务消费者(Feign客户端)

    1. @GetMapping("order/get")
    2. public Order getOrder(@RequestParam("orderId") String orderId);

十、服务注册与发现中心的可靠性和安全性

10.1 可靠性

在微服务架构系统中,服务消费者是通过服务注册与发现中⼼发现服务、调用服务的,服务注册与发现中心
服务器⼀旦挂掉,将会导致整个微服务架构系统的崩溃,如何保证Eureka的可靠性呢?

  • 使用eureka集群

Eureka集群搭建

相互注册、相互发现

  1. ## 设置服务注册与发现中⼼的端⼝
  2. server:
  3. port: 8761
  4. ## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的
  5. ## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名
  6. spring:
  7. application:
  8. name: service-eureka
  9. eureka:
  10. client:
  11. ## 设置服务注册与发现中⼼是否为集群搭建
  12. register-with-eureka: true
  13. ## 设置服务注册与发现中是否作为服务进⾏注册
  14. fetch-registry: true
  15. ## ip 就是服务注册中⼼服务器的ip
  16. ## port 就是服务注册与发现中⼼设置的port
  17. service-url:
  18. defaultZone: http://192.168.54.10:8761/eureka

10.2 安全性

当完成Eureka的搭建之后,只要知道ip和port就可以随意的注册服务、调⽤服务,这是不安全的,我们可以
通过设置帐号和密码来限制服务的注册及发现。

在eureka中整合Spring Security安全框架实现帐号和密码验证

10.2.1 添加SpringSecurity的依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>

10.2.2 设置访问eureka的帐号和密码

  1. spring:
  2. security:
  3. user:
  4. name: zhangsan
  5. password: 123456

10.2.3 配置Spring Security

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  4. @Override
  5. protected void configure(HttpSecurity http) throws Exception {
  6. http.csrf().disable();
  7. //设置当前服务器的所有请求都要使⽤spring security的认证
  8. http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
  9. }
  10. }

10.2.4 服务提供者和服务消费者连接到注册中心都要帐号和密码

  1. eureka:
  2. client:
  3. service-url:
  4. defaultZone: http://zhangsan:123456@localhost:8761/eureka

十一、熔断器-Hystrix

服务故障的雪崩效应:当A服务调⽤B服务时,由于B服务的故障导致A服务处于阻塞状态,当量的请求可能会
导致A服务因资源耗尽⽽出现故障。

为了解决服务故障的雪崩效应,出现了熔断器模型。

11.1 熔断器介绍

image.png

熔断器作用 :

  • **服务降级**:⽤户请求A服务, A服务调⽤B服务,当B服务出现故障或者在特定的时间段内不能给A服务响应,
    为了避免A服务因等待B服务⽽产⽣阻塞, A服务就不等B服务的结果了,直接给⽤户⼀个降级响应
  • **服务熔断**:⽤户请求A服务, A服务调⽤B服务,当B服务出现故障的频率过⾼达到特定阈值(5s 20次)时,当
    ⽤户再请求A服务时, A服务将不再调⽤B服务,直接给⽤户⼀个降级响应

11.2 熔断器的原理

image.png

11.3 基于Ribbon服务调用的熔断器使用

11.3.1 服务消费者的服务降级

  • 添加熔断器依赖 hystrix

    1. <dependency>
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    4. </dependency>
  • 在启动类添加 @EnableHystrix 注解

    1. @SpringBootApplication
    2. @EnableDiscoveryClient
    3. @EnableHystrix
    4. public class ApiOrderAddApplication {
    5. public static void main(String[] args) {
    6. SpringApplication.run(ApiOrderAddApplication.class, args);
    7. }
    8. }
  • 在调⽤服务提供者的业务处理⽅法中,进⾏降级配置

    1. @Service
    2. public class OrderAddServiceImpl implements OrderAddService {
    3. @Autowired
    4. private RestTemplate restTemplate;
    5. @HystrixCommand(fallbackMethod = "fallbackSaveOrder",commandProperties = {
    6. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
    7. })
    8. public ResultVO saveOrder(Order order) {
    9. //1. 调⽤ order-add服务进⾏保存
    10. //参数1:访问服务的url
    11. //参数2:传递的对象参数
    12. //参数3:指定服务提供者返回的数据类型
    13. ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order, ResultVO.class);
    14. System.out.println(vo);
    15. //2. 调⽤ orderitem-add 保存订单快照
    16. //3. 调⽤ stock-update 修改商品库存
    17. //4. 调⽤ shopcart-del 删除购物⻋记录
    18. return vo;
    19. }
    20. /**
    21. * 降级⽅法:与业务⽅法拥有相同的参数和返回值
    22. * @return
    23. */
    24. public ResultVO fallbackSaveOrder(Order order){
    25. return ResultVO.fail("⽹络异常,请重试!",null);
    26. }
    27. }

11.3.2 服务提供者的服务降级

  • 配置步骤⼀致
  • 服务提供者接口降级
  1. @RestController
  2. @RequestMapping("/order")
  3. public class OrderController {
  4. @Autowired
  5. private OrderService orderService;
  6. @HystrixCommand(fallbackMethod = "fallbackAddOrder",commandProperties = {
  7. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
  8. })
  9. @PostMapping("/add")
  10. public ResultVO addOrder(@RequestBody Order order){
  11. System.out.println("-------------------order-add");
  12. System.out.println(order);
  13. try {
  14. Thread.sleep(5000);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. return orderService.saveOrder(order);
  19. }
  20. public ResultVO fallbackAddOrder(@RequestBody Order order){
  21. System.out.println("-------------------order-add--fallback");
  22. return ResultVO.fail("订单保存失败! ",null);
  23. }
  24. }

11.3.3 服务熔断配置

  • 熔断器状态:闭合、打开、半开
  • 服务熔断配置
  1. @HystrixCommand(fallbackMethod = "fallbackSaveOrder",commandProperties = {
  2. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000"),
  3. @HystrixProperty(name="circuitBreaker.enabled",value="true"), //启⽤服务熔断
  4. @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="10000"),//时间
  5. @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="10"),//请求次数
  6. @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50"),//服务错误率
  7. })
  8. public ResultVO saveOrder(Order order) {
  9. //1. 调⽤ order-add服务进⾏保存
  10. ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order,ResultVO.class);
  11. System.out.println(vo);
  12. //2. 调⽤ orderitem-add 保存订单快照
  13. //3. 调⽤ stock-update 修改商品库存
  14. //4. 调⽤ shopcart-del 删除购物⻋记录
  15. return vo;
  16. }
  17. /**
  18. * 降级⽅法:与业务⽅法拥有相同的参数和返回值
  19. * @return
  20. */
  21. public ResultVO fallbackSaveOrder(Order order){
  22. return ResultVO.fail("⽹络异常,请重试! ",null);
  23. }

服务熔断:当⽤户请求服务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

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.3.11.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <properties>
  8. <java.version>1.8</java.version>
  9. <spring-cloud.version>Hoxton.SR11</spring-cloud.version>
  10. </properties>
  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>
  • 在application.yml启用 熔断器机制

    1. feign:
    2. hystrix:
    3. enabled: true
  • 在启动类添加 @EnableHystrix

    1. @SpringBootApplication
    2. @EnableDiscoveryClient
    3. @EnableFeignClients
    4. @EnableHystrix
    5. public class ApiOrderAddFeignApplication {
    6. public static void main(String[] args) {
    7. SpringApplication.run(ApiOrderAddFeignApplication.class, args);
    8. }
    9. }
  • 创建服务降级处理类

    FeignClient的服务降级类: 1.必须实现Feign客户端接⼝, 2.必须交给Spring容器管理

  1. @Component
  2. public class OrderAddClientFallback implements OrderAddClient {
  3. public ResultVO addOrder(Order order, String str) {
  4. System.out.println("-------addOrder的降级服务");
  5. return ResultVO.fail("fail",null);
  6. }
  7. public Order getOrder(String orderId) {
  8. System.out.println("-------getOrder的降级服务");
  9. return new Order();
  10. }
  11. }
  • 在Feign客户端指定降级处理类

    1. @FeignClient(value = "order-add", fallback = OrderAddClientFallback.class)
    2. public interface OrderAddClient {
    3. //1.对⽤POST请求调⽤服务, Feign客户端的⽅法参数默认为body传值(body只能有⼀个值)
    4. //2.如果有多个参数,则需要通过@RequestParam声明参数为请求⾏传值
    5. @PostMapping("order/add")
    6. public ResultVO addOrder(Order order,@RequestParam("str") String str);
    7. @GetMapping("order/get")
    8. public Order getOrder(@RequestParam("orderId") String orderId);
    9. }
  • Service类通过Feign客户端调⽤服务

    1. @Service
    2. public class OrderAddServiceImpl implements OrderAddService {
    3. @Autowired
    4. private OrderAddClient orderAddClient;
    5. //当我们创建Feign客户端的降级类并交给Spring管理后 在Spring容器中就会出现两个OrderAddClient对象
    6. @Override
    7. public ResultVO saveOrder(Order order) {
    8. //1. 调⽤ order-add服务进⾏保存
    9. ResultVO vo = orderAddClient.addOrder(order,"测试字符串");
    10. Order order1 = orderAddClient.getOrder("订单编号");
    11. System.out.println(order1);
    12. //2. 调⽤ orderitem-add 保存订单快照
    13. //3. 调⽤ stock-update 修改商品库存
    14. //4. 调⽤ shopcart-del 删除购物⻋记录
    15. return vo;
    16. }
    17. }

11.4.2 Ribbon 参数配置

image.png

  1. ribbon:
  2. ## Ribbon建⽴连接最⼤等待时间
  3. ConnectTimeout: 1000
  4. ## 在当前服务提供者尝试连接次数
  5. MaxAutoRetries: 2
  6. ## 与服务提供者通信时间
  7. ReadTimeout: 5000
  8. ## 设置熔断器服务降级时间 (默认 1000)
  9. hystrix:
  10. command:
  11. default:
  12. execution:
  13. isolation:
  14. thread:
  15. timeoutInMilliseconds: 8000

11.5 熔断器仪表盘监控

如果服务器的并发压⼒过⼤、服务器⽆法正常响应,则熔断器状态变为open属于正常的情况;但是如果⼀个
熔断器⼀直处于open状态、或者说服务器提供者没有访问压⼒的情况下熔断器依然为open状态,说明熔断器
的状态就不属于正常情况了。如何了解熔断器的⼯作状态呢 ?

  • 熔断器仪表盘

11.5.1 搭建熔断器仪表盘

  • 创建SpringBoot项目,添加依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-actuator</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>org.springframework.boot</groupId>
    7. <artifactId>spring-boot-starter-web</artifactId>
    8. </dependency>
    9. <dependency>
    10. <groupId>org.springframework.cloud</groupId>
    11. <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    12. </dependency>
  • 配置仪表盘的port和appName

    1. server:
    2. port: 9999
    3. spring:
    4. application:
    5. name: hystrix-dashboard
    6. hystrix:
    7. dashboard:
    8. proxy-stream-allow-list: "localhost"
  • 启动类添加@EanbleHystrixDashboard注解

    1. @SpringBootApplication
    2. @EnableHystrixDashboard
    3. public class HystrixDashboardApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(HystrixDashboardApplication.class, args);
    6. }
    7. }
  • 访问 http://localhost:9999/hystrix
    image.png

11.5.2 配置使用了熔断器的服务可被监控

  • 添加依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-actuator</artifactId>
    4. </dependency>
  • 配置

    1. @Configuration
    2. public class DashBoardConfig {
    3. @Bean
    4. public ServletRegistrationBean getServletRegistrationBean(){
    5. HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    6. ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
    7. registrationBean.setName("HystrixMetricsStreamServlet");
    8. registrationBean.setLoadOnStartup(1);
    9. registrationBean.addUrlMappings("/hystrix.stream");
    10. return registrationBean;
    11. }
    12. }
  • 查看指定服务的熔断器⼯作参数4e894231-bde1-4a1f-b4ea-b2a239077233.png
    image.png

十二、服务链路追踪

12.1 服务追踪说明

  • 微服务架构是通过业务来划分服务的,使⽤REST调⽤。对外暴露的⼀个接⼝,可能需要很多个服务协同才能
    完成这个接⼝功能,如果链路上任何⼀个服务出现问题或者⽹络超时,都会形成导致接⼝调⽤失败 image.png
  • 随着业务的不断扩张,服务之间互相调⽤会越来越复杂,它们之间的调⽤关系也许如下: image.png
  • 随着服务的越来越多,对调⽤链的分析会越来越复杂。

12.2 Zipkin

  • ZipKin是⼀个开放源代码的分布式跟踪系统,由Twitter公司开源,它致⼒于收集服务的定时数据,以解决微服
    务架构中的延迟问题,包括数据的收集、存储、查找和展现。它的理论模型来⾃于 Google Dapper 论⽂。
  • 每个服务向 ZipKin 报告计时数据, ZipKin 会根据调⽤关系通过 ZipKin UI ⽣成依赖关系图,显示了多少跟踪请
    求通过每个服务,该系统让开发者可通过⼀个 Web 前端轻松的收集和分析数据,例如⽤户每次请求服务的处
    理时间等,可⽅便的监测系统中存在的瓶颈。

12.3 搭建zipkin服务器

  • 创建SpringBoot项⽬(版本2.1.x)
  • 添加依赖

    1. <dependency>
    2. <groupId>io.zipkin.java</groupId>
    3. <artifactId>zipkin-server</artifactId>
    4. <version>2.11.10</version>
    5. </dependency>
    6. <!--zipkin界⾯-->
    7. <dependency>
    8. <groupId>io.zipkin.java</groupId>
    9. <artifactId>zipkin-autoconfigure-ui</artifactId>
    10. <version>2.11.10</version>
    11. </dependency>
  • 在启动类添加@EnableZipkinServer注解

    1. @SpringBootApplication
    2. @EnableZipkinServer
    3. public class ZipkinApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(ZipkinApplication.class, args);
    6. }
    7. }
  • 配置yml

    1. spring:
    2. application:
    3. name: zipkin
    4. server:
    5. port: 9411
    6. management:
    7. endpoints.web.exposure.include: '*'
    8. metrics.web.server.auto-time-requests: false

12.4 服务中Sleuth配置

  • 在服务应⽤中添加Sleuth依赖

    1. <!-- spring-cloud-sleuth-zipkin -->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    5. <version>2.0.2.RELEASE</version>
    6. </dependency>
  • 在服务应⽤中配置yml

    1. spring:
    2. application:
    3. name: goods-provider
    4. zipkin:
    5. enabled: true
    6. base-url: http://localhost:9411
    7. sleuth:
    8. sampler:
    9. probability: 0.1

12.5 zipkin服务数据存储

  • 创建数据库数据表

    1. CREATE TABLE IF NOT EXISTS zipkin_spans (
    2. `trace_id` BIGINT NOT NULL,
    3. `id` BIGINT NOT NULL,
    4. `name` VARCHAR(255) NOT NULL,
    5. `parent_id` BIGINT,
    6. `debug` BIT(1),
    7. `start_ts` BIGINT COMMENT "Span.timestamp(): epoch micros used for endTs query and to
    8. implement TTL",
    9. `duration` BIGINT COMMENT "Span.duration(): micros used for minDuration and maxDuration
    10. query"
    11. ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
    12. ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id`, `id`) COMMENT "ignore insert on
    13. duplicate";
    14. ALTER TABLE zipkin_spans ADD INDEX(`trace_id`, `id`) COMMENT "for joining with
    15. zipkin_annotations";
    16. ALTER TABLE zipkin_spans ADD INDEX(`trace_id`) COMMENT "for getTracesByIds";
    17. ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT "for getTraces and getSpanNames";
    18. ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT "for getTraces ordering and range";
    19. CREATE TABLE IF NOT EXISTS zipkin_annotations (
    20. `trace_id` BIGINT NOT NULL COMMENT "coincides with zipkin_spans.trace_id",
    21. `span_id` BIGINT NOT NULL COMMENT "coincides with zipkin_spans.id",
    22. `a_key` VARCHAR(255) NOT NULL COMMENT "BinaryAnnotation.key or Annotation.value if type
    23. == -1",
    24. `a_value` BLOB COMMENT "BinaryAnnotation.value(), which must be smaller than 64KB",
    25. `a_type` INT NOT NULL COMMENT "BinaryAnnotation.type() or -1 if Annotation",
    26. `a_timestamp` BIGINT COMMENT "Used to implement TTL; Annotation.timestamp or
    27. zipkin_spans.timestamp",
    28. `endpoint_ipv4` INT COMMENT "Null when Binary/Annotation.endpoint is null",
    29. `endpoint_ipv6` BINARY(16) COMMENT "Null when Binary/Annotation.endpoint is null, or no
    30. IPv6 address",
    31. `endpoint_port` SMALLINT COMMENT "Null when Binary/Annotation.endpoint is null",
    32. `endpoint_service_name` VARCHAR(255) COMMENT "Null when Binary/Annotation.endpoint is
    33. null"
    34. ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
    35. ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id`, `span_id`, `a_key`,
    36. `a_timestamp`) COMMENT "Ignore insert on duplicate";
    37. ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`) COMMENT "for joining with
    38. zipkin_spans";
    39. ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`) COMMENT "for getTraces/ByIds";
    40. ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT "for getTraces
    41. and getServiceNames";
    42. ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT "for getTraces";
    43. ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT "for getTraces";
    44. CREATE TABLE IF NOT EXISTS zipkin_dependencies (
    45. `day` DATE NOT NULL,
    46. `parent` VARCHAR(255) NOT NULL,
    47. `child` VARCHAR(255) NOT NULL,
    48. `call_count` BIGINT
    49. ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
    50. ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
  • pom依赖
    ```xml

    io.zipkin.zipkin2 zipkin-storage-mysql-v1 2.11.12

mysql mysql-connector-java 5.1.47

  1. - 配置yml
  2. ```properties
  3. spring:
  4. application:
  5. name: zipkin
  6. datasource:
  7. username: root
  8. password: admin123
  9. driver-class-name: com.mysql.jdbc.Driver
  10. url: jdbc:mysql://localhost:3306/zipkin
  11. zipkin:
  12. storage:
  13. type: mysql

十三、微服务架构总结

image.png

十四、分布式配置中心

在使⽤微服务架构开发的项⽬中,每个服务都有⾃⼰的配置⽂件(application.yml) ,如果将每个服务的配置
文件直接写在对应的服务中,存在以下问题:

  1. 服务开发完成之后,需要打包部署,配置⽂件也会打包在jar⽂件中,不便于项⽬部署之后的配置修改
    (在源码中修改——重新打包——重新上传——重新运⾏)
  2. 微服务架构中服务很多,配置⽂件也很多,分散在不同服务中不便于配置的管理
  3. 如果想要对服务进⾏集群部署,需要打包多个jar⽂件,上传,运⾏

14.1 分布式配置中心介绍

image.png

14.2 《锋迷商城》分布式配置中心搭建

步骤:

  • 创建⼀个Git远程仓库,⽤来存放配置⽂件
  • 搭建分布式配置中⼼服务器(Spring Cloud Config) Config server
    • 连接到配置⽂件的Git仓库
    • 注册到eureka
  • 修改每个服务,删除application.yml中的所有配置,连接到分布式配置中⼼

14.2.1 创建Git远程仓库

  • 创建远程仓库: https://gitee.com/qfytao/fmmall-config.git
    • 366274379 / admin123
  • 在本地D盘创建 fmmall-config⽬录,作为本地存放配置⽂件的⽬录,在⽬录中创建files⽬录
  • 使⽤idea打开 fmmall-config ⽬录

image.png

  • 将《锋迷商城》项⽬中服务的配置⽂件拷⻉粘贴到files⽬录,以服务的名称给配置⽂件命名
    image.png
  • 将 fmmall-config 创建成git仓库(本地仓库)
  • 将本地仓库push到创建的git远程仓库
    image.png

14.2.2 搭建分布式配置中心服务器

  • 创建SpringBoot应⽤,添加依赖
    image.png
  • 配置application.yml
    ```properties server: port: 8888 spring: application: name: config-server cloud: config: server:
    1. git:
    2. uri: https://gitee.com/qfytao/fmmall-config.git
    3. search-paths: files
    4. username: 366274379@qq.com
    5. password: admin123

eureka: client: service-url: defaultZone: http://zhangsan:123456@localhost:8761/eureka

  1. - 在启动类添加注解
  2. ```java
  3. @SpringBootApplication
  4. @EnableEurekaClient
  5. @EnableConfigServer
  6. public class ConfigServerApplication {
  7. public static void main(String[] args) {
  8. SpringApplication.run(ConfigServerApplication.class, args);
  9. }
  10. }

14.2.3 配置服务,通过分布式配置中心加载配置文件

  • 添加依赖

    1. <dependency>
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-config</artifactId>
    4. </dependency>
  • 配置服务的application.yml

    1. spring:
    2. cloud:
    3. config:
    4. uri: http://localhost:8888
    5. name: api-order-submit
    6. label: master