项目是基于SpringBoot和SpringCloud的微服务架构
一、微服务架构的介绍
1、什么是微服务
- 微服务架构风格是一种使用一个个独立部署运行的服务模块共同开发单个应用的方式途径,每个服务运行在自己的进程中,独立部署到不同的服务器上。
- 各个服务模块使用轻量级机制通信,通常是HTTP API
- ,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
2、微服务的优点
- 各个服务独立、分开,代码体量小,便于维护
- 灵活性高,比如不同服务可以使用不同的数据库(mysql,redis等),开发起来也灵活
- 单体架构的代码量大,耦合性高,维护困难,扩展性也差
二、SpringCloud简介
- Spring Cloud并不是一种技术,他是一系列技术框架的集合。
- Spring Cloud基于 Spring Boot 实现的开发工具,必须基于Spring Boot开发
- 它利用 Spring Boot 的开发便利性简化了分布式系统基础设施的开发,如服务发现、服务注册、配置中心、消息总线、负载均衡、 熔断器、数据监控等,都可以用 SpringBoot 的开发风格做到一键启动和部署
1. Spring Cloud相关基础服务组件
- 服务发现——Netflix Eureka (Nacos)
- 服务调用——Netflix Feign
- 熔断器 ——Netflix Hystrix
- 服务网关 ——Spring Cloud GateWay
- 分布式配置——Spring Cloud Config (Nacos)
- 消息总线 —— Spring Cloud Bus (Nacos)
- 上面除了(Nacos),其余都是SpringCloud原生的组件。
- Nacos是阿里巴巴开发的一个组件,已经加入到SpringCloud孵化器中
- Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, Spring Cloud Config
- (Nacos)代表项目中用Nacos代替
三、 搭建Nacos服务——服务注册和服务发现
微服务中,首先需要面对的问题就是如何查找服务(软件即服务),其次,就是如何在不同的服务之间进行通信?如何更好更方便的管理应用中的每一个服务,如何建立各个服务之间联系的纽带,由此Nacos注册中心诞生
Nacos的功能:
- 服务注册、服务发现
- 配置中心
1、服务注册配置
- 设置该服务在Nacos注册中心的服务名
- 在服务的配置文件中配置nacos地址,完成服务注册
# 服务名(这是SpringCloud中会用到的)
spring.application.name=service-edu
# nacos注册中心
spring.cloud.nacos.discovery.server-addr= localhost:8848
- 已注册的微服务可以在这个nacos的网址中看到:Http:/localhost:8848/nacos
- 默认账号密码为nacos/nacos
- 在nacos网站中主要分为两个模块——配置管理和服务管理,分别对应配置中心和注册中心
- 服务列表中又包括服务列表(已注册的服务)和订阅者列表(服务消费者)
2、服务发现注解
- 在微服务启动类上添加服务发现注解 @EnableDiscoveryClient //服务发现功能
- 只有添加了@EnableDiscoveryClient,才能完成在Nacos注册中心的服务注册
面试问题
1、什么是Nacos?
- Nacos还是Spring Cloud Alibaba组件之一,用来替代Spring Cloud的一些原生组件。
- 简单来说 Nacos 就是注册中心 + 配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册与发现,服务配置,服务管理等问题。
2、Nacos健康检查的方式?
- Nacos Server必须要确保注册的服务实例是健康的,而心跳检测就是服务健康检测的手段。服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求
- 所谓心跳机制就是客户端通过schedule定时向服务端发送一个数据包 ,然后启动-个线程不断检测服务端的回应,如果在设定时间内没有收到服务端的回应,则认为服务器出现了故障。Nacos服务端会根据客户端的心跳包不断更新服务的状态。
3、为什么要将服务注册到nacos?
- (注册中心统一管理,为了更好的查找这些服务)
4、Nacos注册中心的基本过程和原理?********
- 服务实例在启动时注册到服务注册表,并在关闭时注销。服务消费者查询服务注册表,获得可用的服务实例。
- 服务注册的底层是基于HTTP协议完成请求的。所以注册服务就是服务通过registerService方法向Nacos Server发送一个HTTP请求:
- registerService方法源码里主要就是:创建了一个HashMap,向里面put进去了命名空间namespaceID、服务名、服务所在的IP和端口号等信息。
- 上述HTTP请求到了nacos server后,调用registerInstance()方法注册服务实例
- 首先nacos server会创建一个ConcurrentHashMap来存储注册的服务实例,对应nacos网页中的服务列表
- 然后根据请求参数创建一个服务实例,保存到ConcurrentHashMap中
- 然后建立心跳机制监测服务健康状态
5、在Nacos中服务提供者是如何向Nacos注册中心(Registry)续约的?
- 为了防止该实例被Server端剔除,默认5s发送一次心跳。
6、注册中心的核心数据是什么?
- (服务的名字和它对应的网络地址)
7、注册中心中心核心数据的存取为什么会采用读写锁?
- (底层安全和性能)
8、Nacos负载均衡底层原理?
- 通过Ribbon实现的,,Ribbon中定义了一些负载均衡算法,然后基于这些算法从服务实例中获取一个实例为消费方提供服务。
四、服务调用 ——SpringCloud Feign
SpringCloud Feign可以帮助我们快捷地调用HTTP API,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定
消费者服务(服务调用方) 调用 生产者服务(服务被调用方)
- 1、在调用端的服务启动类上添加注解@EnableFeignClients,表示开启微服务调用功能
- 在调用端创建接口,完成接口绑定;
- 定义Http请求API,基于此API借助OpenFeign访问远端服务,代码如下:
- 其中,@FeignClient描述的接口底层会为其创建实现类。
@Component
@FeignClient("service-vod")//指定调用的服务名,前提要注册到nacos注册中心中
public interface VodClient {
//根据视频id删除阿里云视频
@DeleteMapping("/eduvod/video/removeAliyunVideoById/{id}")
public R removeAliyunVideoById(@PathVariable("id") String id);
}
Feign 调用过程分析(了解)
Feign应用过程分析(底层逻辑先了解):
1)通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
2) Feign Starter 会在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
3) Feign接口被调用时,底层代理对象会将接口中的请求信息通过编码器创建 Request对象,基于此对象进行远程过程调用。
4) Feign客户端请求对象会经Ribbon进行负载均衡,挑选出一个健康的 Server 实例(instance)。
5) Feign客户端会携带 Request 调用远端服务并返回一个响应。
6) Feign客户端对象对Response信息进行解析然后返回客户端。
1、什么是Feign?
- Spring Cloud 的一个组件,其实就是一个java http客户端。可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法。
2、为什么使用feign?
- 基于Feign可以更加友好的实现服务调用,简化服务消费方对服务提供方方法的调用。
- 服务消费方基于RestTemplate方式请求服务(RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类)提供方的服务时,代码量复杂且不易维护,此时Feign诞生。
3、@FeignClient注解的作用是什么?(或者 如何实现服务远程调用?)
- 当程序启动时,会进行包扫描,扫描所有 @FeignClients 的注解的类,并将这些信息注入 Spring IOC 容器中。
- 当定义的 Feign 接口中的方法被调用时,通过JDK动态代理的方式,来生成具体的 RequestTemplate。
- 当生成代理时,Feign 会为每个接口方法创建一个 RequetTemplate 对象,该对象封装了 HTTP 请求需要的全部信息,如请求参数名、请求方法等信息都是在这个过程中确定的。完成远程请求执行和获取远程结果
4、服务消费方是如何调用服务提供方的服务的?
- feign
项目中哪些模块用到了服务调用Feign?
五、熔断器Hystrix
Hystrix的三个功能:
- 服务降级
- 服务熔断
- 服务限流
1、服务雪崩(雪崩效应)
分布式系统环境下,服务间类似依赖(服务调用关系)非常常见,一个业务调用通常依赖(调用)多个基础服务。如下图,
如果Service C因为抗不住请求,变得不可用。那么Service B的请求也会阻塞,慢慢耗尽Service B的线程资源,Service B就会变得不可用。紧接着,Service A也会不可用。
So,简单地讲。一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。
2、服务雪崩的解决方案
1) 应用扩容(扩大服务器承受力)
- 加机器
- 升级硬件
2)流量控制(超出限定流量,返回类似重试页面让用户稍后再试)
- 限流
- 关闭重试
3) 缓存
- 将用户可能访问的数据大量的放入缓存中,减少访问数据库的请求。
4)服务降级
- 服务接口拒绝服务
- 页面拒绝服务
- 延迟持久化
- 随机拒绝服务
5) 服务熔断
3、Hystrix——服务降级
- 指的是生产者服务出现异常时,为了避免雪崩效应,需要向服务消费方提供一个符合预期的、可处理的备选响应—FallBack(即兜底的解决方案)
Hystrix——服务熔断
- 服务器达到最大请求量时,为了避免服务器宕机,直接拒绝访问,然后调用服务降级的方法返回兜底的响应。
5、Hystrix——服务限流
- 服务器请求量大时,通过队列的方式排队请求
6、项目中使用了Hystrix服务降级,配置和使用如下
- service-edu模块使用了Hystrix的服务降级
- service-edu(服务消费端)需要远程调用service-vod模块(生产者服务)的controller接口
- (面试时可以说是service-edu(服务消费端)远程调用支付模块(生产者服务)的接口)
- 在service-edu(服务消费端)设置了Hystrix的服务降级。其实也可以在service-vod模块(生产者服务)中设置服务降级。一个是消费侧的服务降级,一个是生产侧的服务降级
首先在service-edu模块添加依赖:
<!--hystrix依赖,主要是用 @HystrixCommand --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
然后在service-edu(服务消费端)添加配置:(1、开启熔断机制 2、设置超时时间)
#开启熔断机制 feign.hystrix.enabled=true # 设置hystrix超时时间,默认1000ms hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
在远程调用的接口上@FeignClient注解上指定fallback类,也就是远程调用请求超时的时候,调用本地哪个类的对应的方法来兜底
@Component @FeignClient(value = "service-vod",fallback = VodClientImpl.class)//指定调用的服务名,前提要注册到nacos注册中心中 public interface VodClient { //根据视频id删除阿里云视频 @DeleteMapping("/eduvod/video/removeAliyunVideoById/{id}") public R removeAliyunVideoById(@PathVariable("id") String id); //根据多个视频id删除多个阿里云视频 @DeleteMapping("/eduvod/video/removeBatch") public R removeBatch(@RequestParam("videoIdList") List<String> videoIdList); }
兜底实现类:(返回提示出错信息)
@Component //教给spring管理 public class VodClientImpl implements VodClient { //出错之后会执行,兜底方法 @Override public R removeAliyunVideoById(String id) { return R.error().message("删除视频出错了"); } @Override public R removeBatch(List<String> videoIdList) { return R.error().message("删除多个视频出错了"); } }