SpringCloud

SpringCloud
李阳跌跌撞撞终于来到这里,我会记得这一天。
一、前置课程
1、MybatisPlus
1.1 项目启动
导入起步依赖
<!―-MybatisPlus-->
<dependency>
<groupId>com.baomidou</grouprd>
<artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</ dependency>自义定
Mapper
继承MyBatisPlus
提供的BaseMapper
接口,并且指定实体类泛型eg:
public interface UserMapper extends BaseMapper<User> {}
1.2 常用注解
约定大于配置
@TableName
:用来指定表名@Table
: 用来指定表中主键字段信息@TableField
:用来指定表中的普通字段信息
使用@TableField
注解的常见场景
- 成员变量名与数据库字段名不一致
- 成员变量名以is开头且是布尔值
- 成员变量名与数据库关键字冲突
- 成员变量不是数据库字段
1.3 常见配置
mybatis-plus: |
1.4 核心功能
模糊查询
//查询名字中带o,balance>1000的id,user等 |
条件更新
// 更新用户名为jack的的余额为2000元 |
条件更新
// 更新id为1,2,4的用户余额扣100 |
自定义SQL
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
- 基于Wrapper构建where条件
- 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
- 自定义SQL,并且使用wrapper条件
Service接口–IService
自定义Service去继承Iservice(要指定实体泛型),自定义ServiceImpl去继承ServiceImpl(要指定Mapper泛型和实体泛型)
eg:
public interface IUserserice extends IService<User> {} |
public class UserServiceImpl extends ServigeImpl<UserMapper,User> implements TUserService {} |
1.5 Restful风格接口
后端传给前端要定义VO,前端传给后端定义DTO
这里有一个我之前没有注意的点,po要转为vo,可以使用hutool
工具包进行拷贝
BeanUtil.copyproperties(原始实体,目标实体) |
1.6 逻辑删除
1.7 枚举处理器
- 在枚举类的值上添加
@EnumValue
注解
配置全局枚举处理器
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
1.8 JSON类型处理器
- 首先定义一个单独的JSON实体
- 然后将User类的info字段修改为UserInfo类型,并声明类型处理器:
- 在类上开启自动映射(这里设置autoResultMap = true是因为MyBatis-Plus对于这种类中再嵌套一个自定义类的,是需要手动在.xml中定义相关字段等,或者像这样开启自动映射)
1.9 分页插件
在未引入分页插件的情况下,MybatisPlus
是不支持分页功能的,IService
和BaseMapper
中的分页方法都无法正常起效。 所以,我们必须配置分页插件。
新建MybatisConfig.java
|
编写分页查询的测试
|
也可以支持排序(根据balance排序,false是降序)
int pageNo = 1, pageSize = 5; |
通用分页查询案例
定义一个统一的分页查询条件的实体,包含分页、排序参数、过滤条件
public class PageQuery {
private Long pageNo;
private Long pageSize;
private String sortBy;
private Boolean isAsc;
}然后让我们需要分页的实体去继承分页实体
callSuper = true
根据子类自身的字段值和从父类继承的字段值 来生成hashcode,当两个子类对象比较时,只有子类对象的本身的字段值和继承父类的字段值都相同,equals方法的返回值是true@EqualsAndHashCode
用于自动生成 equals() 和 hashCode() 方法,并且在比较时,调用父类(super)的 equals() 和 hashCode() 方法
public class UserQuery extends PageQuery {
private String name;
private Integer status;
private Integer minBalance;
private Integer maxBalance;
}新建统一返回结果集
public class PageDTO<T> {
private Long total;
private Long pages;
private List<T> list;
}分页查询代码示例
public PageDTO<UserVO> queryUsersPage(PageQuery query) {
// 1.构建条件
// 1.1.分页条件
Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
// 1.2.排序条件
if (query.getSortBy() != null) {
page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
}else{
// 默认按照更新时间排序
page.addOrder(new OrderItem("update_time", false));
}
// 2.查询
page(page);
// 3.数据非空校验
List<User> records = page.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return new PageDTO<>(page.getTotal(), page.getPages(), Collections.emptyList());
}
// 4.有数据,转换
List<UserVO> list = BeanUtil.copyToList(records, UserVO.class);
// 5.封装返回
return new PageDTO<UserVO>(page.getTotal(), page.getPages(), list);
}
将分页条件封装在工具类中
PageQuery
|
PageDTO
package com.itheima.mp.domain.dto; |
业务层代码可简化为
|
2.Docker
1. 安装问题
1. 解决配置网络时es33显示被已拔出问题
在电脑服务中开启VMware DHCP Service”和“VMware NAT Service”。即可
2. 解决Docker镜像问题
操她奶奶的,你妹的傻鸟Docker,用老师安装的方式一直不成功,最终在评论区找到答案
无法安装docker 建议直接:bash <(curl -sSL https://linuxmirrors.cn/docker.sh) 这条命令,一次性全安装完毕 |
2. 安装MySQL
docker run -d \ |
tee /etc/docker/daemon.json <<-'EOF' |
3. Docker常见命令
命令 | 说明 | 文档地址 |
---|---|---|
docker images | 查看本地镜像 | docker images |
docker rmi | 删除本地镜像 | docker rmi |
docker run | 创建并运行容器(不能重复创建) | docker run |
docker stop | 停止指定容器 | docker stop |
docker start | 启动指定容器 | docker start |
docker restart | 重新启动容器 | docker restart |
docker rm | 删除指定容器 | docs.docker.com |
docker ps | 查看容器 | docker ps |
docker logs | 查看容器运行日志 | docker logs |
docker exec | 进入容器 | docker exec |
docker save | 保存镜像到本地压缩文件 | docker save |
docker load | 加载本地压缩文件到镜像 | docker load |
docker inspect | 查看容器详细信息 | docker inspect |
如何把镜像交给运维人员:1.使用docker save形成本地压缩包,运维人员再使用docker load将本地这个压缩包进行解压;2.将镜像推送到镜像仓库,运维人员再从镜像仓库进行拉取
4. 数据卷
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便。大家思考几个问题:
- 如果要升级MySQL版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
- MySQL、Nginx容器运行后,如果我要修改其中的某些配置该怎么办?
- 我想要让Nginx代理我的静态资源怎么办?
因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦。
4.1 什么是数据卷
数据卷(volume)是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。(ps:其实类似于双向绑定,修改数据卷里面的nginx内容,实际的nginx的内容也会修改)
以Nginx为例,我们知道Nginx中有两个关键的目录:
html
:放置一些静态资源conf
:放置配置文件
如果我们要让Nginx代理我们的静态资源,最好是放到html
目录;如果我们要修改Nginx的配置,最好是找到conf
下的nginx.conf
文件。
但遗憾的是,容器运行的Nginx所有的文件都在容器内部。所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作。如图:
在上图中:
- 我们创建了两个数据卷:
conf
、html
- Nginx容器内部的
conf
目录和html
目录分别与两个数据卷关联。 - 而数据卷conf和html分别指向了宿主机的
/var/lib/docker/volumes/conf/_data
目录和/var/lib/docker/volumes/html/_data
目录
这样以来,容器内的conf
和html
目录就 与宿主机的conf
和html
目录关联起来,我们称为挂载。此时,我们操作宿主机的/var/lib/docker/volumes/html/_data
就是在操作容器内的/usr/share/nginx/html/_data
目录。只要我们将静态资源放入宿主机对应目录,就可以被Nginx代理了。
4.2 数据卷的命令
命令 | 说明 | 文档地址 |
---|---|---|
docker volume create | 创建数据卷 | docker volume create |
docker volume ls | 查看所有数据卷 | docs.docker.com |
docker volume rm | 删除指定数据卷 | docs.docker.com |
docker volume inspect | 查看某个数据卷的详情 | docs.docker.com |
docker volume prune | 清除数据卷 | docker volume prune |
注意:容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建。
如何挂载数据卷?
- 在创建容器时,利用-v数据卷名:容器内目录完成挂载
- 容器创建时,如果发现挂载的数据卷不存在时,会自动创建
4.3 创建Nginx 数据卷
哇神奇
[root@localhost ~]# docker rm -f nginx |
这是 Docker 的默认存储位置,所有命名卷都会存放在 /var/lib/docker/volumes/<卷名>/_data
下
volume对应的宿主机目录,html是虚拟目录 ,/usr/share/nginx/html
容器内的对应目录
4.4 MySQL 容器的数据挂载
[root@localhost ~]# docker inspect mysql |
发现有默认的数据挂载,但是默认匿名挂载名字复杂目录太深了,下面给他修改到挂载到根目录
docker run -d \ |
注意:SQL文件必须要有创建数据库的命令,不然无法创建成功数据库
然后将SQL文件放在init文件夹里面
运行
docker run -d \ |
即可
5. 自定义镜像
镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建镜像的过程其实就是把上述文件打包的过程。
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。而这种记录镜像结构的文件就称为Dockerfile
指令 | 说明 | 示例 |
---|---|---|
FROM | 指定基础镜像 | FROM centos:6 |
ENV | 设置环境变量,可在后面指令使用 | ENV key value |
COPY | 拷贝本地文件到镜像的指定目录 | COPY ./xx.jar /tmp/app.jar |
RUN | 执行Linux的shell命令,一般是安装过程的命令 | RUN yum install gcc |
EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的 | EXPOSE 8080 |
ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 | ENTRYPOINT java -jar xx.jar |
6. 网络
容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。
所以,我们必须借助于docker的网络功能来解决这个问题 常见命令
命令 | 说明 | 文档地址 |
---|---|---|
docker network create | 创建一个网络 | docker network create |
docker network ls | 查看所有网络 | docs.docker.com |
docker network rm | 删除指定网络 | docs.docker.com |
docker network prune | 清除未使用的网络 | docs.docker.com |
docker network connect | 使指定容器连接加入某网络 | docs.docker.com |
docker network disconnect | 使指定容器连接离开某网络 | docker network disconnect |
docker network inspect | 查看网络详细信息 | docker network inspect |
[root@localhost mysql]# docker network ls |
7.使用Docker打包项目(tlias为例)
老东西 终于把焚决交出来了
7.1后端打包
准备MySQL容器,并且创建tlias数据库以及表结构(上面在本地目录挂载MySQL时已经2完成)
准备Java应用(tlias)镜像,部署Docker容器,运行测试
修改tlias项目的配置文件,修改数据库服务地址及logback日志文件存放地址,打jar包。
编写Dockerfile文件。
构建Docker镜像。
部署Docker容器。
具体步骤
打开idea将yml中数据库连接部分改为Docker中的MySQL
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/tlias?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: abc123在idea中的Maven中的Lifecycle中点击
package
,等待build成功。然后我们的target文件夹中会出现我们的Jar包了。jar包名字很长可以适当重命名新建Dockerfile
# 使用 CentOS 7 作为基础镜像
FROM centos:7
# 添加 JDK 到镜像中
COPY jdk21.tar.gz /usr/local/
RUN tar -xzf /usr/local/jdk21.tar.gz -C /usr/local/ && rm /usr/local/jdk21.tar.gz
# 设置环境变量
ENV JAVA_HOME=/usr/local/jdk-21.0.1
ENV PATH=$JAVA_HOME/bin:$PATH
# 配置阿里云OSS
ENV OSS_ACCESS_KEY_ID=[你的]
ENV OSS_ACCESS_KEY_SECRET=[你的]
#统一编码
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
# 创建应用目录
RUN mkdir -p /tlias
WORKDIR /tlias
# 复制应用 JAR 文件到容器
COPY tlias.jar tlias.jar
# 暴露端口
EXPOSE 8080
# 运行命令
ENTRYPOINT ["java","-jar","/tlias/tlias.jar"]在
/usr/local/
下新建tlias-docker-app
文件夹,里面上传进去我们的tlias.jar
,Dockerfile
,jdk21
然后去构建Docker镜像
[root@localhost tlias-docker-app]# docker build -t tlias:1.0 .
[+] Building 266.8s (11/11) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 818B 0.0s
=> [internal] load metadata for docker.io/library/centos:7 146.9s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/6] FROM docker.io/library/centos:7@sha256:be65f488b7764ad3638f23 111.2s
=> => resolve docker.io/library/centos:7@sha256:be65f488b7764ad3638f236b 0.0s
=> => sha256:eeb6ee3f44bd0b5103bb561b4c16bcb82328cfe5809 2.75kB / 2.75kB 0.0s
=> => sha256:2d473b07cdd5f0912cd6f1a703352c82b512407 76.10MB / 76.10MB 103.9s
=> => sha256:be65f488b7764ad3638f236b7b515b3678369a5124c 1.20kB / 1.20kB 0.0s
=> => sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839 529B / 529B 0.0s
=> => extracting sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43 7.1s
=> [internal] load build context 0.0s
=> => transferring context: 173B 0.0s
=> [2/6] COPY jdk21.tar.gz /usr/local/ 2.0s
=> [3/6] RUN tar -xzf /usr/local/jdk21.tar.gz -C /usr/local/ && rm /usr 5.4s
=> [4/6] RUN mkdir -p /tlias 0.4s
=> [5/6] WORKDIR /tlias 0.0s
=> [6/6] COPY tlias.jar tlias.jar 0.1s
=> exporting to image 0.7s
=> => exporting layers 0.6s
=> => writing image sha256:8b8dd47f37a8752599c8d24b4b01dcce96ccb2469d4f4 0.0s
=> => naming to docker.io/library/tlias:1.0 0.0s
[root@localhost tlias-docker-app]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tlias 1.0 8b8dd47f37a8 About a minute ago 783MB
redis latest f2cd22713a18 7 days ago 128MB
nginx latest 9592f5595f2b 2 weeks ago 192MB
mysql latest 4c2531d6bf10 2 months ago 859MB取名字为tlias,版本1.0,在当前文件夹里
部署docker容器
[root@localhost tlias-docker-app]# docker run -d --name tlias-server -p 8080:8080 --network baskly tlias:1.0
ffc30c9fde8cf187b0c035e1fd27b12417ebcf988af34b3779c2cf50040f7a4d
[root@localhost tlias-docker-app]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ffc30c9fde8c tlias:1.0 "java -jar /tlias/tl…" 18 seconds ago Up 17 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp tlias-server使用
docker logs -f tlias-service
查看后端启动日志[root tlias-docker-app]# docker logs -f tlias-server
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.4)
2025-07-13T21:02:32.843Z INFO 1 --- [tlias-web-managemen] [ main] c.itheima.TliasWebManagemenApplication : Starting TliasWebManagemenApplication v0.0.1-SNAPSHOT using Java 21.0.1 with PID 1 (/tlias/tlias.jar started by root in /tlias)
2025-07-13T21:02:32.849Z INFO 1 --- [tlias-web-managemen] [ main] c.itheima.TliasWebManagemenApplication : No active profile set, falling back to 1 default profile: "default"
2025-07-13T21:02:35.395Z INFO 1 --- [tlias-web-managemen] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-07-13T21:02:35.427Z INFO 1 --- [tlias-web-managemen] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-07-13T21:02:35.428Z INFO 1 --- [tlias-web-managemen] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.39]
2025-07-13T21:02:35.493Z INFO 1 --- [tlias-web-managemen] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-07-13T21:02:35.495Z INFO 1 --- [tlias-web-managemen] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2524 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
,------. ,--. ,--. ,--.
| .--. ' ,--,--. ,---. ,---. | '--' | ,---. | | ,---. ,---. ,--.--.
| '--' | ' ,-. | | .-. | | .-. : | .--. | | .-. : | | | .-. | | .-. : | .--'
| | --' \ '-' | ' '-' ' \ --. | | | | \ --. | | | '-' ' \ --. | |
`--' `--`--' .`- / `----' `--' `--' `----' `--' | |-' `----' `--'
`---' `--' is intercepting.
2025-07-13T21:02:37.128Z INFO 1 --- [tlias-web-managemen] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-07-13T21:02:37.149Z INFO 1 --- [tlias-web-managemen] [ main] c.itheima.TliasWebManagemenApplication : Started TliasWebManagemenApplication in 5.209 seconds (process running for 5.838)这样我们的项目就启动成功了使用ApiFox发送请求也能正常输出结果
7.2. 前端项目部署
创建一个新的nginx容器,将资料中提供的前端项目的静态资源部署到nginx中。
在root文件夹下创建一个新文件夹用于映射Nginx,名字叫
tlias-nginx
。然后配置挂载
docker run -d \
--name nginx-tlias \
-v /root/tlias-nginx/html:/usr/share/nginx/html \
-v /root/tlias-nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
--network baskly \
-p 80:80 \
nginx:1.20.2然后访问(未开启需要开启一下服务)
# 安装ntpdate(若未安装)
sudo yum install -y ntpdate
# 同步阿里云时间服务器(北京时间)
sudo ntpdate ntp.aliyun.com解决因为Linux时间和系统系统不符导致的时区不同步问题
8. DockerCompose
老东西 把异火也交出来了
Docker Compose通过一个单独的docker-compose.yml模板文件(YANL 格式)来定义一组相关联的应用容器,帮助我们实现多个相互关联的Docker容器的快速部署。
现在我们使用DockerCompose来重新构建项目
- 准备资源(tlias.sql,服务端的jdk17、jar包、Dockerfile,前端项目打包文件、nginx.conf)
- 准备docker-compose.yml配置文件
- 基于DockerCompose快速构建项目
services: |
根据compose定义的路径在文件夹下新建空文件夹,然后将以下文件存放在文件夹中
docker compose [OPTIONS] [COMMAND] |
其中,OPTIONS和COMMAND都是可选参数,比较常见的有:
类型 | 参数或指令 | 说明 |
---|---|---|
Options | -f | 指定compose文件的路径和名称 |
-p | 指定project名称。project就是当前compose文件中设置的多个service的集合,是逻辑概念 | |
Commands | up | 创建并启动所有service容器 |
down | 停止并移除所有容器、网络 | |
ps | 列出所有启动的容器 | |
logs | 查看指定容器的日志 | |
stop | 停止容器 | |
start | 启动容器 | |
restart | 重启容器 | |
top | 查看运行的进程 | |
exec | 在指定的运行中容器中执行命令 |
创建容器
[root@localhost app]# docker compose up -d
[+] Running 11/11
✔ mysql Pulled 94.0s
✔ 90dac1e734aa Already exists 0.0s
✔ bf40b60a847d Pull complete 32.0s
✔ 9d9cb66e1171 Pull complete 32.1s
✔ 31b29e08d2d1 Pull complete 32.8s
✔ 1f5a1dfb5b55 Pull complete 32.8s
✔ 7becd864c61c Pull complete 32.8s
✔ 00a0a1479659 Pull complete 35.0s
✔ cff841917be4 Pull complete 35.0s
✔ 8e98c1c43da6 Pull complete 76.4s
✔ 61ba5ff08093 Pull complete 76.4s
[+] Building 38.5s (11/11) FINISHED docker:default
=> [tlias internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 818B 0.0s
=> [tlias internal] load metadata for docker.io/library/centos:7 36.3s
=> [tlias internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [tlias 1/6] FROM docker.io/library/centos:7@sha256:be65f488b7764ad363 0.0s
=> [tlias internal] load build context 2.1s
=> => transferring context: 232.73MB 2.1s
=> CACHED [tlias 2/6] COPY jdk21.tar.gz /usr/local/ 0.0s
=> CACHED [tlias 3/6] RUN tar -xzf /usr/local/jdk21.tar.gz -C /usr/local 0.0s
=> CACHED [tlias 4/6] RUN mkdir -p /tlias 0.0s
=> CACHED [tlias 5/6] WORKDIR /tlias 0.0s
=> CACHED [tlias 6/6] COPY tlias.jar tlias.jar 0.0s
=> [tlias] exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:e2a98bd74211094262b8ba068f777e782edeba8b11cdf 0.0s
=> => naming to docker.io/library/app-tlias 0.0s
[+] Running 4/4
✔ Network itheima Created 0.1s
✔ Container mysql Started 1.2s
✔ Container tlias-server Started 1.2s
✔ Container nginx-tlias Started开始停止容器
[root@localhost app]# docker compose stop
[+] Stopping 3/3
✔ Container nginx-tlias Stopped 0.2s
✔ Container tlias-server Stopped 0.2s
✔ Container mysql Stopped 2.9s
[root@localhost app]# docker compose start
[+] Running 3/3
✔ Container mysql Started 0.3s
✔ Container tlias-server Started 0.3s
✔ Container nginx-tlias Started 0.4s
[root@localhost app]#🥲心心念念的Docker终于结束了
二、微服务
1. 导入后端项目
导入后端项目启动项目时报了错误
java.lang.reflect.InaccessibleObjectException |
看弹幕说时因为MyBatisPlus版本与Java高版本冲突导致的反射问题
配置vm
--add-opens java.base/java.lang.invoke=ALL-UNNAMED |
即可解决
解决时区不同步问题
[root@localhost ~]# date |
2. 单体架构与微服务
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:架构简单,部署成本低
缺点:团队协作成本高,系统发布效率低,系统可用性差
微服务架构,是服务化思想指导下的一套最佳实践架构方案。服务化,就是把单体架构中的功能模块拆分为多个独立项目。
3. 微服务拆分原则
什么时候需要拆分微服务?
- 如果是创业型公司,最好先用单体架构快速迭代开发,验证市场运作模型,快速试错。当业务跑通以后,随着业务规模扩大、人员规模增加,再考虑拆分微服务。
- 如果是大型企业,有充足的资源,可以在项目开始之初就搭建微服务架构
如何拆分?
- 首先要做到高内聚、低耦合
- 从拆分方式来说,有横向拆分和纵向拆分两种。纵向就是按照业务功能模块,横向则是拆分通用性业务,提高复用性
服务拆分之后,不可避免的会出现跨微服务的业务,此时微服务之间就需要进行远程调用。微服务之间的远程调用被称为RPC,即远程过程调用。RPC的实现方式有很多,比如:
- 基于Http协议
- 基于Dubbo协议
我们使用的是Http方式,这种方式不关心服务提供者的具体技术实现,只要对外暴露Http接口即可,更符合微服务的需要。
4. 远程调用(RPC)
在拆分的时候,我们发现一个问题:就是购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service
服务,导致我们无法查询。
最终结果就是查询到的购物车数据不完整,因此要想解决这个问题,我们就必须改造其中的代码,把原本本地方法调用,改造成跨微服务的远程调用(RPC,即Remote Produce Call)。
因此,现在查询购物车列表的流程变成了这样:
那么问题来了:我们该如何跨服务调用,准确的说,如何在cart-service
中获取item-service
服务中的提供的商品数据呢?
答案是肯定的,我们前端向服务端查询数据,其实就是从浏览器远程查询服务端数据。比如我们刚才通过Swagger测试商品查询接口,就是向http://localhost:8081/items
这个接口发起的请求:
而这种查询就是通过http请求的方式来完成的,不仅仅可以实现远程查询,还可以实现新增、删除等各种远程请求。 那么:我们该如何用Java代码发送Http的请求呢?
4.1 RestTemplate
Java发送http请求可以使用Spring提供的RestTemplate,使用的基本步骤如下:
- 注册RestTemplate到Spring容器
- 调用RestTemplate的API发送请求,常见方法有:
- getForObject:发送Get请求并返回指定类型对象
- PostForObject:发送Post请求并返回指定类型对象
- put:发送PUT请求
- delete:发送Delete请求
- exchange:发送任意类型请求,返回ResponseEntity
感觉这种如果有大量用户同时请求不会变的很卡吗??????
构造器注入
Spring推荐我们使用Lombok构造器注入而不是使用@Autowired
因此我们可以在类上添加@RequiredArgsConstructor
注解,使用final
注入类
eg:
4.2 注册中心
在微服务远程调用的过程中,包括两个角色:
- 服务提供者:提供接口供其它微服务访问,比如
item-service
- 服务消费者:调用其它微服务提供的接口,比如
cart-service
- 服务者可以是消费者,消费者也可以是服务者
在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:
流程如下:
- 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
- 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
- 调用者自己对实例列表负载均衡,挑选一个实例
- 调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
- 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
- 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
- 注册中心一旦认为某个实例宕机并将其剔除实例列表,那么其他服务的调用者就无法再调用这个实例。
- 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
- 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
4.3 Nacos注册中心
docker run -d \ |
访问路径
192.168.163.129:8848/nacos |
4.4 OpenFeign
哇神奇
- 将来Feign可以根据服务名称去注册中心中去拉取实例列表
- Feign会使用负载均衡自动获取一个实例(我们在pom中引入了负载均衡的依赖了)
- 然后定义GET请求,路径为
/items
,