黑马商城-项目笔记&面试包装

q:什么时候需要拆分微服务?

  • 如果是创业型公司,最好先用单体架构快速迭代开发,验证市场运作模型,快速试错。当业务跑通以后,随着业务规模扩大、人员规模增加,再考虑拆分微服务。
  • 如果是大型企业,有充足的资源,可以在项目开始之初就搭建微服务架构。

q:如何拆分?

  • 首先要做到高内聚、低耦合
  • 从拆分方式来说,有横向拆分和纵向拆分两种。纵向就是按照业务功能模块,横向则是拆分通用性业务,提高复用性

服务注册与发现

RestTemplate

当不同服务之间需要有相互调用的时候,因为服务已经被拆分,不同的服务使用不同的实例启动,因此需要远程的调用。Spring给我们提供了一个RestTemplate的API,方便实现Http请求的发送。

使用方法也很简单,定义一个配置类,然后将RestTemplate注册为Bean,之后就能在业务方法中注入并调用了。

但是上述的方法进行远程调用时会存在很多问题:

1.同一个服务多台部署,调用该服务的话就应该知道这些服务实例的地址,维护麻烦,选择麻烦

2.服务运行过程中,某一个item-service实例宕机了,但是调用者对其是没有感知的,依旧会选择调用。

3.如果并发过高,临时部署了多台被调用的实例,调用者也无法动态的更新实例列表

为了解决以上问题,就必须引入注册中心的概念。

注册中心 & Nacos

在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:

这里我们使用SpringCloudAlibaba的Nacos。Nacos同时还具备配置管理功能。

Nacos的配置需要一个数据库来存储相关的一些Nacos数据,里面有很多张表,因此要提前准备一下。我是在虚拟机的docker中部署的。

服务注册

部署完成之后就是把项目中的服务注册到Nacos中,步骤如下:

1.在服务的pom.xml文件中添加Nacos服务注册与发现的依赖

2.在application.yml文件中添加Naccox的相关配置(地址信息等)

3.重启服务实例

完成之后,打开Nacos,就能在服务列表里发现刚刚注册的服务。

服务发现

服务的消费者要去订阅Nacos服务,这个过程就是服务发现,有以下几个步骤:

1.引入依赖。还是跟服务注册时引入的依赖一样,这个依赖同时包含了服务注册与发现的功能

2.application.yml中配置Nacos的地址。

3.服务发现并调用。服务发现需要一个DiscoveryClient的工具,SpringCloud已经为我们自动装配,可以直接注入使用。

使用示例如下:

OpenFeign

我们利用Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了。

我们需要想办法改变远程调用模式,让远程调用就像本地方法调用一样简单,这里就需要使用OpenFeign

在cart-service服务的pom.xml中引入OpenFeign依赖和loadBalancer依赖

同时还要再cart服务的启动类上添加注解@EnableFeignClients,启用OpenFeign功能

cart-service中,定义一个新的接口,编写Feign客户端:

这里的接口跟Controller有点类似,定义请求服务名称、请求方式、请求路径、参数、返回值。这样OpenFeign就可以利用动态代理帮我们实现这个方法。

使用时候也很简单,就是将这个客户端注入到业务代码中,然后直接调用这个方法。

连接池

Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:

  • HttpURLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http:

1.引入依赖

2.开启连接池,在yml文件中:

这样连接池就生效了。

网关-Gateway

项目进行到这一步时,会存在一些问题:

  • 请求不同数据时要访问不同的入口,需要维护多个入口地址,麻烦
  • 前端无法调用nacos,无法实时更新服务列表

还有用户身份信息的验证:

  • 每个微服务都需要编写登录校验、用户信息获取的功能吗?
  • 当微服务之间调用时,该如何传递用户信息?

这就要用到网关。我们的网关可以设计用来完成以下两个功能

  • 网关可以做安全控制,也就是登录身份校验,校验通过才放行
  • 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去

我们使用SpringCloudGateway来实现网关

网关路由

网关本身也是一个微服务,我们需要创建这个微服务并且注册到Nacos当中,步骤如下:

1.创建网关微服务模块

2.引入依赖:

3.创建网关的启动类

4.配置路由

hm-gateway模块的resources目录新建一个application.yaml文件,内容如下

之后以8080端口(网关的端口)拼接微服务接口,就能正常访问了!

网关登录校验

单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。

我们的登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到秘钥。如果每个微服务都去做登录校验,这就存在着两大问题:

  • 每个微服务都需要知道JWT的秘钥,不安全
  • 每个微服务重复编写登录校验代码、权限校验代码,麻烦

因此我们考虑再网关中作登录校验这件事情

网关过滤器

登录校验必须在请求转发到微服务之前做,否则就失去了意义。而网关的请求转发是Gateway内部代码实现的,要想在请求转发之前做登录校验,就必须了解Gateway内部工作的基本原理。

因此我们要自定义一个过滤器,在其中实现登录校验功能,并且把过滤器执行顺序定义到NettyRoutingFilter之前,就能满足我们的需求。

网关过滤器链中的过滤器有两种:

  • GatewayFilter:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.
  • GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。

自定义GatewayFilter不是直接实现GatewayFilter,而是实现AbstractGatewayFilterFactory。最简单的方式是这样的:

自定义 GlobalFilter简单的多,直接实现GlobalFilter。

我们使用自定义GlobalFilter完成登录校验。首先需要用到JWT相关的工具,引入之后,我们开始定义过滤器:

微服务获取用户

校验成功后,我们还需要将用户身份信息传递到下游微服务。由于网关发送请求到微服务依旧是Http请求,我们可以将用户信息以请求头的方式传递到下游微服务。具体实现思路如下:

因此,我们需要改造网关路由器,将用户信息保存到请求头并转发:

随后在微服务中编写拦截器获取用户,因为这是一个通用的模块,所以写在hm-common中,并写好自动装配。这样微服务只需要引入hm-common就可以直接具备拦截器功能,无需重复编写。

package com.hmall.common.interceptor;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       // 1.获取请求头中的用户信息
       String userInfo = request.getHeader("user-info");
       // 2.判断是否为空
       if (StrUtil.isNotBlank(userInfo)) {
           // 不为空,保存到ThreadLocal
               UserContext.setUser(Long.valueOf(userInfo));
      }
       // 3.放行
       return true;
  }

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

接着在hm-common中编写SpringMVC的配置类,配置登录拦截器

这里多提一点,这个配置类默认不会生效,因为它所在包与微服务的扫描包不一致。基于SpringBoot的自动装配原理,我们要将其添加到resource目录下的META-INF/spriong.factories文件中

OpenFeign传递用户

由于微服务之间的调用是基于OpenFeign来实现的,我们可以借助OpenFeign提供的一个拦截器接口feign.RequestInterceptor,通过RequestTemplate来添加请求头

在DefaultFeignConfig中编写这个拦截器

这样一来微服务之间的调用也会传递用户信息了。

配置管理

进行到现在,我们依然有几个问题需要解决:

  • 网关路由在配置文件中写死了,如果变更必须重启微服务
  • 某些业务配置在配置文件中写死了,每次修改都要重启服务
  • 每个微服务都有很多重复的配置,维护成本高

我们可以使用配置管理器服务来解决,Nacos提供了这个功能。

配置共享

我们可以把微服务的共享配置抽取到Nacos中统一管理,这样就不需要每个微服务都重复配置了。分为两步:

1.在Nacos中添加共享配置

2.微服务拉取共享配置

读取Nacos配置是在SpringCloud初始化时,之后才会进行SpringBoot初始化,读取application.yml。但是我们Nacos地址是放在后者的,怎么才能去加载Nacos中的位置文件呢?解决办法如下:

因此,微服务整合Nacos配置管理的步骤如下:

这样就完成了

配置热更新

很多配置都要临时调整,但调整之后都要重新打包、重启服务才能生效,很麻烦。Nacos提供了配置热更新的能力。

使用的时候分为两步:

1.在Nacos中添加配置

2.在微服务读取配置

无需重启,配置生效!

服务保护

雪崩问题:假设说商品服务并发较高,占用过多Tomcat连接,导致商品服务所有接口响应时间增加;此时购物车服务调用商品服务,从而导致购物车服务响应时间也边长,甚至阻塞无法访问。依次类推,整个微服务集群中与商品服务于购物车服务相关的服务都会出现这个问题,最终导致整个集群不可用。

服务保护方案:

  • 请求限流:通过一个限流器,让数量高低起伏的请求经过限流器变得平稳。
  • 线程隔离:线程隔离的思想来自轮船的舱壁模式,为了避免某个接口故障或压力过大导致雪崩问题,可以限定每个接口可以使用的资源范围,比如给查询购物车业务限定可用线程数上限为20,这样即便商品服务出现故障,也不会导致服务器的线程资源被耗尽,不会影响其它接口,避免雪崩问题
  • 服务熔断虽然线程隔离可以避免雪崩问题,但是商品服务出现故障仍然会导致购物车服务出现故障。因此要编写降级逻辑(调用失败后的处理逻辑)、异常统计和熔断策略。

Sentinel

Sentinel 的使用可以分为两个部分:

  • 核心库(Jar包):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。
  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等

控制台的部署:直接把jar包下载下来并制定端口启动

微服务整合举例:在cart-service模块中引入sentinel依赖,并修改配置文件,添加对应的配置信息

默认情况下,sentinel会监控SpringMVC的每一个接口,将其作为簇点资源。可以对簇点资源进行限流、隔离、熔断等保护措施。

同时,默认情况下Sentinel会把路径作为簇点资源的单位。但是这样的话无法分辨同一个路径下请求方式不同的接口。我们需要对application.yml配置文件做如下修改

之后就可以对不同的簇点资源做保护措施等操作了。

请求限流这个保护措施对应的是那个流控的按钮,可以设置该簇点的每秒最大请求数(QPS)

线程隔离中,我们有时候需要对微服务调用的某个下游服务做线程隔离,但是调用的时候是使用OpenFeign,因此就需要OpenFeign整合Sentinel

这样一来就能看到查询商品的FeignClient变成了一个簇点资源

线程隔离对应的也是流控这个按钮,但是这里勾选的是并发线程数

服务熔断

服务熔断是让对于并不太健康的接口直接停止调用,走我们的降级逻辑。

对FeignClient编写失败后的降级逻辑有两种方法:

  • 方式一:FallbackClass,无法对远程调用的异常做处理
  • 方式二:FallbackFactory,可以对远程调用的异常做处理,我们一般选择这种方式。

这里用方式二演示:

这样,被限流了的请求直接走降级逻辑,响应时间很低。但是未被限流的请求还是去调用了这个响应很慢的商品接口,导致响应时长很高,这个时候我们要对响应时长很高的接口做服务熔断,也就是直接走降级逻辑。

RabbitMQ

我们基于Docker来安装RabbitMQ

docker run \
-e RABBITMQ_DEFAULT_USER=itheima \
-e RABBITMQ_DEFAULT_PASS=123321 \
-v mq-plugins:/plugins \
--name mq \
--hostname mq \
-p 15672:15672 \
-p 5672:5672 \
--network hm-net\
-d \
rabbitmq:3.8-management

可以看到有两个端口 其中15672端口是控制台的端口,5672是消息发送处理的接口。RabbitMQ架构图如下:

SpringAMQP

Spring官方基于RabbitMQ提供的一天对于RabbitMQ首发消息的模版工具,并且SpringBoot对其实现了自动装配,使用起来非常方便,只需要引入AMQP依赖,包含了RabbitMQ。

SpringAMQP提供了三个功能:

  • 自动声明队列、交换机及其绑定关系
  • 基于注解的监听器模式,异步接收消息
  • 封装了RabbitTemplate工具,用于发送消息

SpringAMQP的消息发送

SpringAMQP的消息接收

WorkQueues模型

我们通常会让多个消费者绑定同一个队列,从而提升消息处理的速度。这个就是WorkQueues模型。

但是默认情况下消息会平均地分配给每一个监听这个队列的消费者。解决这个问题的方法就是在consumer所在的yml文件中添加如下配置:

交换机的类型

MQ消息的可靠性

MQ消息的可靠性可以分为三个方面的可靠性:

1.发送者的可靠性

2.MQ的可靠性

3.消费者的可靠性

延迟消息

关于死信消费者对于恢复库存的并发场景下数据不一致的问题:

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇