课程线上笔记

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
    @TableName("tb_user")
    public class User {
    @TableId(value="id",type=IdType.AUTO)
    private Long id;
    @TableField("username") // 成员变量与数据库名称不一致
    private String name;
    @TableField("is_married") // 成员变量以`is`开头,但是是布尔值,若不标注会自动去掉`is`
    private Boolean isMarried;
    @TableField("order") // 成员变量名与数据库关键字冲突 (最好是自定义字段的时候规避)
    private Integer order;
    @TableField(exist = false) // 成员变量不是数据库字段(参数直接写`exist = false`)
    private String address;
    }

application.yml 配置文件中也能进行全局配置

1
2
3
4
5
6
7
8
9
10
mybatis-plus:
type-aliases-package: com.itheima.mp.domain.po
mapper-locations: "classpath*:mapper/*.xml"
configuration:
map-underscore-to-camel-case: true # 是否开启下划线驼峰映射
cache-enabled: false # 是否开启二级缓存
global-config:
db-config:
id-type: assign_id # id策略为雪花算法
update-strategy: not_null # 更新策略,只更新非空字段

条件构造器的使用

Wrapper类的结构

1
2
3
4
5
6
7
Wrapper:
└─AbstractWrapper
├─AbstractLambdaWrapper
│ ├─LambdaUpdateWrapper
│ └─LambdaQueryWrapper
└─UpdateWrapper
└─QueryWrapper

自定义SQL写法

使用场景:当where比较难写,同时where前面的条件也比较难写

  • ① 基于Wrapper构建where条件

    1
    2
    3
    4
    List<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(@Param("ew")UpdateWrapper<User> wrapper,@Param("amount")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
    6
    ArrayList<User> users = new ArrayList<>();
    for (int j =1; j < 10000; j++) {
    users.add(buildUser(j));
    }
    // 使用batch 集合插入
    userService.saveBatch(users);
  • ③ 使用MySQL自带的配置

    1
    2
    # 在数据库驱动后加上
    rewriteBatchedStatements = true
插件

代码生成器

有两个插件 MybatisXMybatisPlus 两种插件

Docker

docker安装
  1. 卸载,如果存在旧的docker ,则先卸载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-engine \
    docker-selinux
  2. 使用 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。

  3. 安装 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)版本安装在系统中。

  4. 启动Docker

    1
    2
    $ sudo systemctl enable docker
    $ sudo systemctl start docker
  5. 配置镜像加速器

    阿里云会提供对应的docker镜像加速器,自行配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    12  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
2
3
4
5
6
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql

docker自动下载镜像,可以自己创建镜像仓库,运行在容器里面,容器可以存在搓个

命令解读

1
2
3
4
5
6
docker run -d \  # docker run 创建并运行 -d 让容器在后台运行
--name mysql \ # --name 容器起名,必须唯一
-p 3306:3306 \ # -p 端口映射 前面是本地端口,后面是容器端口
-e TZ=Asia/Shanghai \ # -e 配置环境变量
-e MYSQL_ROOT_PASSWORD=123 \ # -e 后面是环境变量参数
mysql:8.3.0 # 镜像名称和tag(版本号,不写就是最新版)
常见命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker pull 拉取镜像
docker images 查看本地镜像
docker rmi 删除镜像
docker bulid 使用本地的dockerfile,通过 构建自己的镜像
docker save 保存在本地文件 通过
docker load 加载其他镜像到本地
docker push 推送镜像到镜像仓库
docker run 创建容器并运行
docker stop 停下容器进程,不会删除容器
docker start 启动容器进程
docker ps 查看当前容器的状态 后面加上-a 查看包括已停止的进程
docker rm 删除容器
docker logs 查看容器日志 -f 持续查看
docker exec 可以进入容器内部

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
2
docker stop [进程名]  # 先进行停止进程操作
docker rm [容器名] # 在进行删除操作
数据卷

数据卷(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
    9
    docker 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
2
3
4
5
6
7
8
9
10
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
# 运行命令行
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime &ho $TZ > /etc/timezone
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]

构建镜像

命令: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
2
3
4
5
6
7
docker network ls  # 查看所有网络
docker network create # 创建一个网络
docker network rm # 移除一个网络
docker network prune # 清除未使用的网络
docker network connect # 使指定容器链接加入某网络
docker network disconnect # 使指定容器链接离开某网络
docker network inspect # 查看网络详细信息

这里直接创建自定义网络

1
docker network heima

连接网络到容器

1
docker network connect heima mysql

或者创建容器的时候进行连接网络

1
2
3
4
5
docker run -d \
-- name hema \
-p 8080:8080 \
--network heima \
docker-demo

然后在一个容器里面就可以ping另一个容器里的进程服务

Docker 部署

部署java应用

首先,进入到java项目(顶级父类的maven)进行打包

打包完成之后,找到Dokcerfile文件进行构建

1
docker build -t hmall .

构建完成之后创建容器并运行

1
2
3
4
5
docker run -d \		# 后台运行
--name hmall \ # 容器名称
-p 8080:8080 \ # 端口映射
--network heima \ # 连接网络(自定义网络)
hmall # 镜像名称(自己创建的镜像)

运行之后就能看到结果了

1
docker ps

部署前端应用

构建好前端项目,配置nginx配置

nginx.conf

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
worker_processes  1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/json;

sendfile on;

keepalive_timeout 65;

server {
listen 18080;
# 指定前端项目所在的位置
location / {
root /usr/share/nginx/html/hmall-portal;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /api {
rewrite /api/(.*) /$1 break;
proxy_pass http://hmall:8080;
}
}
server {
listen 18081;
# 指定前端项目所在的位置
location / {
root /usr/share/nginx/html/hmall-admin;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /api {
rewrite /api/(.*) /$1 break;
proxy_pass http://hmall:8080;
}
}
}

创建docker容器并运行

1
2
3
4
5
6
7
8
docker run -d \
--name nginx \
-p 18080:18080 \
-p 18081:18081 \
-v /root/demo/nginx/html:/usr/share/nginx/html \
-v /root/demo/nginx/nginx.conf:/etc/nginx/nginx.conf \
--network heima \
nginx

这样运行之后就可以查看了

1
docker ps
DockerCompose

dockerCompose 通过一个单独的docker-compose.yml 模板文件来定义一组相关联的容器,帮助我们实现多个互相关联的docker容器的快速部署

对比两种部署方式

1
2
3
4
5
6
7
8
9
10
docker 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 hmall
mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version:"3.8"
services:
mysql:
image: mysql
container_name: mysql
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 159357
volumes:
- "/root/mysql/data:/var/lib/mysql"
- "/root/mysql/init:/docker-entrypoint-initdb.d"
- "/root/mysql/conf:/etc/mysql/conf.d"
networks:
- hmall

或者是四个程序一起部署的

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
version: "3.8"

services:
mysql:
image: mysql
container_name: mysql
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123
volumes:
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
- "./mysql/init:/docker-entrypoint-initdb.d"
networks:
- hm-net
hmall:
build:
context: .
dockerfile: Dockerfile
container_name: hmall
ports:
- "8080:8080"
networks:
- hm-net
depends_on:
- mysql
nginx:
image: nginx
container_name: nginx
ports:
- "18080:18080"
- "18081:18081"
volumes:
- "./nginx/nginx.conf:/etc/nginx/nginx.conf"
- "./nginx/html:/usr/share/nginx/html"
depends_on:
- hmall
networks:
- hm-net
networks:
hm-net:
name: hmall

直接使用compose命令进行部署

所有所需的文件都在当前目录下

1
2
3
4
5
6
7
8
# 启动所有服务
docker compose up

# 在后台启动所有服务
docker compose up -d

# 在后台所有启动服务,指定编排文件
docker compose -f docker-compose.yml up -d

使用命令查看,查看最好是当前目录(配置文件docker-compose.yml目录)下

1
2
# 查看所有镜像
docker-compose -f ./my-compose.yml ps
1
2
3
4
5
6
# 停止和启动
docker compose stop
docker compose start

# 销毁容器所有内容
docker compose down

微服务

拆分项目

首先进行安装项目

数据库

  • 创建通用网络

    1
    docker network create hm-net
  • 创建mysql容器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    docker 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实现了这些组件的自动装配,从而提供了良好的开箱即用的体验

微服务拆分

拆分原则:

  1. 什么时候拆分

    1. 创业型项目(先易后难):先采用单体架构,快速开发,快速是错。随着规模扩大,逐渐拆分
    2. 确定的大型项目(先难后易):资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦
  2. 怎么拆分

    • 高内聚:每个微服务的指职责要尽量单一,包含的业务相互关联度高、完整度高
    • 低耦合:每个微服务的功能要相对独立,尽量减少对其他服务的依赖

    拆分方式

    • 纵向拆分:按照业务模块来拆分
    • 横向拆分:抽取公共服务,提高复用性

工程结构有两种:

  • 独立的Project

  • Maven聚合

下面进行拆分

拆分商品服务

  • 在父组件下创建新组件 item-service 创建简单的项目即可,不用创建springboot项目

  • 添加依赖

    • 可以从原本的service模块中拷贝依赖,看哪些需要,哪些不需要

    • 确定了需要的依赖之后,创建目录,拷贝配置文件,也是查看需要哪些配置,不需要的就直接删除,不知道需不需要先删除,后续再查看是否需要再添加即可

    • 配置好了目录和配置文件之后,就进入了代码编写

    • 目录结构

    • com
      └─hmall
          └─item
              ├─controller
              ├─domain
              │  ├─dto # 数据传输对象 用于前端表单提交或者其他服务应用
              │  ├─po # 实体类对象 用于数据库对接
              │  ├─query # 查询类,用于查询信息参数集成
              │  └─vo # view对象 用于返回前端数据渲染
              ├─mapper
              └─service
      
      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);
      }
      }
      4. 补全项目启动配置文件(application.yml) 5. 创建对应的代码目录 6. 从对应服务模块复制对应代码

处理到这里就会发现,模块之间的依赖问题,一个模块依赖于另一个模块的内容,这个时候就需要远程调用

远程调用

这里需要利用java代码向java服务器发送请求,达到调用其他服务器数据的作用

在自动调用的时候注入bean

1
2
3
4
5
// 用于发送请求
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}

然后需要发送请求的地方进行注入并调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Resource
private RestTemplate restTemplate;

public void test(){
// 发送请求
// 通过restTemplate 发送http请求 得到http响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtils.join(itemIds, ","))
);
}

// 解析响应
if (!response.getStatusCode().is2xxSuccessful()){
// 请求失败直接结束
return;
}
// 请求成功,拿到数据
List<ItemDTO> items = response.getBody();
服务治理

注册中心

主要用于服务的分发工作,一方面接收服务的提供者,一方面记录并监控微服务实例状态,推送至服务消费者

服务提供者

暴露服务接口,并且通过心跳机制向注册中心报告自己的健康状态,当心跳异常的时候,注册中心会将异常服务剔除

服务消费者

调用其他服务提供的接口,会在启动时注册自己的信息到注册中心,消费者可以从注册中心订阅和拉去服务信息

Nacos 注册中心

安装

准备一个nacos的数据表,导入到数据库中

部署

配置nacos配置文件 custom.env,并上传配置文件到/nacos/root目录

1
2
3
4
5
6
7
8
9
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=192.168.1.8
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=159357
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai

创建nacos容器

这里使用本地包加载的方式提前挂载好了镜像

1
docker load -i nacos.tar
1
2
3
4
5
6
7
8
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim

运行成功,可以直接查看服务进程

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // 1、根据服务名称,获取服务列表实例
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
if (CollUtils.isEmpty(instances)) return;
// 2、手写负载均衡、从服务列表中挑选一个实例
ServiceInstance serviceInstance = instances.get(RandomUtil.randomInt(instances.size()));
// 3、取出服务ip拼接 发送请求

// 通过restTemplate 发送http请求 得到http响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
serviceInstance.getUri() + "/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtils.join(itemIds, ","))
);

但是这种远程调用的方式很麻烦,只是查询一个商品,需要很多代码,这时候就需要一个更好实现的工具

OpenFeign

OpenFeign是一个声明式的http客户端,是springcloud在eureka公司开源的feign的基础上改造来的,能使我们更加优雅的实现http请求

导入OpenFeign

① 引入依赖,包括OpenFeign 和负载均衡组件SpringCloudLoadBalancer

1
2
3
4
5
6
7
8
9
10
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

②通过@EnableFeignClients注解,启用OpenFeign功能

1
@EnableFeignClients // 开启openFeign

③ 编写FeignClient接口

1
2
3
4
5
6
@FeignClient("item-service") // 标记为一个Feign客户端
public interface ItemClient {

@GetMapping("/items")
List<ItemDTO> queryItemById(@RequestParam("ids") Collection<Long> ids);
}

get 请求只用来传递基本参数 而且加注解 @RequestParam

post 请求用来传递对象参数 并且加注解 @RequestBody

④ 使用FeignClient进行远程调用

1
2
3
4
@Resource
private ItemClient itemClient;

List<ItemDTO> items = itemClient.queryItemById(itemIds);

不过这种方式它发送请求的效率比较低,使用的是HttpURLConnection:默认实现,不支持连接池,每次发送请求都会自动创建一个Client,效率非常低

这里进行优化,使用支持连接池的OKHtpp来发送请求

① 引入OpenFeign 对 OKHttp支持的依赖

1
2
3
4
5
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>

② 开启连接池配置

1
2
3
feign:
okhttp:
enabled: true # 开启OKHttp功能
OpenFeign最佳实践

以上使用openFeign方式不是最佳的方式,这里进行更加优雅的实现

方式一:在对应的模块下面拆分功能,把该功能拆分出来

方式二:单独创个模块,单独实现该功能

这里拆用方式二

① 创建模块,导入所需依赖,只用导入项目common依赖和openFeign依赖以及负载均衡依赖即可

② 创建目录,导入响应dto以及对应client

③ 删除其他项目用到的dto以及client接口,添加该模块依赖

④ 在需要OpenFeign功能的模块启动类上添加FeignClient的模块路径

1
@EnableFeignClients(basePackages = "com.hmall.api.client") // 开启openFeign 如果client的配置不在本模块下,记得配置basePackages路径

这样就做到了一个模块不依赖于其他模块最佳实现OpenFeign

OpenFeign 日志输出

若FeifnClient所在的包的日志级别达到了Debug时,才会输出日志,而且其日志级别有4级,NoneBasicHeadersFull,由于默认级别为None,所以默认看不到日志信息

① 在刚刚配置OpenFeignClient模块中创建config文件

1
2
3
4
5
6
7
8
9
10
11
public class DefaultFeignConfig {

/**
* feign 日志配置
* 只有调试的时候才会使用这个日志功能
* */
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
}

② 在需要OpenFeign功能的启动器的@EnableFeignClients注解上加上basePackageClasses属性,值为刚刚创建的配置类的类型

1
@EnableFeignClients(basePackages = "com.hmall.api.client",basePackageClasses = DefaultFeignConfig.class) 

这样就能通过配置类自定义设置OpenFeign的日志级别了

网关

主要就是路由配置,能够拦截请求分配请求端口

配置

① 新建一个模块gateway,添加依赖,需要nacos负载均衡以及springcloud网关依赖,创建启动类

② 配置模块配置文件application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.1.8:8848
gateway: # 网关配置
routes:
- id: item-service
uri: lb://item-service
predicates: # 路由断言
- Path=/items/**,/search/** # 配置多个controller
- id: user-service
uri: lb://user-service
predicates:
- Path=/users/**,/addresses/** # 配置多个controller
路由属性

网关路由对应的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
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.1.8:8848
gateway: # 网关配置
routes:
- ……
default-filters:
- AddRequestHeader=truth2,no way back # 所有服务都生效

网关登录校验

具体操作有以下步骤:

  1. 如何在网关转发之前做登录校验
  2. 网关如何将用户信息传递给微服务
  3. 如何在微服务之间传递用户信息
自定义过滤器

网关过滤器有两种:

  • 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

    @Component
    public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {
    @Override
    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() {
    @Override
    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);
    }

    // 自定义配置属性,成员变量名称很重要,下面会用到
    @Data
    public static class Config{
    private String a;
    private String b;
    private String c;
    }

    // 将变量名称一次返回,顺序很重要,将来要读取参数时,要按顺序获取
    @Override
    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
    13
    server:
    port: 8080
    spring:
    application:
    name: gateway
    cloud:
    nacos:
    server-addr: 192.168.1.8:8848
    gateway: # 网关配置
    routes:
    - ……
    default-filters:
    - PrintAny=1,2,3 #自定义GatewayFilter
  • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Component
    public class MyFilter implements GlobalFilter, Ordered{

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    // 获取请求
    ServerHttpRequest request = exchange.getRequest();
    // 过滤器业务逻辑
    System.out.println("Global pre 阶段执行了");
    // 放行
    return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
    // 数字越小,优先级越高
    return 0;
    }
    }
实现登录校验

在网关模块创建拦截器,这里使用的是JWT校验

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
55
56
57
58
59
60
61
62
63
64
65
66
67
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

@Resource
private AuthProperties authProperties;

private final AntPathMatcher antPathMatcher = new AntPathMatcher();

@Resource
private JwtTool jwtTool;

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
ServerHttpRequest request = exchange.getRequest();
// 实现登录校验
// 1、判断是否需要登录,不需要直接放行
if (isExclude(request.getPath().toString())){
// 如果是非过滤的路径,直接放行
return chain.filter(exchange);
}
// 2、获取token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
if (headers!=null && !headers.isEmpty()){
token = headers.get(0);
}
// 3、校验并解析token 进行校验
Long userId = null;
try {
userId = jwtTool.parseToken(token);
} catch (Exception e) {
// 进行拦截 设置状态码为 401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 4、在校验成功之后 传递用户信息
String userInfo = userId.toString();
// 修改用户gateway模块中的登录校验拦截器,保存用户到下游的请求头中
exchange.mutate()
.request(builder -> builder.header("user-info",userInfo).build());
// 放行
return chain.filter(exchange);
}

/**
* 判断是否是需要放行的路径
* @param path
* @return
*/
private boolean isExclude(String path) {
for (String pathPattern:
authProperties.getExcludePaths()) {
if (antPathMatcher.match(pathPattern,path)) {
return true;
}
}
return false;
}

@Override
public int getOrder() {
return 0;
}
}

这里实现了过滤器,还需要在common模块中进行拦截,拦截用户信息到context,然后controller通过context拿取用户信息

配置springMVC拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// common模块下的 interceptors包下
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取用户登录信息
String userInfo = request.getHeader("user-info");
// 获取了用户,若不为空,存入ThreadLocal
if (StrUtil.isNotBlank(userInfo)){
// 保存用户信息
UserContext.setUser(Long.valueOf(userInfo));
}
// 放行
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理用户
UserContext.removeUser();
}
}

配置完成还要对拦截器进行注册

1
2
3
4
5
6
7
8
9

@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(new UserInterceptor());
}
}

注册完成拦截器之后,需要扫描配置类,需要在配置文件spring.factories中添加配置

1
2
3
4
5
# 在common模块的resources目录下的META-INF/spring.factories文件添加配置,
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MyBatisConfig,\
com.hmall.common.config.MvcConfig,\
com.hmall.common.config.JsonConfig
微服务之间传递用户信息

因为用户信息存在context中,而context是通过拦截器拦截请求过滤器获取其中的用户信息得到的,但是微服务之间发送请求是通过openFeign实现的,所以需要通过openFeign的拦截器接口保存用户信息参数,这里在DefaultFeignConfig里面配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* openFeign 拦截器配置 配置请求头加上用户信息
* @return
*/
@Bean
public RequestInterceptor userInfoRequestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
Long userId = UserContext.getUser();
if (userId!=null) {
template.header("user-info", userId.toString());
}
}
};
}

配置管理

配置共享

直接在nacos里面创建配置文件即可

举例:shared-jdbc.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
datasource:
url: jdbc:mysql://${hm.db.host}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${hm.db.uname:root}
password: ${hm.db.pw}
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
拉取共享配置

① 添加依赖

1
2
3
4
5
6
7
8
9
10
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

② 创建bootstarp.yaml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: cart-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.1.8 # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: shared-jdbc.yaml # 共享mybatis配置
- dataId: shared-log.yaml # 共享日志配置
- dataId: shared-swagger.yaml # 共享日志配置

③ 在基础配置application.yaml中补全nacos配置文件中需要的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8082 # 注意,这个端口不能与其他组件端口一致,需要进行更改
feign:
okhttp:
enabled: true # 开启OKHttp功能

hm: # 这些就是nacos配置文件中占位的配置,需要自己去补充
db:
database: hm-cart
port: 3306
pw: 159357
swagger:
title: '黑马商城购物车服务'
package: com.hmall.cart.controller

重启服务,springcloud就会先读取bootstarp.yaml配置,然后胜场applicationConnext上下文文件,然后再去读取application.yaml文件

配置热更新

当修改配置文件中的配置时,微服务无需启动即可使配置生效

前提条件

  1. nacos中要有一个于微服务名有关的配置文件,中间的可选

    1
    [服务名]-[spring.active.profile].[后缀名]

    文件名称由三部分组成:

    • **服务名**:我们是购物车服务,所以是cart-service
    • **spring.active.profile**:就是spring boot中的spring.active.profile,可以省略,则所有profile共享该配置
    • **后缀名**:例如yaml

    cart-service.yaml配置文件内容

    1
    2
    3
    hm:
    cart:
    maxAmount: 1 # 购物车商品数量上限
  2. 微服务中要以特定方式读取需要更新的配置属性

    1
    2
    3
    4
    5
    6
    7
    @Data
    @Component
    @ConfigurationProperties(prefix = "hm.cart")
    public class CartProperties {
    private Integer maxAmount;
    }

    在业务层进行注入调用即可

动态路由

实现动态路由首先要将路由配置保存到nacos,当nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息。

实现:

① 监听nacos配置变更消息

② 当配置变更时,将最新的路由信息更新到网关路由表

具体实现

① 添加依赖到网关模块

1
2
3
4
5
6
7
8
9
10
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

② 添加bootstrap.yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: cart-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.1.8 # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: shared-log.yaml # 共享日志配置

③ 移除原本的路由配置

④ 创建路由挂载类

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
55
56
57
@Slf4j
@Component
public class DynamicRouteLoader {

@Resource
private NacosConfigManager nacosConfigManager;

@Resource
private RouteDefinitionWriter routeDefinitionWriter;

private final String dataId = "gateway-routes.json";
private final String group = "DEFAULT_GROUP";

private final Set<String> routeIds = new HashSet<>();

@PostConstruct // 项目初始化之后执行
public void initRouteConfigListener() throws NacosException {
// 项目启动,先拉去一次配置,并添加配置监听器
String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() {
return null;
}

@Override
public void receiveConfigInfo(String configInfo) {
// 配置监听到变更,需要进行更新路由表
updateConfigInfo(configInfo);
}
});
// 第一次读取到需要更新到路由表
updateConfigInfo(configInfo);
}

/**
* 解析配置文件,更新路由表
* @param configInfo 配置文件信息
*/
public void updateConfigInfo(String configInfo) {
log.info("监听到路由更新");
// 解析配置文件,转成RouteDefinition
List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
// 先删除旧路由表,再更新新路由表
for (String routeId:routeIds){
routeDefinitionWriter.delete(Mono.just(routeId));
}
routeIds.clear();
// 更新路由表
for (RouteDefinition routeDefinition:routeDefinitions){
// 更新路由表
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
// 记录id 方便下一次更新时删除
routeIds.add(routeDefinition.getId());
}
}
}

⑤ 在nacos 创建配置 这里的配置需要是json格式

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
[
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
},
{
"id": "cart",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/carts/**"}
}],
"filters": [],
"uri": "lb://cart-service"
},
{
"id": "user",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
}],
"filters": [],
"uri": "lb://user-service"
},
{
"id": "trade",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/orders/**"}
}],
"filters": [],
"uri": "lb://trade-service"
},
{
"id": "pay",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/pay-orders/**"}
}],
"filters": [],
"uri": "lb://pay-service"
}
]

再重启网关服务,就能进行动态更新路由了

服务保护

服务保护方案:

  • 请求限流

    限制访问微服务的请求的并发量,避免服务因流量激增出现故障

  • 线程隔离

    限定每个业务使用线程数量而将故障业务隔离,避免故障扩散

  • 服务熔断

    断路器统计请求的异常的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求。熔断期间,所有请求快速失败,全部走fallback逻辑

实现这些功能SpringCloud有两个组件:SentinelHystrix

Sentinel

配置Sentinel

下载jar包

sentinel资源

启动

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
    5
    spring:
    cloud:
    sentinel:
    transport:
    dashboard: localhost:8090

簇点链路

所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel监控的资源。默认情况下,Sentinel会监控SpringMVC的每一个Endpoint(接口(controller))。

因此,我们看到/carts这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。

不过,需要注意的是,我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts路径:

所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径作为簇点资源名:

首先,在cart-serviceapplication.yml中添加下面的配置:

1
2
3
4
5
6
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
http-method-specify: true # 开启请求方式前缀
请求限流

访问的每一个接口就是一个簇点,可以对每个簇点进行配置流控、熔断等功能

配置GET:/Cart限流 QPS为6 ,通过jmtter发送请求,1000次10s,QPS为10 所以会显示6个通过,4个拒绝 达到了限流的功能

线程隔离

在sentinel控制台中,会出现Feign接口的簇点资源,点击流控,切换阈值为并发线程数,配置为5个线程,也就是每秒最多通过2个

Fallback

当查询失败的时候,不会直接抛出异常,会给用户返回值进行处理,从而不影响其他业务的使用

① 创建一个fallback的工厂类 在处理client的模块进行编写

在hm-api模块中给ItemClient定义降级处理类,实现FallbackFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Slf4j
public class ItemClientFallback implements FallbackFactory<ItemClient> {
@Override
public ItemClient create(Throwable cause) {
return new ItemClient() {
@Override
public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause);
// 查询购物车允许失败,查询失败,返回空集合
return CollUtils.emptyList();
}

@Override
public void deductStock(List<OrderDetailDTO> items) {
// 库存扣减业务需要触发事务回滚,查询失败,抛出异常
throw new BizIllegalException(cause);
}
};
}
}

② 使用,在hm-api模块中的com.hmall.api.config.DefaultFeignConfig类中将ItemClientFallback注册为一个Bean

1
2
3
4
5
6
7
/**
* 注册fallback bean
*/
@Bean
public ItemClientFallback ItemClientFallback(){
return new ItemClientFallback();
}

③ 在对应的FeignClient的参数上添加fallbackFactory

1
2
@FeignClient(value = "item-service",configuration = DefaultFeignConfig.class,fallbackFactory = ItemClientFallback.class) // 标记为一个Feign客户端
public interface ItemClient {

④ 在配置中,开启Feign深层的sentinel的监控

1
2
3
4
5
feign:
okhttp:
enabled: true # 开启OKHttp功能
sentinel:
enabled: true # 开启sentinel 监控

这样就不会在高并发的情况下,导致访问请求失败了,而是进行处理的结果

服务熔断

断路器实现熔断会根据调用的异常比例、慢请求比例、如果超出熔断阈值会熔断该服务。即拦截访问该服务的一切请求;而服务恢复时,会放行该服务的请求。

分布式事务