微服务技术
MyBatis-Plus
@TableName:用来指定表名,写在类的头上
@TableId:用来指定表中的主键关键字段信息
- 有value属性,用来指定具体的字段,type属性,该字段的生成方式,有自增长、自定义、还有雪花算法自动生成
@TableField:用来指定表中的普通字段信息
使用TableField几个场景:
- 成员变量与数据库名称不一致
- 成员变量以
is
开头,但是是布尔值,若不标注会自动去掉is
- 成员变量名与数据库关键字冲突 (最好是自定义字段的时候规避)
- 成员变量不是数据库字段(参数直接写
exist = false
)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {
private Long id;
// 成员变量与数据库名称不一致
private String name;
// 成员变量以`is`开头,但是是布尔值,若不标注会自动去掉`is`
private Boolean isMarried;
// 成员变量名与数据库关键字冲突 (最好是自定义字段的时候规避)
private Integer order;
// 成员变量不是数据库字段(参数直接写`exist = false`)
private String address;
}
在application.yml
配置文件中也能进行全局配置
1 | mybatis-plus: |
条件构造器的使用
Wrapper类的结构
1 | Wrapper: |
自定义SQL写法
使用场景:当where比较难写,同时where前面的条件也比较难写
① 基于Wrapper构建where条件
1
2
3
4List<Long> Ids = Arrays.asList(1L,2L,3L);
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.in("id", Ids);
long update = userMapper.updateBalanceById(userUpdateWrapper,200);② 在自定义的mapper方法中用Param注解声明wrapper变量名称,必须是ew
1
long updateBalanceById(; UpdateWrapper<User> wrapper, Integer amount)
③ 自定义SQL 并使用wrapper条件
1
2
3<update id="updateBalanceById">
update `user` set `balance` = `balance` - #{ amount } ${ew.customSqlSegment}
</update>
批处理
三种处理方法
① for循环一条一条插入,极慢,不推荐使用
② 使用IService中的集合批量插入方法
1
2
3
4
5
6ArrayList<User> users = new ArrayList<>();
for (int j =1; j < 10000; j++) {
users.add(buildUser(j));
}
// 使用batch 集合插入
userService.saveBatch(users);③ 使用MySQL自带的配置
1
2# 在数据库驱动后加上
rewriteBatchedStatements = true
插件
代码生成器
有两个插件 MybatisX 和 MybatisPlus 两种插件
Docker
docker安装
卸载,如果存在旧的docker ,则先卸载
1
2
3
4
5
6
7
8
9yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine \
docker-selinux使用 APT 安装
由于
apt
源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。1
2
3
4
5
6
7
8$ sudo apt-get update
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。
为了确认所下载软件包的合法性,需要添加软件源的
GPG
密钥。1
2
3
4
5$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 官方源
# $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg然后,我们需要向
sources.list
中添加 Docker 软件源1
2
3
4
5
6
7
8
9$ echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 官方源
# $ echo \
# "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
# $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null以上命令会添加稳定版本的 Docker APT 镜像源,如果需要测试版本的 Docker 请将 stable 改为 test。
安装 Docker
更新 apt 软件包缓存,并安装
docker-ce
:1
2
3$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin【或者】
使用脚本自动安装
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Ubuntu 系统上可以使用这套脚本安装,另外可以通过
--mirror
选项使用国内源进行安装:若你想安装测试版的 Docker, 请从 test.docker.com 获取脚本
1
2
3
4# $ curl -fsSL test.docker.com -o get-docker.sh
$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun
# $ sudo sh get-docker.sh --mirror AzureChinaCloud执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定(stable)版本安装在系统中。
启动Docker
1
2$ sudo systemctl enable docker
$ sudo systemctl start docker配置镜像加速器
阿里云会提供对应的docker镜像加速器,自行配置
1
2
3
4
5
6
7
8
912 sudo mkdir -p /etc/docker
13 sudo tee /etc/docker/daemon.json <<-'EOF'
14 {
15 "registry-mirrors": ["https://saa6aaym.mirror.aliyuncs.com"]
16 }
17 EOF
20 sudo systemctl daemon-reload
21 sudo systemctl enable docker
22 sudo systemctl start docker
【完成】
安装mysql
1 | docker run -d \ |
docker自动下载镜像,可以自己创建镜像仓库,运行在容器里面,容器可以存在搓个
命令解读
1 | docker run -d \ # docker run 创建并运行 -d 让容器在后台运行 |
常见命令
1 | docker pull 拉取镜像 |
docker ps 输出格式化命令 -a
查看包括已停止的进程
1 docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}" -a同时在系统配置文件中 ~/.bashrc 命令文件中同样可以配置
命令别名:
1
2
3
4 # 找到文件使用vim修改
# 新增
alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
alias dis='docker images'
查看docker 进程日志 -f
持续查看
1 | docker logs -f nginx |
进入容器内部 -it
使用终端
1 | docker exec -it nginx bash |
docker删除image
1 | docker stop [进程名] # 先进行停止进程操作 |
数据卷
数据卷(volume)是一个虚拟的目录,是容器目录与宿主机目录之间的映射的桥梁
创建数据卷并自动挂载
1 | docker run -v 数据卷:容器内目录 |
这里创建一个带有数据卷的nginx容器
1 | docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx |
通过docker volume ls
命令来查看数据卷
1 | docker volume ls |
通过docker volume inspect [数据卷名称] ls
查看该数据卷的详细数据(查数据卷详情)
1 | docker volume inspect html ls |
这个时候,本地主机的目录:/usr/share/nginx/html
就与docker容器里面的目录:/var/lib/docker/volumes/html/_data
形成了映射关系
查看一个容器是否挂载了数据卷(查容器挂载详情)
1 | docker inspect mysql |
本地目录数据挂载
要挂载本地目录必须以’/‘或者’./‘开头,如果直接以名称开头,会被识别成数据卷而非本地目录
-v mysql:/var/lib/mysql # 会被识别成一个数据卷叫mysql
1
2
3
- ```bash
-v ./mysql:/var/lib/mysql # 会被识别为当前目录下的mysql目录
【实践】:
- 查看mysql容器,判断是否有数据卷挂载
- 基于宿主机目录实现Mysql数据目录、配置文件、初始化脚本的挂载(查阅官方文档)
- ①挂载/root/mysql/data到容器内的/var/lib/mysql目录
- ②挂载/root/mysql/init到容器内的/docker-entrypoint-initdb.d目录,携带课前资料准备的SQL脚本
- ③挂载/root/mysql/conf到容器内的/etc/mysql/conf.d目录,携带课前资料准备的配置文件
【操作】
这里直接run 挂载
1
2
3
4
5
6
7
8
9docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=159357 \
-v /root/mysql/data:/var/lib/mysql \
-v /root/mysql/init:/docker-entrypoint-initdb.d \
-v /root/mysql/conf:/etc/mysql/conf.d \
mysql
镜像层级
入口(EntryPoint)
镜像运行入口,一般就是程序启动脚本和参数,镜像入口
层(layer)
添加安装包、依赖、配置等,每次操作都形成新的一层
基础镜像(BaseImage)
应用依赖的系统函数库、环境、配置、文件等,镜像最底层
dockerfile文件
镜像就是包含应用程序、程序运行所需的系统函数库、运行配置文件的文件包。构建镜像的过程就是把上述文件打包的过程
镜像文件
1 | # 基础镜像 |
构建镜像
命令:docker build -t [镜像名]:[版本] ./Dockerfile
-t
表示给镜像构建一个指定的标签
1 | docker build -t docker-demo ./Dockerfile |
运行镜像
1 | docker run -d --name hema -p 8080:8080 docker-demo |
若发现没有运行成功,重启linux ,重新运行docker容器 docker start
Docker 网络
命令
1 | docker network ls # 查看所有网络 |
这里直接创建自定义网络
1 | docker network heima |
连接网络到容器
1 | docker network connect heima mysql |
或者创建容器的时候进行连接网络
1 | docker run -d \ |
然后在一个容器里面就可以ping另一个容器里的进程服务
Docker 部署
部署java应用
首先,进入到java项目(顶级父类的maven)进行打包
打包完成之后,找到Dokcerfile文件进行构建
1 | docker build -t hmall . |
构建完成之后创建容器并运行
1 | docker run -d \ # 后台运行 |
运行之后就能看到结果了
1 | docker ps |
部署前端应用
构建好前端项目,配置nginx配置
nginx.conf
1 | worker_processes 1; |
创建docker容器并运行
1 | docker run -d \ |
这样运行之后就可以查看了
1 | docker ps |
DockerCompose
dockerCompose 通过一个单独的docker-compose.yml 模板文件来定义一组相关联的容器,帮助我们实现多个互相关联的docker容器的快速部署
对比两种部署方式
1 | docker run -d \ |
1 | version:"3.8" |
或者是四个程序一起部署的
1 | version: "3.8" |
直接使用compose命令进行部署
所有所需的文件都在当前目录下
1 | # 启动所有服务 |
使用命令查看,查看最好是当前目录(配置文件docker-compose.yml目录)下
1 | # 查看所有镜像 |
1 | # 停止和启动 |
微服务
拆分项目
首先进行安装项目
数据库
创建通用网络
1
docker network create hm-net
创建mysql容器
1
2
3
4
5
6
7
8
9
10docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=159357 \
-v /root/mysql/data:/var/lib/mysql \
-v /root/mysql/init:/docker-entrypoint-initdb.d \
-v /root/mysql/conf:/etc/mysql/conf.d \
--network hm-net \
mysql
后端项目
把local配置文件虚拟机地址以及修改用户名密码为虚拟机mysql用户名密码,然后修改项目启动配置为local启动
前端项目
把nginx项目放在没有中文目录下的文件夹内,通过命令行启动
1 | start nginx.exe |
单体项目
将业务所有功能集中到一个项目
优点 | 缺点 |
---|---|
架构简单 部署成本低 |
团队协作成本高 系统发布效率低 系统可用性差 |
微服务架构
微服务架构,是服务化思想指导下的一套最佳实践架构方案。服务化就是把单体架构中的功能模块拆分成多个独立项目
优点:颗粒度小、团队自治、服务自治
SpringCloud
spring cloud集成了各种微服务功能组件,并基于springboot实现了这些组件的自动装配,从而提供了良好的开箱即用的体验
微服务拆分
拆分原则:
什么时候拆分?
- 创业型项目(先易后难):先采用单体架构,快速开发,快速是错。随着规模扩大,逐渐拆分
- 确定的大型项目(先难后易):资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦
怎么拆分?
- 高内聚:每个微服务的指职责要尽量单一,包含的业务相互关联度高、完整度高
- 低耦合:每个微服务的功能要相对独立,尽量减少对其他服务的依赖
拆分方式
- 纵向拆分:按照业务模块来拆分
- 横向拆分:抽取公共服务,提高复用性
工程结构有两种:
独立的Project
Maven聚合
下面进行拆分
拆分商品服务
在父组件下创建新组件 item-service 创建简单的项目即可,不用创建springboot项目
添加依赖
可以从原本的service模块中拷贝依赖,看哪些需要,哪些不需要
确定了需要的依赖之后,创建目录,拷贝配置文件,也是查看需要哪些配置,不需要的就直接删除,不知道需不需要先删除,后续再查看是否需要再添加即可
配置好了目录和配置文件之后,就进入了代码编写
目录结构
com └─hmall └─item ├─controller ├─domain │ ├─dto # 数据传输对象 用于前端表单提交或者其他服务应用 │ ├─po # 实体类对象 用于数据库对接 │ ├─query # 查询类,用于查询信息参数集成 │ └─vo # view对象 用于返回前端数据渲染 ├─mapper └─service
4. 补全项目启动配置文件(application.yml) 5. 创建对应的代码目录 6. 从对应服务模块复制对应代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 需要注意的步骤
1. 根据模块创建新的项目模块
2. 复制补全需要的依赖文件(pom.xml)
3. 创建启动文件,修改配置
```java
@MapperScan("包扫描路径")
@SpringBootApplication
public class XxxApplication {
public static void main(String[] args) {
SpringApplication.run(XxxApplication.class, args);
}
}
处理到这里就会发现,模块之间的依赖问题,一个模块依赖于另一个模块的内容,这个时候就需要远程调用了
远程调用
这里需要利用java代码向java服务器发送请求,达到调用其他服务器数据的作用
在自动调用的时候注入bean
1 | // 用于发送请求 |
然后需要发送请求的地方进行注入并调用
1 |
|
服务治理
注册中心
主要用于服务的分发工作,一方面接收服务的提供者,一方面记录并监控微服务实例状态,推送至服务消费者
服务提供者
暴露服务接口,并且通过心跳机制向注册中心报告自己的健康状态,当心跳异常的时候,注册中心会将异常服务剔除
服务消费者
调用其他服务提供的接口,会在启动时注册自己的信息到注册中心,消费者可以从注册中心订阅和拉去服务信息
Nacos 注册中心
安装
准备一个nacos的数据表,导入到数据库中
部署
配置nacos配置文件 custom.env,并上传配置文件到/nacos/root
目录
1 | PREFER_HOST_MODE=hostname |
创建nacos容器
这里使用本地包加载的方式提前挂载好了镜像
1 | docker load -i nacos.tar |
1 | docker run -d \ |
运行成功,可以直接查看服务进程
1 | docker logs -f nacos |
在本地机上可以登录访问http://虚拟机ip:8848/nacos
可以进入nacos后台管理页面,登录用户名和密码都是nacos
到此,部署完成
服务注册
①引入依赖
<!--Nacos 服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
1
2
3
4
5
6
7
8
9
10
11
- **②配置Nacos地址**
- ```yml
spring:
application:
name: item-service # 微服务名称
cloud:
# nacos 配置
nacos:
server-addr: 192.168.1.8:8848 # nacos 服务地址
直接启动项目即可,可以通过启动多个实例来模拟多模块的场景
服务发现
步骤和服务注册类似,前两步都是引入依赖和配置Nacos地址
不同的是,服务发现需要拉去服务实例列表
③服务发现
就可以改造调用查询接口的代码,使用服务发现调用接口获取信息
1 | // 1、根据服务名称,获取服务列表实例 |
但是这种远程调用的方式很麻烦,只是查询一个商品,需要很多代码,这时候就需要一个更好实现的工具
OpenFeign
OpenFeign是一个声明式的http客户端,是springcloud在eureka公司开源的feign的基础上改造来的,能使我们更加优雅的实现http请求
导入OpenFeign
① 引入依赖,包括OpenFeign 和负载均衡组件SpringCloudLoadBalancer
1 | <!--openFeign--> |
②通过@EnableFeignClients注解,启用OpenFeign功能
1 | // 开启openFeign |
③ 编写FeignClient接口
1 | // 标记为一个Feign客户端 |
get 请求只用来传递基本参数 而且加注解 @RequestParam
post 请求用来传递对象参数 并且加注解 @RequestBody
④ 使用FeignClient进行远程调用
1 |
|
不过这种方式它发送请求的效率比较低,使用的是HttpURLConnection:默认实现,不支持连接池,每次发送请求都会自动创建一个Client,效率非常低
这里进行优化,使用支持连接池的OKHtpp来发送请求
① 引入OpenFeign 对 OKHttp支持的依赖
1 | <!--OK http 的依赖 --> |
② 开启连接池配置
1 | feign: |
OpenFeign最佳实践
以上使用openFeign方式不是最佳的方式,这里进行更加优雅的实现
方式一:在对应的模块下面拆分功能,把该功能拆分出来
方式二:单独创个模块,单独实现该功能
这里拆用方式二
① 创建模块,导入所需依赖,只用导入项目common
依赖和openFeign
依赖以及负载均衡
依赖即可
② 创建目录,导入响应dto
以及对应client
③ 删除其他项目用到的dto
以及client
接口,添加该模块依赖
④ 在需要OpenFeign
功能的模块启动类上添加FeignClient
的模块路径
1 | // 开启openFeign 如果client的配置不在本模块下,记得配置basePackages路径 |
这样就做到了一个模块不依赖于其他模块最佳实现OpenFeign
OpenFeign 日志输出
若FeifnClient所在的包的日志级别达到了Debug时,才会输出日志,而且其日志级别有4级,None、Basic、Headers、Full,由于默认级别为None,所以默认看不到日志信息
① 在刚刚配置OpenFeignClient模块中创建config文件
1 | public class DefaultFeignConfig { |
② 在需要OpenFeign功能的启动器的@EnableFeignClients注解上加上basePackageClasses属性,值为刚刚创建的配置类的类型
1 |
这样就能通过配置类自定义设置OpenFeign的日志级别了
网关
主要就是路由配置,能够拦截请求分配请求端口
配置
① 新建一个模块gateway
,添加依赖,需要nacos
和负载均衡
以及springcloud
网关依赖,创建启动类
② 配置模块配置文件application.yml
1 | server: |
路由属性
网关路由对应的java类型是RouteDefinition,其中常见的属性有
- id:路由唯一标识
- uri:路由目标地址
- predicates:路由断言,判断请求是否符合当前路由
- filters:路由过滤器,对请求或者响应做特殊处理
路由断言
名称 | 说明 | 示例 |
---|---|---|
Path | 请求路径必须符合指定的方式 | - Path=/users/**,/user/{id} |
…… |
路由过滤器
名称 | 说明 | 示例 |
---|---|---|
AddRequestHeader | 给当前请求添加一个请求头 | AddRequestHeader=headerName,headerValue |
StripPrefix | 去除请求路径中的N段前缀 | StripPrefix=1,则路径/a/b转发时只保留/b |
…… |
如果需要对所有的服务进行配置路由过滤器,就在routes的同级目录配置 default-filters
1 | server: |
网关登录校验
具体操作有以下步骤:
- 如何在网关转发之前做登录校验
- 网关如何将用户信息传递给微服务
- 如何在微服务之间传递用户信息
自定义过滤器
网关过滤器有两种:
GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效(这种的使用较少,所以需要使用的时候了解一下就行了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {
public GatewayFilter apply(Config config) {
// // 内部类
// return new GatewayFilter() {
// @Override
// public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// // 这里就跟globalFilter一样了 获取请求,实现过滤器业务就行了
// ServerHttpRequest request = exchange.getRequest();
// // 实现业务功能
// System.out.println("pring any filter running");
// // 放行
// return chain.filter(exchange);
// }
// };
// 因为内部类不能实现Ordered接口 所以需要使用装饰类进行实现
return new OrderedGatewayFilter(new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 这里就跟globalFilter一样了 获取请求,实现过滤器业务就行了
ServerHttpRequest request = exchange.getRequest();
// 实现业务功能
String a = config.getA();
String b = config.getB();
String c = config.getC();
System.out.println("pring any filter running");
System.out.println(a+b+c);
// 放行
return chain.filter(exchange);
}
}, 1);
}
// 自定义配置属性,成员变量名称很重要,下面会用到
public static class Config{
private String a;
private String b;
private String c;
}
// 将变量名称一次返回,顺序很重要,将来要读取参数时,要按顺序获取
public List<String> shortcutFieldOrder() {
return List.of("a","b","c");
}
// 将Config 字节码传递给父类,父类负责帮我们读取yaml配置
public PrintAnyGatewayFilterFactory() {
super(Config.class);
}
}还要在配置文件中进行配置才能生效
1
2
3
4
5
6
7
8
9
10
11
12
13server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.1.8:8848
gateway: # 网关配置
routes:
- ……
default-filters:
- PrintAny=1,2,3 #自定义GatewayFilterGlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyFilter implements GlobalFilter, Ordered{
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
ServerHttpRequest request = exchange.getRequest();
// 过滤器业务逻辑
System.out.println("Global pre 阶段执行了");
// 放行
return chain.filter(exchange);
}
public int getOrder() {
// 数字越小,优先级越高
return 0;
}
}
实现登录校验
在网关模块创建拦截器,这里使用的是JWT校验
1 |
|
这里实现了过滤器,还需要在common模块中进行拦截,拦截用户信息到context,然后controller通过context拿取用户信息
配置springMVC拦截器
1 | // common模块下的 interceptors包下 |
配置完成还要对拦截器进行注册
1 |
|
注册完成拦截器之后,需要扫描配置类,需要在配置文件spring.factories中添加配置
1 | # 在common模块的resources目录下的META-INF/spring.factories文件添加配置, |
微服务之间传递用户信息
因为用户信息存在context中,而context是通过拦截器拦截请求过滤器获取其中的用户信息得到的,但是微服务之间发送请求是通过openFeign实现的,所以需要通过openFeign的拦截器接口保存用户信息参数,这里在DefaultFeignConfig里面配置
1 | /** |
配置管理
配置共享
直接在nacos里面创建配置文件即可
举例:shared-jdbc.yaml
1 | spring: |
拉取共享配置
① 添加依赖
1 | <!--nacos配置管理--> |
② 创建bootstarp.yaml配置文件
1 | spring: |
③ 在基础配置application.yaml中补全nacos配置文件中需要的部分
1 | server: |
重启服务,springcloud就会先读取bootstarp.yaml配置,然后胜场applicationConnext上下文文件,然后再去读取application.yaml文件
配置热更新
当修改配置文件中的配置时,微服务无需启动即可使配置生效
前提条件
nacos中要有一个于微服务名有关的配置文件,中间的可选
1
[服务名]-[spring.active.profile].[后缀名]
文件名称由三部分组成:
- **
服务名
**:我们是购物车服务,所以是cart-service
- **
spring.active.profile
**:就是spring boot中的spring.active.profile
,可以省略,则所有profile共享该配置 - **
后缀名
**:例如yaml
cart-service.yaml
配置文件内容1
2
3hm:
cart:
maxAmount: 1 # 购物车商品数量上限- **
微服务中要以特定方式读取需要更新的配置属性
1
2
3
4
5
6
7
public class CartProperties {
private Integer maxAmount;
}在业务层进行注入调用即可
动态路由
实现动态路由首先要将路由配置保存到nacos,当nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息。
实现:
① 监听nacos配置变更消息
② 当配置变更时,将最新的路由信息更新到网关路由表
具体实现
① 添加依赖到网关模块
1 | <!--nacos配置管理--> |
② 添加bootstrap.yaml文件
1 | spring: |
③ 移除原本的路由配置
④ 创建路由挂载类
1 |
|
⑤ 在nacos 创建配置 这里的配置需要是json格式
1 | [ |
再重启网关服务,就能进行动态更新路由了
服务保护
服务保护方案:
请求限流
限制访问微服务的请求的并发量,避免服务因流量激增出现故障
线程隔离
限定每个业务使用线程数量而将故障业务隔离,避免故障扩散
服务熔断
由断路器统计请求的异常的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求。熔断期间,所有请求快速失败,全部走fallback逻辑
实现这些功能SpringCloud有两个组件:Sentinel 和 Hystrix
Sentinel
配置Sentinel
下载jar包
启动
1 | java '-Dserver.port=8090' '-Dcsp.sentinel.dashboard.server=localhost:8090' '-Dproject.name=sentinel-dashboard' '-jar' sentinel-dashboard.jar |
与微服务进行整合
导入依赖
1
2
3
4
5<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>配置配置文件
1
2
3
4
5spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
簇点链路
所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel
监控的资源。默认情况下,Sentinel
会监控SpringMVC
的每一个Endpoint
(接口(controller))。
因此,我们看到/carts
这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。
不过,需要注意的是,我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts
路径:
所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径
作为簇点资源名:
首先,在cart-service
的application.yml
中添加下面的配置:
1 | spring: |
请求限流
访问的每一个接口就是一个簇点,可以对每个簇点进行配置流控、熔断等功能
配置GET:/Cart限流 QPS为6 ,通过jmtter发送请求,1000次10s,QPS为10 所以会显示6个通过,4个拒绝 达到了限流的功能
线程隔离
在sentinel控制台中,会出现Feign接口的簇点资源,点击流控,切换阈值为并发线程数,配置为5个线程,也就是每秒最多通过2个
Fallback
当查询失败的时候,不会直接抛出异常,会给用户返回值进行处理,从而不影响其他业务的使用
① 创建一个fallback的工厂类 在处理client的模块进行编写
在hm-api模块中给ItemClient
定义降级处理类,实现FallbackFactory
:
1 |
|
② 使用,在hm-api
模块中的com.hmall.api.config.DefaultFeignConfig
类中将ItemClientFallback
注册为一个Bean
:
1 | /** |
③ 在对应的FeignClient的参数上添加fallbackFactory
1 | // 标记为一个Feign客户端 |
④ 在配置中,开启Feign深层的sentinel的监控
1 | feign: |
这样就不会在高并发的情况下,导致访问请求失败了,而是进行处理的结果
服务熔断
断路器实现熔断会根据调用的异常比例、慢请求比例、如果超出熔断阈值会熔断该服务。即拦截访问该服务的一切请求;而服务恢复时,会放行该服务的请求。