项目源码与所需资料
链接:https://pan.baidu.com/s/1azwRyyFwXz5elhQL0BhkCA?pwd=8z59
提取码:8z59
文章目录
demo12-课程管理
1.微服务
1.1微服务介绍
- 微服务是架构风格
- 把一个项目拆分成独立的多个服务,多个服务是独立运行,每个服务占用独立进程(我们前面的8001、8002、8003端口就是这样的)
1.2微服务与单体架构区别
- 单体架构所有的模块全都耦合在一块,代码量大,维护困难
- 微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决
- 单体架构所有的模块都共用一个数据库,存储方式比较单一
- 微服务每个模块都可以使用不同的存储方式(比如有的用redis,有的用mysql等),数据库也是单个模块对应自己的数据库
- 单体架构所有的模块开发所使用的技术一样
- 微服务每个模块都可以使用不同的开发技术,开发模式更灵活
1.3什么样的项目适合微服务
微服务可以按照业务功能本身的独立性来划分,如果系统提供的业务是非常底层的,如:操作系统内核、存储系统、网络系统、数据库系统等等,这类系统都偏底层,功能和功能之间有着紧密的配合关系,如果强制拆分为较小的服务单元,会让集成工作量急剧上升,并且这种人为的切割无法带来业务上的真正的隔离,所以无法做到独立部署和运行,也就不适合做成微服务了
1.4微服务开发框架
目前微服务的开发框架,最常用的有以下四个:
Spring Cloud:http://projects.spring.io/spring-cloud(现在非常流行的微服务架构)
Dubbo:http://dubbo.io
Dropwizard:http://www.dropwizard.io(关注单个微服务的开发)
Consul、etcd&etc.(微服务的模块)
2.Spring Cloud
2.1什么是Spring Cloud
- Spring Cloud并不像Spring、Mybatis、JavaWeb、Servlet…那样是一种技术,而是很多技术的总称,或者说是很多技术的集合
- Spring Cloud里面有很多框架(技术),我们使用Spring Cloud里面这些框架实现微服务操作
- 想要使用Spring Cloud需要依赖于框架SpringBoot
2.2Spring Cloud和Spring Boot的区别与联系
1.区别:
- Spring Boot就是Spring,是快速构建Spring的脚手架
- Spring Cloud是一系列框架的总称
2.联系:
- 使用Spring Cloud需要基于Spring Boot
3.微服务架构:
一提到微服务架构我们就要想到Spring Cloud而不是Spring Boot,Spring Boot只是里面的技术,真正的微服务架构是Spring Cloud
2.3Spring Cloud相关基础服务组件
- 服务发现——Netflix Eureka(Nacos)
- 服务发现就是注册中心的概念
- 服务调用——Netflix Feign
- 熔断器——Netflix Hystrix
- 服务网关——Spring Cloud GateWay
- 分布式配置——Spring Cloud Config(Nacos)
- 消息总线——Spring Cloud Bus(Nacos)
服务发现、分布式配置、消息总线现在都用Nacos来实现
2.4Spring Cloud的版本
1.Spring Cloud并没有熟悉的数字版本号,而是对应一个开发代号:
Cloud代号 | Boot版本(train) | Boot版本(tested) | lifecycle |
---|---|---|---|
Angle | 1.2.x | incompatible with 1.3 | EOL in July 2017 |
Brixton | 1.3.x | 1.4.x | 2017-07卒 |
Camden | 1.4.x | 1.5.x | - |
Dalston | 1.5.x | not expected 2.x | - |
Edgware | 1.5.x | not expected 2.x | - |
Finchley | 2.0.x | not expected 1.5.x | - |
Greenwich | 2.1.x | ||
Hoxton | 2.2.x |
开发代号看似没有什么规律,但实际上首字母是有顺序的,比如:Dalston版本,我们可以简称D版本;Edgware版本,我们可以简称E版本。这样的话这些版本代码其实就是ABCDEFGH
2.我们项目中用的Spring Boot是2.2.1,所以使用Cloud时需要使用Hoxton版本
3.Spring Cloud小版本分为:
- SNAPSHOT:快照版本,随时可能修改,所以我们一般不使用这个版本
- M:MileStone,M1表示第1个里程碑版本,一般同时标注PRE,表示预览版本
- SR:Service Release,SR1表示第1个正式版本,一般同时标注GA:(GenerallyAvailable),表示稳定版本
一般我们肯定用稳定版本,如果没有稳定版本就用SR,如果没有SR就用M,别使用SNAPSHOT
3.注册中心
3.1引出注册中心
1.我们在"demo09-课程管理"的"8.3删除小节"说过,小节下是有视频的,我们删除小节时需要顺带将阿里云中的该视频删掉
2.我们可以在service_edu模块的控制器EduVideoController的updateVideo方法中写删除阿里云视频的业务逻辑(或者在业务层写这个业务逻辑也可以)
3.但是我们还是用微服务的方式来实现这个业务
4.微服务:把我们的项目划分为多个模块(或者说多个服务),每个服务都是独立运行的,每个服务中专门做一些功能,比如:我们的service_edu专门做课程、service_oss专门做阿里云oss、service_vod专门做视频
5.我们在"demo11-课程管理"的"7.3控制层"做了删除阿里云视频的业务逻辑,所以我们现在,如果不使用微服务方式实现,那么我们可以直接将"7.3控制层"中删除阿里云视频的业务逻辑代码复制粘贴到service_edu模块
6.但我们现在要使用微服务方式实现,那么就需要在service_edu模块中调用service_vod模块中删除阿里云视频的方法,这样的实现方式就叫做微服务
7.但是service_edu模块和service_vod模块是独立运行的,想要实现在service_edu模块调用service_vod模块的方法,有一种实现方式:在service_edu模块的pom.xml中引入service_vod依赖,但是这样的话这两个模块就有关联了,这就不叫微服务了
8.正确的实现方式是使用Spring Cloud模块中的技术:注册中心
9.我们将service_edu模块和service_vod模块都注册到注册中心里面,这样就可以实现在service_edu模块调用service_vod模块的方法
3.1注册中心介绍
1.如果我们想要实现不同的微服务模块之间的调用,就需要把这些模块在注册中心进行注册
2.常见的注册中心:
-
Eureka(原生,2.0遇到性能瓶颈,停止维护)
-
Zookeeper(支持,专业的独立产品。例如:dubbo)
-
Consul(原生,GO语言开发)
-
Nacos
-
相对于Spring Cloud Eureka来说,Nacos更强大:Nacos = Spring Cloud Eureka + Spring Cloud Config、Spring Cloud Bus
Nacos可以与Spring、Spring Boot,、Spring Cloud 集成,并能代替Spring Cloud Eureka、Spring Cloud Config、Spring Cloud Bus
通过Nacos Server和spring-cloud-starter-alibaba-nacos-discovery实现服务的注册与发现
-
3.Nacos执行流程:
- 注册时用到模块的ip和端口号
- 生产者就是提供方法的;消费者就是调用方法的。我们需要实现在service_edu模块调用service_vod模块的方法,那么此时service_edu就是消费者;service_vod就是生产者
4.Nacos安装和服务注册
4.1安装与访问
1.安装nacos:
nacos提供在了资料中,解压到任意目录即可
2.启动nacos服务
windows系统双击bin目录下的startup.cmd启动nacos服务;linux系统双击bin目录下的startup.sh启动nacos服务
3.访问:
访问地址是:http://localhost:8848/nacos
账号密码都是nacos
4.2服务注册(service_edu)
服务注册以service_edu为例:
1.在service_edu中引入依赖,因为我们后面service模块下的子模块有很多都要注册到注册中心,所以我们选择在service模块的pom.xml中引入依赖
我们在"demo03-后台讲师管理模块"的"2.2.3创建子模块"的第5步将这个依赖注释掉了,现在我们将这个依赖打开即可(别忘了刷新maven)
2.在service_edu的配置文件application.properties中配置nacos地址(即nacos的ip和端口号)
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
3.在service_edu的启动类中添加Nacos客户端注解@EnableDiscoveryClient
,目的是让注册生效:
4.重启后端项目,在地址栏输入http://localhost:8848/nacos,登录后在"服务列表"菜单就可以看到我们成功将service_edu服务注册进来了
为什么截图中服务名是service-edu?因为我们在配置文件中配置了服务名为service_edu
为什么我们配置的服务名是service-edu而不是service_edu?因为如果用下划线可能会有问题,这里用横杠而不是下划线
4.3服务注册(service_vod)
1.在service_vod的配置文件application.properties中配置nacos地址(即nacos的ip和端口号)
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
2.在service_vod的启动类中添加Nacos客户端注解@EnableDiscoveryClient
,目的是让注册生效:
3重启后端项目,在地址栏输入http://localhost:8848/nacos,登录后在"服务列表"菜单就可以看到我们成功将service_vod服务注册进来了
4.4问题
1.重启service_oss服务后控制台报错了
2.这是因为我们在service模块的pom.xml中引入了nacos依赖,所以启动service_oss模块时就会去找nacos注册中心,但是我们并没有在service_oss中配置注册,所以就找不到,所以就会报错。
所以我们需要将service_oss服务也注册到注册中心,具体步骤在"4.3服务注册(service_vod)",自行进行配置吧
5.删除小节时删除视频(服务调用)
5.1Feign
想要实现服务之间的调用需要用到Spring Cloud中的组件Feign
5.2实现服务调用
实现服务调用的前提是我们需要先将互相调用的服务在nacos中进行注册,这一步我们在前面已经实现了
1.在service模块的pom.xml中引入服务调用的依赖
我们在"demo03-后台讲师管理模块"的"2.2.3创建子模块"的第5步将这个依赖注释掉了,现在我们将这个依赖打开即可(别忘了刷新maven)
2.在调用端(service_edu)的启动类添加注解@EnableFeignClients
3.在调用端的eduservice包下创建包client,然后在client包下创建接口VodClient
@FeignClient("service-vod")
@Component
public interface VodClient {
//定义调用的方法路径
@DeleteMapping("/eduvod/video/removeAlyVideo/{id}")
public R removeAlyVideo(@PathVariable("id") String id);
}
- @FeignClient注解用于指定从哪个服务中调用功能 ,名称与被调用的服务名保持一致
- @PathVariable注解一定要指定参数名称,否则出错
4.完善调用端的控制器EduVideoController中deleteVideo方法以实现删除小节时删除阿里云视频
①先在调用端的控制器EduVideoController中注入vodClient
//注入vodClient
@Autowired
private VodClient vodClient;
②然后在deleteVideo方法中添加如下代码
/**
* 1.删除小节时删除阿里云视频
* 具体实现:根据小节id得到视频id,然后调用方法实现视频删除
*/
//1.1根据小节id得到视频id
EduVideo eduVideo = videoService.getById(id);
String videoSourceId = eduVideo.getVideoSourceId();
//1.2根据视频id,远程调用实现视频删除(线判断id不为空才调用方法删除)
if (!StringUtils.isEmpty(videoSourceId)) {
vodClient.removeAlyVideo(videoSourceId);
}
截图中第50行使用的工具类StringUtils是spring包下的,别导错包了
5.3测试
重启后端项目后在9528端口自行测试,我测试没问题
6.删除课程时删除视频(接口)
6.1分析
1.一个课程有多个章节,一个章节有很多小节,每个小节都有一个视频(也有可能没有给小节添加视频),也就是说删除课程时需要删除多个视频
2.我们在service_vod的控制器VodController中编写过"根据视频id删除阿里云中的视频"的方法,那么删除多个视频时可以这样:写一个循环,循环多次调用这个方法,这种方式是完全可行的
3.我们不使用刚刚说的那种方式,我们这里直接重新编辑一个方法,这个方法是"根据多个视频id删除阿里云中的视频"
6.2控制层
在service_vod的控制器VodController中编写方法"根据多个视频id删除阿里云中的视频"
//根据多个视频id删除阿里云中的视频
@DeleteMapping("delete-batch")
public R deleteBatch(@RequestParam("videoIdList") List videoIdList) {
vodService.removeMoreAlyVideo(videoIdList);
return R.ok();
}
截图中第61行的@RequestParam("videoIdList")
是为了表明参数是videoIdList,老师说了不加这个代码也是可以的
6.3业务层接口
在service_vod的业务层接口VodService中定义抽象方法
//根据多个视频id删除阿里云中的视频
void removeMoreAlyVideo(List videoIdList);
6.4业务层实现类
在service_vod的业务层实现类VodServiceImpl中实现上一步定义的抽象方法
//根据多个视频id删除阿里云中的视频
@Override
public void removeMoreAlyVideo(List videoIdList) {
try {
//1.创建初始化对象
DefaultAcsClient client = InitVodClient.initVodClient(
ConstantVodUtils.ACCESS_KEY_ID,
ConstantVodUtils.ACCESS_KEY_SECRET);
//2.创建删除的request
DeleteVideoRequest request = new DeleteVideoRequest();
//3.向request对象里面设置视频id
//①需要先将videoIdList集合中的id遍历为1,2,3的形式
String videoIds = StringUtils.join(videoIdList.toArray(), ",");
//②设置视频id
request.setVideoIds(videoIds);
//4.调用初始化对象里面的方法,实现删除
client.getAcsResponse(request);
} catch(Exception e) {
e.printStackTrace();
throw new GuliException(20001, "删除视频失败");
}
}
StringUtils.join(videoIdList.toArray(), ",")
表示先将集合转为数据,然后将数组中的数据按照逗号(,)进行分割- 这里的StringUtils工具类使用的是org.apache.commons.lang包下的
7.删除课程时删除视频(远程调用)
1.在调用端(service_edu)的VodClient接口中定义调用"根据多个视频id删除阿里云中的视频"的方法
//定义调用"根据多个视频id删除阿里云中的视频"的方法
@DeleteMapping("/eduvod/video/delete-batch")
public R deleteBatch(@RequestParam("videoIdList") List videoIdList);
2.我们在service_edu的业务层实现类EduVideoServiceImpl的removeVideoByCourseId中说过删除小节前需要先删除小节下的视频文件(TODO)
3.我们在removeVideoByCourseId方法中完善业务逻辑
//TODO 删除小节前需要先删除小节下的视频文件
//1.根据课程id查询课程中所有的视频id(视频id在小节表中,所以我们查小节表edu_video)
//①得到List<EduVideo>
QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
wrapperVideo.eq("course_id", courseId);
wrapperVideo.select("video_source_id"); //查询指定的列
List<EduVideo> eduVideoList = baseMapper.selectList(wrapperVideo);
//②将List<EduVideo>变为List<String>
List<String> videoIds = new ArrayList<>();
for (int i = 0; i < eduVideoList.size(); i++) {
EduVideo eduVideo = eduVideoList.get(i);
String videoSourceId = eduVideo.getVideoSourceId();
//将不为空的视频id放到videoIds集合中
if (!StringUtils.isEmpty(videoSourceId)) {
videoIds.add(videoSourceId);
}
}
//2.远程调用:根据多个视频id删除阿里云中的视频
//判断:如果课程下没有一个视频,那就不用调用这个方法了
if (videoIds.size() > 0) {
vodClient.deleteBatch(videoIds);
}
4.在实现类EduVideoServiceImpl中注入VodClient
@Autowired
private VodClient vodClient;
5.测试
1.重启后端项目,可以看到service_edu项目报错了:Could not resolve element type of Iterable type @org.springframework.web.bind.annotation.RequestParam java.util.List<?>. Not declared?
2.这是因为我们没有给List集合加泛型,所以会转换失败,解决方法法:
①给service_vod项目的控制器VodController的deleteBatch方法的List集合加泛型(老师说了这个地方不加也是可以的,但是为了规范还是加了)
②给service_edu项目的VodClient接口的deleteBatch方法的List集合加泛型
3.再次重启后端项目就没问题了,后面的在9528端口自行测试吧
8.Spring Cloud调用接口流程
Spring Cloud 在接口调用上,大致会经过如下几个组件配合:
Feign ----->Hystrix —>Ribbon —>Http Client(apache http components 或者 Okhttp) 具体交互流程上,如下图所示:
-
接口化请求调用:当调用被
@FeignClient
注解修饰的接口时,在框架内部,将请求转换成Feign的请求实例feign.Request
,交由Feign框架处理。 -
Feign:转化请求Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,封装了Http调用流程。
-
Hystrix:熔断处理机制 Feign的调用关系,会被Hystrix代理拦截,对每一个Feign调用请求,Hystrix都会将其包装成
HystrixCommand
,参与Hystrix的流控和熔断规则。如果请求判断需要熔断,则Hystrix直接熔断,抛出异常或者使用FallbackFactory
返回熔断Fallback
结果;如果通过,则将调用请求传递给Ribbon
组件。 -
Ribbon:服务地址选择 当请求传递到
Ribbon
之后,Ribbon
会根据自身维护的服务列表,根据服务的服务质量,如平均响应时间,Load等,结合特定的规则,从列表中挑选合适的服务实例,选择好机器之后,然后将机器实例的信息请求传递给Http Client
客户端,HttpClient
客户端来执行真正的Http接口调用; -
HttpClient:Http客户端,真正执行Http调用根据上层
Ribbon
传递过来的请求,已经指定了服务地址,则HttpClient开始执行真正的Http请求
9.熔断器
9.1介绍
- 如果在service_edu模块调用service_vod模块的方法时,突然service_vod服务器宕机了(挂掉了),此时就要用到熔断器使其不让访问service_vod
- 假如在service_edu中设置请求时间最多为5秒,超时就报错请求。如果在service_edu模块调用service_vod模块的方法时,已经过5秒仍没有数据返回,此时就会报错,此时我们就需要用熔断器来延迟请求时间
9.2在项目中整合熔断器
1.添加依赖
在service模块的pom.xml中添加hystrix依赖和ribbon依赖
我们在"demo03-后台讲师管理模块"的"2.2.3创建子模块"的第5步将这两个依赖注释掉了,现在我们将这两个依赖打开即可(别忘了刷新maven)
2.在调动端(service_edu)的配置文件application.properties中开启熔断器机制
#开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
# hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
feign.hystrix.enabled
的值默认是false,也就是不开启熔断机制
3.我们已经在调用端的client包下创建了接口VodClient,现在我们在client包下创建这个接口的实现类VodFileDegradeFeignClient
@Component
public class VodFileDegradeFeignClient implements VodClient{
@Override
public R removeAlyVideo(String id) {
return R.error().message("删除视频出错了");
}
@Override
public R deleteBatch(List<String> videoIdList) {
return R.error().message("删除多个视频出错了");
}
}
如果调用成功就不会执行实现类VodFileDegradeFeignClient中的方法,如果调用失败就会执行实现类中的方法
4.修改调用端的VodClient接口中的注解@FeignClient
修改前@FeignClient("service-vod")
修改后@FeignClient(name = "service-vod", fallback = VodFileDegradeFeignClient.class)
9.3测试
1.首先我们知道据视频id删除阿里云中的视频的方法removeAlyVideo删除成功状态码是20000,删除失败状态码是20001
2.注意红方框圈起来的代码
将上述红方框圈起来的代码改为如下代码
R result = vodClient.removeAlyVideo(videoSourceId);
if (result.getCode() == 20001) {
throw new GuliException(20001, "根据单个视频id删除视频失败,熔断器...");
}
3.重启后端服务,随便添加一个课程,课程下随便添加一个章节,章节下随便添加一个小节,小节下随便上传一个视频
4.把service_vod服务停掉
5.点击这个小节的"删除"按钮
6.看到如下错误,这就是熔断器