问题1.服务拆分后如何进行服务之间的调用
我们该如何跨服务调用,准确的说,如何在cart-service
中获取item-service
服务中的提供的商品数据呢?
解决办法:Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请求的发送。
问题2.假如商品微服务被调用较多,为了应对更高的并发,我们进行了多实例部署,每个item-service
的实例其IP或端口不同
解决办法:Nacos实现注册中心
流程如下:
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
调用者自己对实例列表负载均衡,挑选一个实例
调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
服务注册步骤: 服务发现步骤:(要多引入一个负载均衡依赖)
问题3:远程调用的代码太复杂与本地方法调用差异太大,编程时的体验也不统一,想让远程调用像本地方法调用一样简单。
解决办法:OpenFeign组件
其实远程调用的关键点就在于四个:
请求方式
请求路径
请求参数
返回值类型
所以,OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写,非常方便。feign替我们完成了服务拉取、负载均衡、发送http请求的所有工作,是不是看起来优雅多了。
问题4:由于每个微服务都有不同的地址或端口,入口不同。所以前端请求不同数据时要访问不同的入口,需要维护多个入口地址,麻烦。前端无法调用nacos,无法实时更新服务列表(某一个服务挂了前端不知道)
问题5:
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,这就存在一些问题:
每个微服务都需要编写登录校验、用户信息获取的功能吗?
当微服务之间调用时,该如何传递用户信息?
解决办法:网关(网关解决两个问题:你是谁?(鉴权)你找谁?(路由))。
- 网关路由,解决前端请求入口的问题。
- 网关鉴权,解决统一登录校验和用户信息获取的问题。
网关也相当于一个微服务,其会注册到nacos,并拉取所有的服务信息,所以要获取某个微服务对网关来说不是难事。
路由:
路由规则的定义语法如下:
id
:路由的唯一标示predicates
:路由断言,其实就是匹配条件filters
:路由过滤条件,后面讲uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
spring:
cloud:
gateway:
routes:
- id: item
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
鉴权:
既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做,这样之前说的问题就解决了
鉴权存在的问题:
网关路由是配置的,请求转发是Gateway内部代码,我们如何在转发之前做登录校验?
如图中所示,网关内部有一个过滤器链,最终请求转发是有一个名为NettyRoutingFilter
的过滤器来执行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter
之前,这就符合我们的需求了!
网关过滤器链中的过滤器有两种:
GatewayFilter
:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route
.
GlobalFilter
:全局过滤器,作用范围是所有路由,不可配置。
我们可以自定义实现这两个拦截器,来实现对指定路径的参数和权限校验(例如jwt)
网关校验JWT之后,如何将用户信息传递给微服务?
由于网关发送请求到微服务依然采用的是Http
请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。
改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务
编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行
微服务之间也会相互调用,这种调用不经过网关,又该如何传递用户信息?
下单的过程中,需要调用商品服务扣减库存,调用购物车服务清理用户购物车。而清理购物车时必须知道当前登录的用户身份。但是,订单服务调用购物车时并没有传递用户信息,购物车服务无法知道当前用户是谁!
由于微服务获取用户信息是通过拦截器在请求头中读取,因此要想实现微服务之间的用户信息传递,就必须在微服务发起调用时把用户信息存入请求头。
微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。我们如何才能让每一个由OpenFeign发起的请求自动携带登录用户信息呢?
这里要借助Feign中提供的一个拦截器接口:feign.RequestInterceptor,
然后实现apply方法,利用RequestTemplate
类来添加请求头,将用户信息保存到请求头中。这样以来,每次OpenFeign发起请求的时候都会调用该方法,传递用户信息。
问题6:
网关路由在配置文件中写死了,如果变更必须重启微服务
某些业务配置在配置文件中写死了,每次修改都要重启服务
每个微服务都有很多重复的配置,维护成本高
解决办法:统一的配置管理器服务解决。而Nacos不仅仅具备注册中心功能,也具备配置管理的功能。
微服务共享的配置可以统一交给Nacos保存和管理,在Nacos控制台修改配置后,Nacos会将配置变更推送给相关的微服务,并且无需重启即可生效,实现配置热更新。
网关的路由同样是配置,因此同样可以基于这个功能实现动态路由功能,无需重启网关即可修改路由配置。
需要注意的是,读取Nacos配置是SpringCloud上下文(ApplicationContext
)初始化时处理的,发生在项目的引导阶段。然后才会初始化SpringBoot上下文,去读取application.yaml
。也就是说引导阶段,application.yaml
文件尚未读取,根本不知道nacos 地址,该如何去加载nacos中的配置文件呢?
SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml
(或者bootstrap.properties
)的文件,如果我们将nacos地址配置到bootstrap.yaml
中,那么在项目引导阶段就可以读取nacos中的配置了。
配置热更新:
这就要用到Nacos的配置热更新能力了,分为两步:
在Nacos中添加配置
在微服务读取配置:@ConfigurationProperties(prefix = "hm.cart")
动态路由:(路由信息跟其他的配置文件不一样,不能通过上面的热部署更新来更新路由信息)
网关的路由配置全部是在项目启动时由org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator
在项目启动的时候加载,并且一经加载就会缓存到内存中的路由表内(一个Map),不会改变。也不会监听路由变更,所以,我们无法利用上节课学习的配置热更新来实现路由更新。
原因是:
spring.cloud.gateway.routes
是在项目启动时被读取并加载进内存,Spring 并没有提供基于 ConfigChangeEvent 的监听机制来自动刷新它。所以即使你在 Nacos 改了路由配置,Spring 的
RouteDefinitionLocator
不会重新加载,网关也不会自动生效。
非动态路由:当网关服务启动时,从配置文件中读取,并缓存入路由表,但是无法实现热更新,每次配置文件更改都需要重启路由服务。解决:将路由配置交给nacos管理,当配置文件更改时拉取最新的配置并更新到路由表(一个缓存区)。要解决问题:1.如何拉取,2.如何更新。
这里核心的步骤有2步:
创建ConfigService,目的是连接到Nacos
添加配置监听器,编写配置变更的通知处理逻辑
更新路由要用到
org.springframework.cloud.gateway.route.RouteDefinitionWriter
这个接口
问题7:业务健壮性和级联问题
健壮性:
例如在之前的查询购物车列表业务中,购物车服务需要查询最新的商品信息,与购物车数据做对比,提醒用户。大家设想一下,如果商品服务查询时发生故障,查询购物车列表在调用商品服 务时,是不是也会异常?从而导致购物车查询失败。但从业务角度来说,为了提升用户体验,即便是商品查询失败,购物车列表也应该正确展示出来,哪怕是不包含最新的商品信息。
级联(雪崩):
商品服务的请求量很大,比如大家都在访问商品详情页、查询商品库存、加载商品图片等。
每个请求都要通过商品服务的 Tomcat 线程池(连接池) 处理。
当并发太高时,请求会把 Tomcat 的线程/连接资源 占满。
这时,新来的请求没办法马上被处理,只能排队等待。
如果排队超时或者请求处理慢,就会出现超时、长延迟甚至失败的现象。
解决方案:
微服务保护
服务保护方案:sentinel
请求限流
隔离和熔断
分布式事务
初识分布式事务
Seata