谷粒学院
SpringCloud相关概念介绍
一、什么是微服务
1、微服务的由来
微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
2、为什么需要微服务
系统的问题总结来说就是扩展性差,可靠性不高,维护成本高。J2EE,切换时间太长,成本太高,新系统稳定性的收敛也需要一些时间。
3、微服务和单体架构的区别
微服务:
- 模块独立,代码量减少
- 模块可采用不同的存储方式(redis、mysql等)
- 开发灵活,便于维护
单体架构:
- 模块耦合大,代码量大,维护困难
- 存储方式单一
- 模块开发使用技术应用
4、什么样的项目适合微服务
如果系统提供的业务是非常底层的,如:操作系统内核、存储系统、网络系统、数据库系统
等等,这类系统都偏底层,功能和功能之间有着紧密的配合关系,如果强制拆分为较小的服务单元,会让集成工作量急剧上升,并且这种人为的切割无法带来业务上的真正的隔离,所以无法做到独立部署和运行,也就不适合做成微服务了。
5、微服务开发框架
目前微服务的开发框架,最常用的有以下四个:
Spring Cloud:http://projects.spring.io/spring-cloud
(现在非常流行的微服务架构)
Dubbo:http//dubbo.io
Dropwizard:http://www.dropwizard.io
(关注单个微服务的开发)
Consul、etcd&etc.(微服务的模块)
二、什么是SpringCloud
Spring Cloud并不是一种技术,他是一系列技术框架的集合。它利用 Spring Boot 的开发便利性简化了分布式系统基础设施的开发,如服务发现、服务注册、配置中心、消息总线、负载均衡、 熔断器、数据监控等,都可以用 SpringBoot 的开发风格做到一键启动和部署
1、Spring Cloud和Spring Boot是什么关系
SpringBoot:
- 快速开发单个微服务
- 专注于快速、方便集成的单个微服务个体
- 使用了默认大于配置的理念
- 可以单独使用
SpringCloud:
-
基于 Spring Boot 实现的开发工具,必须基于Spring Boot开发
-
关注全局的服务治理框架
-
离不开 Spring Boot
2、Spring Cloud相关基础服务组件
-
服务发现——Netflix Eureka (
Nacos
) -
服务调用——Netflix Feign
-
熔断器 ——Netflix Hystrix
-
服务网关 ——Spring Cloud GateWay
-
分布式配置——Spring Cloud Config (
Nacos
) -
消息总线 —— Spring Cloud Bus (
Nacos
)
3、Spring Cloud的版本
Spring Cloud 并没有熟悉的数字版本号,而是对应一个开发代号。
开发代号看似没有什么规律,但实际上首字母是有顺序的,比如: Dalston 版本,我们可以简称 D 版本,对应的 Edgware 版本我们可以简称 E 版本。
小版本:
Spring Cloud 小版本分为 :
-
M : MileStone , M1 表示第 1 个里程碑版本,一般同时标注 PRE ,表示
预览版版
。 -
SR : Service Release , SR1 表示第 1 个正式版本,一般同时标注 GA : (GenerallyAvailable), 表示
稳定版本
。
服务发现-搭建Nacos服务
一、Nacos
1、基本概念
是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
常见的注册中心:
-
Eureka(原生,2.0遇到性能瓶颈,停止维护)
-
Zookeeper(支持,专业的独立产品。例如:dubbo)
-
Consul(原生,GO语言开发)
-
Nacos
Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, Spring Cloud
Config
- 通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现。
Nacos主要提供以下四大功能:
- 服务发现和服务健康监测
- 动态配置服务
- 动态DNS服务
- 服务及其元数据管理
Nacos结构图:
2、Nacos下载和安装
(1)下载地址和版本:
下载地址:https://github.com/alibaba/nacos/releases
下载版本:nacos-server-1.1.4.tar.gz 或 nacos-server-1.1.4.zip,解压任意目录即可
2 ) 启动nacos服务
-
Linux/Unix/Mac
启动命令(standalone代表着单机模式运行,非集群模式)
启动命令:sh startup.sh -m standalone -
Windows
启动命令:cmd startup.cmd 或者双击startup.cmd运行文件。
访问:http://localhost:8848/nacos
用户名密码:nacos/nacos
二、服务注册(service_edu为例)
把service-edu微服务注册到注册中心中,service-vod步骤相同
- 注意,在service包下的都需要配置nacos服务器,因为都配置了下面的nacos配置
不然会报错
1、在service模块配置pom
配置 Nacos 客户端的 pom 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2、添加服务配置信息
配置application.properties,在客户端微服务中添加注册Nacos服务的配置信息
# nacos注册中心
spring.cloud.nacos.discovery.server-addr= localhost:8848
3、添加Nacos客户端注解
在客户端微服务启动类中添加注解
@EnableDiscoveryClient //服务发现功能
4、启动客户端微服务
启动注册中心
启动已注册的微服务,可以在 Nacos 服务列表中看到被注册的微服务
- 跟上面步骤一样给service-vod、service-oss配置注册中心避免报错
服务调用-Feign
一、Feign
1、基本概念
- Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。 - Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
- Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
- Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring CloudRibbon时自行封装服务调用客户端的开发量。
二、实现服务调用
1、需求
删除课时的同时删除云端视频
2、在service模块添加pom依赖
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3、在调用端启动类添加注解
@EnableFeignClients
4、创建包和接口
创建 client 包
@FeignClient注解
用于指定从哪个服务中调用功能 ,名称与被调用的服务名保持一致。
@GetMapping注解
用于对被调用的微服务进行地址映射。
@PathVariable注解
一定要指定参数名称,否则出错
@Component注解
防止,在其他位置注入CodClient时idea报错
@Component
@FeignClient("service-vod")//指定调用的服务名,前提要注册到nacos注册中心中
public interface VodClient {
//根据视频id删除阿里云视频
@DeleteMapping("/eduvod/video/removeAliyunVideoById/{id}")
public R removeAliyunVideoById(@PathVariable("id") String id);
}
5、调用微服务
在【调用端】的VideoServiceImpl中调用client中的方法
//根据课程id删除小节
@Override
public void removeVideoByCourseId(String id) {
//查询云端视频id
EduVideo eduVideo = baseMapper.selectById(id);
String videoSourceId = eduVideo.getVideoSourceId();
//判断小节中是否有对应的视频文件
if (!StringUtils.isEmpty(videoSourceId)){
//有就删除
vodClient.removeAliyunVideoById(videoSourceId);
}
//删除小节
baseMapper.deleteById(id);
}
6、测试
添加一个小节
- 点击删除
- 检查数据库和阿里云视频点播
- 被删除了,测试成功
完善删除课程业务
一、service_vod服务
需求:删除课程的同时删除云端视频,要求添加一个可以删除多个视频的方法
- VodController
//根据id删除多个阿里云视频
@DeleteMapping("/removeBatch")
public R removeBatch(@RequestParam("videoIdList") List<String> videoIdList){
vodService.removeMoreVideo(videoIdList);
return R.ok();
}
- service
接口
//根据id删除多个阿里云视频
void removeMoreVideo(List<String> videoIdList);
Impl
//根据id集合删除多个视频
@Override
public void removeMoreVideo(List<String> videoIdList) {
//将集合转换为1,2,3格式
String str = StringUtils.join(videoIdList.toArray(), ",");
try {
//初始化对象
DefaultAcsClient client = InitObject.initVodClient(ConstantVodUtils.ACCESSKEY_ID, ConstantVodUtils.ACCESSKEY_SECRET);
//创建删除视频request对象
DeleteVideoRequest request = new DeleteVideoRequest();
//向request设置要删除视频的id值
request.setVideoIds(str);
//调用初始化对象的方法实现删除
DeleteVideoResponse response = client.getAcsResponse(request);
System.out.println("RequestId = "+ response.getRequestId()+"\n");
}catch (Exception e){
throw new AchangException(20001,"视频删除失败");
}
}
- Swagger测试
二、service_edu服务
service_edu去调用service_vod接口实现删除多个视频的功能
-
给service_edu启动类上加
@EnableFeignClients
-
创建接口标注
@FeignClient
指定调用的服务名
@Component
@FeignClient("service-vod")//指定调用的服务名,前提要注册到nacos注册中心中
- 在接口下创建调用service_vod的方法
//根据多个视频id删除多个阿里云视频
@DeleteMapping("/eduvod/video/removeBatch")
public R removeBatch(@RequestParam("videoIdList") List<String> videoIdList);
- VideoServiceImpl.java
//根据课程id删除小节
@Override
public void removeVideoByCourseId(String id) {
//根据课程id查询课程里面的所有视频
QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
wrapper.eq("course_id",id);
wrapper.select("video_source_id");
List<EduVideo> eduVideos = baseMapper.selectList(wrapper);
List<String> list = new ArrayList<>();
for (EduVideo eduVideo : eduVideos) {
String sourceId = eduVideo.getVideoSourceId();
if (!StringUtils.isEmpty(sourceId)){
list.add(sourceId);
}
}
//根据多个视频id,删除多个视频
if (list.size()>0){
vodClient.removeBatch(list);
}
QueryWrapper<EduVideo> queryWrapper2 = new QueryWrapper<>();
queryWrapper2.eq("course_id", id);
baseMapper.delete(queryWrapper2);
}
- EduCourseServiceImpl
//删除课程
@Override
public boolean removeCourse(String id) {
//1、根据课程id删除小节
eduVideoService.removeVideoByCourseId(id);
//2、根据课程id删除章节部分
eduChapterService.removeChapterByCourseId(id);
//3、根据课程id删除课程描述
eduCourseDescriptionService.removeById(id);
//4、根据课程id删除课程本身
int result = baseMapper.deleteById(id);
if (result>=1){
return true;
}else {
throw new AchangException(20001,"删除失败");
}
}
阿昌发现的小BUG,优化方案如下:
当之前上传了视频后点击确认,在点击添加小节,会发现视频在存在,并没清空,所以在添加小节弹框的时候将文件列表清空,保证每次添加的时候都没有文件,不然会导致小节表中的视频id为null,方法调用出现错误
- 测试
三、小节删除功能
发现根据老师过来,加入自己的想法后有点bug,所以阿昌打算再多写接口,来实现小节删除功能
1、前端部分
- 页面方法
//删除小节
removeVideo(videoId) {
this.$confirm("此操作将永久删除小节信息, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
//点击确定,执行then方法
video.deleteByVideoIdAndAliyun(videoId).then((resp) => {
//删除成功
//提示信息
this.$message({
type: "success",
message: "删除成功!",
});
//刷新页面
this.getChapterVideoByCourseId();
});
});
}
- api方法
//根据小节id删除单个阿里云视频
deleteByVideoIdAndAliyun(videoId) {
return request({
url: `/eduservice/edu-video/deleteVideoByid/${
videoId}`,
method: `delete`,
})
},
2、后端部分
- EduVideoController
//删除小节
@DeleteMapping("/deleteVideoByid/{id}")
public R deleteVideoByid(@PathVariable String id){
eduVideoService.removeVideoIdAndAliyun(id);
return R.ok();
}
- EduVideoService
void removeVideoIdAndAliyun(String id);
- EduVideoServiceImpl
//根据小节id删除视频和小结内容
@Override
public void removeVideoIdAndAliyun(String id) {
EduVideo eduVideo = baseMapper.selectById(id);
String videoSourceId = eduVideo.getVideoSourceId();
if (!StringUtils.isEmpty(videoSourceId)){
//根据视频id删除阿里云视频
vodClient.removeAliyunVideoById(videoSourceId);
}
//删除小节id
baseMapper.deleteById(id);
}
- 测试
点击删除小节后,数据库和阿里云点播的视频都被删除
点击删除章节,如果她下面有小节的话就会提示不能删除
熔断器Hystrix
一、 Hystrix 基本概念
1、Spring Cloud调用接口过程配合
Spring Cloud 在接口调用上,大致会经过如下几个组件配合:
Feign -----> Hystrix —> Ribbon —> Http Client(apache http components 或者 Okhttp) 具体交互流程上,如下
图所示:
阿昌的理解:
先接口化请求调用,对指定请求的地址进行设定,并交给Feign过来;Feign实际去请求远程提供的接口;如果中途出现异常由Hystrix来中断和熔断服务;如果请求是一个集群,当请求传递给Ribbon后,会根据各个因素和设定的规则来选择要请求的哪个服务器接口来处理,最后httpClient来获取请求并执行调用最后的方法并返回
2、Hystrix概念
二、Feign结合Hystrix使用
改造service-edu模块
1、在service的pom中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--hystrix依赖,主要是用 @HystrixCommand -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、在配置文件中添加hystrix配置
在调用端配置
#开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
3、在service-edu的client包里面创建熔断器的实现类
@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("删除多个视频出错了");
}
}
4、修改VodClient接口的注解
添加属性,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);
}