基础知识
单体架构指所有的功能实现都放在同一个项目当中


微服务:将单体项目里的多个功能模块拆分成单一的项目



项目部署
虚拟机部署mysql
1、删除root/目录下的mysql
rm -rf mysql/
2、直接把mysql整个文件夹拖到root/目录下(用MobaXterm可以直接拖)


3、创建mysql容器
创建自定义网络
docker network create hm-net
创建并允许mysql容器
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
-v /root/mysql/data:/var/lib/mysql \
-v /root/mysql/conf:/etc/mysql/conf.d \
-v /root/mysql/init:/docker-entrypoint-initdb.d \
--network hm-net\
mysql

Windows启动hmall项目
在idea打开hmall项目,然后配置启动类

更改配置文件里的mysql的虚拟机ip和密码

访问成功

Windows启动Nginx
在nginx目录下打开终端,输入
start nginx.exe
在浏览器输入localhost:18080

熟悉项目功能

服务拆分原则

当某个单体项目要去拆分成微服务项目的时候,纵向拆分就是按照业务模块比如商品、订单这些,但是这些被拆分的模块可能有共同的功能,比如登录异常需要登录异常短信提醒,下单需要短信提醒,这些共同的功能要抽取出来,提高复用性。然后目标是这些拆分出来的业务模块,以后要升级或者添加什么功能的时候,修改的代码绝大部分是在模块内部进行的,无需在其他模块修改代码,那就是我们要的高内聚,低耦合

服务拆分
独立project:创建一个空文件夹,在里面把单体项目拆分出来的每个微服务都在idea里new project出来,成为多个project
maven聚合:在一个project下new 多个module,和单体项目差不多,但是多个微服务是分开打包部署运行的

拆分商品服务
1、先右键project去new一个新module

2、创建启动类和配置文件和pom依赖(直接从hm-service拿)
如果module下没有resources包,则先手动创建一个普通的resources包,点击project structure 然后把resources包标记为资源目录

最终结果为


3、因为划分了多个微服务,所以每个微服务都要有自己独立的数据库,将hm-item.sql导入数据库
在datagrip创建好数据库hm-item,然后右击数据库,选择SQL脚本然后点击运行SQL脚本
4、修改resources下的配置文件不合理的地方
5、拷贝单体项目里商品模块的代码到商品微服务当中
拆分购物车服务
拆分流程和商品服务一致,但是有个问题,因为要实时查询购物车中商品的最新信息,所以还需要额外查询商品信息,所以购物车不仅需要去查询购物车的数据,还需要用到商品的数据。
但是购物车和商品已经被我们分成了两个模块,数据库的数据也是隔离的,所以不能直接获取到商品的数据。
这两个模块虽然物理上隔离了,但是网络是相通的.模仿前端向后端发起请求,在购物车服务模块向商品服务模块发起http请求

远程调用

远程服务调用时存在问题(服务治理问题)
多了抗住高并发请求,通常会部署多个容器来接收响应请求,但是第一个问题是,服务的调用者,根本就不知道服务提供者的地址(哪个端口)。第二个问题是就算知道了服务提供者的地址,因为太多个容器了,到底应该访问哪个。第三个问题是如果我挑中的哪个服务挂掉了怎么办,还有如果又新开了新的服务,我又怎么去知道。这些就是服务治理问题。

注册中心原理
提供服务的多个实例部署会把服务信息放到注册中心当中,当有其他服务消费者需要这个服务,就去注册中心找,注册中心会返回给服务消费者多个实例部署的相关信息,通过负载均衡(随机、轮询或其他算法挑选出其中一个服务实例)挑选出其中一个服务实例,然后开始提供服务。为了防止出现服务实例宕机的情况,会在注册中心和服务提供者连接一个心跳续约,也就是说服务提供者会定期往注册中心发出信号,代表自己还没有宕机。如果服务提供者长时间没有心跳,那么就会从注册中心中的服务列表删除,然后告诉服务消费者,这个服务实例已经宕机了。又如果又部署了新的服务实例,也会把服务信息放在注册中心当中,注册中心会把这些消息都推送给服务消费者。


Nacos注册中心

首先运行nacos.sql文件,然后找到custom.env(配置了nacos和mysql的一些连接信息) ,更改里面的配置信息,然后把整个nacos文件夹和nacos.tar(镜像文件)放到虚拟机中的root目录下。
加载镜像文件
docker load -i nacos.tar
部署nacos容器
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
查看是否启动成功
docker logs -f nacos

要通过8848端口去访问nacos

192.168.126.148:8848/nacos
登录账号密码都是nacos

服务注册

在item-service添加依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在application.yaml配置nacos地址

再配置一个ItemApplication启动类

启动两个ItemApplication启动类,打开刚才的nacos

服务发现
服务提供者把服务信息注册在了注册中心当中,接下来消费者就要通过连接nacos注册中心获取到这些服务实例,然后发起请求

在cart-service添加依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在application.yaml配置nacos地址

OpenFeign
nacos构造http请求,发起远程调用item-service代码太繁琐

OpenFeign简化代码


连接池
OpenFeign不带连接池,每次请求都要新建连接,影响性能,所以修改一下配置,让它能用连接池


最佳实践
因为以后会有多个模块,如果有多个模块都需要用到商品模块,那么就需要把OpenFeign的使用代码都重新写一遍,这样子多了好多重复的代码。
第一种方法,不要在消费者模块里面去书写OpenFeign代码,而是让服务提供者商品模块自己管理OpenFeign的代码,耦合度比较低,适合把各个功能拆分成多个project的情况

第二种方案独立开一个单独的服务模块,让所有消费者都向这个模块请求,耦合度稍微比较高,适合一个project下拆分多个module的时候


日志
为了能更好的进行调试程序bug,我们需要OpenFeign的日志,但是OpenFeign的日志默认是不开启的,没有 Feign 调用的详细日志(如请求参数、响应结果)


总结

网关
在之前单体项目的时候,只需要让前端访问8080端口就可以获得后端的数据,但是把单体项目划分为微服务项目以后,就多出了很多个不一样的端口,各自处理的业务数据也不一样,以后的端口还有可能发生变化,这个时候前端就不知道要获取数据应该访问哪个端口业务了。而且每个服务都可能需要获取用户信息,这个时候不可能说让每个业务都去做登录校验的功能,还会有密钥泄漏的风险
要解决这个问题,我们就可以用到网关,前端发送请求到网关,网关知道前端请求的路由,也就是请求哪个服务模块,就可以在前端通过身份校验之后,进行路由转发,也就是在注册中心拉取服务实例之后,通过负载均衡把前端的请求发送到对应的微服务模块。因为网关会对接我们之前弄的注册中心,可以得知各个微服务模块的信息以及服务器数量,并进行负载均衡,如果以后微服务的端口信息什么的发生变化,网关也能及时知道并更新。


网关路由

路由属性



网关登录校验
因为用的是jwt登录校验,在使用每个微服务模块的服务之前都需要对用户进行jwt校验,但是我们又不可能说在每个模块都写这个登录校验的代码,而且如果把jwt密钥发送给各个模块容易有泄漏的风险,所以我们选择在网关转发给微服务之前做jwt的校验。虽然我们在网关做了用户登录校验功能,但是网关把请求转发到微服务模块之后,微服务模块通常都会使用到用户信息,这个时候网关需要把用户信息保存到请求头一起转发给微服务模块。微服务的模块与模块之间的调用也需要转发用户信息过去。

通过网关请求处理的流程可以知道我们需要把jwt登录校验放在网关的pre过滤器部分,并且在NettyRoutingFilter之前执行

自定义过滤器



自定义过滤器

把用户信息封装在http请求的请求头里,然后在common编写拦截器,然后每个微服务模块在拦截器获取到用户信息然后保存到threadlocal当中

各个微服务模块相互调用,也需要传递用户信息过去,把用户信息封装到请求头当中这个操作利用OpenFeign的拦截器来完成,在hm-api模块统一实现

总结

配置管理
在微服务模块当中会出现以下问题:每个微服务模块的重复配置可能会很多,比如登录的过期时长,登录校验错误的次数等等,而且如果某个配置项或者网关路由配置一旦发生变动,就需要重启服务或网关,这样会带来很多的不变,我们就需要把这个经常需要变动的重复的配置抽取出来,放置到nacos中的配置管理服务当中,只要里面的配置一旦发生改变就会推送配置变更给网关和微服务模块

添加配置


拉取共享配置
如果没有引导配置文件bootstrap.yaml,然后要去拉取nacos配置,即使application.yaml里面有关于nacos地址的信息,但是因为加载的先后顺序的原因,也无法获得nacos的地址信息,所以需要一个引导配置文件,先声明nacos的地址信息,这样才能拉取nacos配置,进行下一步



配置热更新


服务保护和分布式事务
服务保护指的是我们需要为某个微服务模块可能发生的一些故障采取某些措施兜底,分布式事务指的是我们之前的单体项目操作数据库的时候满足事务的ACID特性,但是拆分成多个微服务模块,多个模块同时操作数据库,就会破坏事务的ACID特性

服务保护
雪崩问题
假设通过购物车服务去调用商品服务的流程卡住了,完成每个服务需要几秒的时间,在一个高并发微服务的情况下,会有很多请求访问过来,然后都卡在了商品服务这块,随着请求越来越多,逐渐耗尽了购物车服务的tomcat资源,这时候如果有请求过来要访问购物车服务到服务B,因为tomcat资源被耗尽,所以即使和商品服务无关,其他的请求访问也发生了故障,等于整个购物车服务和商品服务一样瘫痪了。而在整个微服务当中,有可能存在着许多的微服务模块需要调用到这个瘫痪的微服务,这就会导致许多的微服务模块大面积瘫痪。



解决方案
让服务的提供者尽量不出现故障,还要让服务的调用者在服务提供者出现故障的时候尽可能地隔离故障,不影响到自己。
保护服务提供者:采用请求限流的方式,不管访问微服务的请求流量多大多迅猛,限流器都会将这些请求以固定的流速传递给服务提供者,不让服务的提供者因为压力过大奔溃

保护服务消费者:将tomcat资源分成好几份,规定每个业务的限制线程数量,当请求到达微服务模块的时候占用一条线程,当业务限制的线程资源耗尽以后,tomcat也不会继续分配资源给这个业务,相当于知道这个服务以及宕机了,直接拒绝请求

但是不能在知道服务宕机了,还要每次请求过来都尝试获取资源,这个耗费的时间太长了,而是应该直接拒绝请求访问,这个时候就要用到服务熔断,当线程资源耗尽,触发拒绝策略的时候,服务熔断就将这次请求记为一次失败访问,当发现5个请求有四个都挂,或者服务响应的时间异常慢,就直接开启服务熔断,拒绝请求访问这个服务,然后让请求走备用方案fallback



Sentinel

在sentinel的jar包目录下打开cmd控制台,输入代码
java -D"server.port=8090" -D"csp.sentinel.dashboard.server=localhost:8090" -D"project.name=sentinel-dashboard" -jar sentinel-dashboard.jar
然后在浏览器输入localhost:8090即可启动,账号和密码都是sentinel
接着在微服务模块引入依赖
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
然后配置文件配置sentinel控制台地址
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
启动微服务,任意访问后sentinel控制台会出现微服务模块

簇点链路
其实就是记录下来每次http请求的请求路径

restful风格的api请求路径很多都相同,差别是用get、put、post请求方式而已,所以开启请求方式+请求路径作为簇点资源名称


请求限流



Fallback
把服务提供者的feignClient作为簇点资源,配置线程隔离后,当线程数打满了,这个Client就会拒绝访问,可以在这个client书写fallback逻辑,这样就可以返回给用户fallback显示的数据

写UserClient的FeignClient的fallback逻辑实现,如果在隔离线程数量的正常范围内调用,则调用正常的UserClient逻辑,如果出现异常,则返回自定义的fallback逻辑


服务熔断

在打开服务熔断后,在熔断时间结束,会进入half-Open来判断服务是否恢复(请求处理是否恢复正常),是否可以关闭服务熔断,如果服务还是没恢复,则继续打开服务熔断,如果服务正常,则关闭服务熔断


分布式事务
之前是单体项目,下订单的时候创建订单、清理购物车、扣减商品库存这三个操作都是遵循事务的,因为操作的是同一个数据库里的三张表,如果出现异常则会回滚事务,满足事务的ACID特性。但是因为订单、购物车、库存被拆分成了微服务,拥有独立的数据库,如果创建订单、清理购物车两个操作成功了,但是扣减库存失败了,这个时候扣减库存失败的异常会返回给订单服务,然后扣减库存操作会回滚,但是购物车服务不知道库存出现异常了,因为调用库存服务的是订单服务,所以清理购物车的操作无法回滚,仍旧执行,而这时候订单服务收到了异常,本地的事务还没有提交,所以订单服务的创建订单操作仍旧可以回滚。


Seata解决分布式事务
事务协调者监控所有服务操作,如果有一个操作失败了,通知所有事务回滚


部署TC服务
1、因为TC要管理全局事务和各个分支事务,要记录相关信息,持久化保存,要创建一个数据库要来保存这些消息

2、把seata所需要的文件和镜像包放到虚拟机上部署

docker load -i seata.tar
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.150.101 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2
查看日志是否启动
docker logs -f seata
浏览器访问seata,用户名和密码在seata的application.yml配置文件中
http://192.168.126.148:7099/#/TransactionInfo

微服务集成Seata

抽取为nacos的共享的配置

XA模式



AT模式


还要加@GlobalTransactional注解

解决问题
导入项目后,左侧项目栏跑到上方
删除.idea文件,叉掉idea,然后重新打开
打开idea后,一直indexing jdk,然后界面不动了,一个按键都点不了
1、file------invalidate caches(因为点不了按键,没有用)
2、删除.idea文件,重启(因为点不了按键,没有用)
3、打开任务管理器,强制关闭idea------找到目录C:\Users\你的用户\AppData\Local\JetBrains\IntelliJIdea2021.1------删除caches和index------重启idea------趁着idea没反应过来,还没卡住的时候,赶紧点击project structure------选择你的jdk应用,等它加载就好了
创建resources文件夹后没有自动扫描目录下的配置文件
把检查resources文件夹是否建在main下面
可以在project structure下设置

项目中的各个模块pom文件引入依赖无法识别
记得把新建的模块添加到父pom文件当中

在每个模块检查自己的模块名是否正确

要是在模块的pom里引入了不该引入的模块,就会这样,如果两个模块原本就需要有依赖关系就是正常的

IDEA项目中java文件夹出现source root标记的问题
这个代表你出现source root的java文件路径已经被其他模块占用了,打开project structure,然后点击module中那个占用你java文件的模块,右边可以查看他占有的source,点击叉号,叉掉不属于这个模块的文件就好,还要检查一下你的pom文件是不是引入了这个你不想要的模块
