Nacos集群搭建
Nacos配置管理
官方Nacos集群图:
其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。
在生产环境中,所有的微服务都需要被注册发现到注册中心去。 同时还需要读取配置, 所以都需要去找Nacos。
就像下图一样 :
统一配置管理
- 新建配置
DataID: 就是配置开发环境 xxx.yaml
- 配置格式就是YAML
- 配置内容:
内容就是做热更新配置是需要变的那种开关类型的配置(有热更新需求的)。 而不是数据库等没有什么需要做的那种配置。
配置获取
Spring项目的启动顺序
- 项目启动
- 读取本地的配置文件yml
- 创建Spring容器
- 加载Bean
- ….
在nacos的配置管理中,在步骤2之前 需要将nacos的配置文件和本地配置文件做一个合并, 然后再去做后续的动作。但是问题来了 。nacos的地址是在application.yml文件中的,那这样就无法先将nacos的配置加载之后再读取本地配置文件application.yml了。
所以就需要将nacos的地址放在本地配置文件读取之前来操作。 在Spring中, bootstrap.yml配置文件在本地配置文件之前被读取。
bootstrap.yml的优先级比Application高很多。
操作步骤
- 引入依赖
1 | <!--nacos配置管理依赖--> |
- 添加bootstrap.yaml
然后,在user-service中添加一个bootstrap.yaml文件,内容如下:
1 | spring: |
这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据
**${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}**作为文件id,来读取配置。
本例中,就是去读取userservice-dev.yaml:
- 读取nacos配置
在user-service中的UserController中添加业务逻辑,读取pattern.dateformat配置:
这里可以将@value注解换成@NacosValue
- 最后访问即可看到效果。
提示:
可能会出错。 出错的继续看看视频的弹幕提示吧
依赖错误是最熬人的。
配置热更新
方法一:@RefreshScope
在@Value注入的变量所在类上添加注解@RefreshScope:
方法二(推荐): 使用@ConfigurationProperties注解代替@Value注解
使用@ConfigurationProperties注解代替@Value注解。
在user-service服务中,添加一个类,读取patterrn.dateformat属性:
1 | package cn.itcast.user.config; |
在UserController中使用这个类代替@Value:
通过@ConfigurationProperties注入可以实现自动刷新。
而通过@Value 注解注入, 需要结合 @RefreshScope来刷新。
配置共享
其实微服务启动时,会去nacos读取多个配置文件,例如:
[spring.application.name]-[spring.profiles.active].yaml
,例如:userservice-dev.yaml[spring.application.name].yaml
,例如:userservice.yaml
而[spring.application.name].yaml
不包含环境,因此可以被多个环境共享。
下面通过heima的一个案例来试配置共享
1)添加一个环境共享配置
我们在nacos中添加一个userservice.yaml文件:
2)在user-service中读取共享配置
在user-service服务中,修改PatternProperties类,读取新添加的属性:
在user-service服务中,修改UserController,添加一个方法:
3)运行两个UserApplication,使用不同的profile
修改UserApplication2这个启动项,改变其profile值:
这样,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。
启动UserApplication和UserApplication2
访问http://localhost:8081/user/prop,结果:
访问http://localhost:8082/user/prop,结果:
可以看出来,不管是dev,还是test环境,都读取到了envSharedValue这个属性的值。
4)配置共享的优先级
当nacos、服务本地同时出现相同属性时,优先级有高低之分:
微服务会从nacos中读取的配置文件
搭建Nacos集群
Feign远程调用
代替RestTemplate调用
RestTmplate存在的问题及其Fegin的介绍
•代码可读性差,编程体验不统一
•参数复杂URL难以维护
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
Fegin的使用
1)引入依赖
我们在order-service服务的pom文件中引入feign的依赖:
1 | <dependency> |
2)添加注解
在order-service的启动类添加注解开启Feign的功能:
3)编写Feign的客户端
在order-service中新建一个接口,内容如下:
1 | package cn.itcast.order.client; |
这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
4)测试
修改order-service中的OrderService类中的queryOrderById方法,使用Feign客户端代替RestTemplate:
5)总结
使用Feign的步骤:
① 引入依赖
② 添加@EnableFe ignClients注解
③ 编写FeignClient接口
④ 使用FeignClient中定义的方法代替RestTemplate
Feign的自定义配置
Feign可以支持很多的自定义配置,如下表所示:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。
一般我们也就改一改日志级别就可以了。
修改日志级别
- 通过配置文件的方式修改
基于配置文件修改feign的日志级别可以针对单个服务:
1 | feign: |
所有的:
1 | feign: |
而日志的级别分为四种:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
最基本的只有请求 和 响应
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
- 通过Java代码的方式修改
先声明一个类,然后声明一个Logger.Level的对象:
1 | public class DefaultFeignConfiguration { |
1 | 如果要全局生效,将其放到启动类的 这个注解中: |
Feign使用优化
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
优化实例:例如Apache的HttpClient
- 引入依赖
在order-service的pom文件中引入Apache的HttpClient依赖:
1 | <!--httpClient的依赖 --> |
- 配置连接池
在order-service的application.yml中添加配置:
1 | feign: |
总结
- 日志级别尽量用basic
- 使用HttpClient或OKHttp代替URLConnection
① 引入feign-httpClient依赖
② 配置文件开启httpClient功能,设置连接池参数
总结
观察可以发现,Feign的客户端与服务提供者的controller代码非常相似:
Feign客户端:
UserController:
这样高度耦合的代码我们如何简化呢?
方法1: 继承方式
一样的代码可以通过继承来共享:
1)定义一个API接口,利用定义方法,并基于SpringMVC注解做声明。
2)Feign客户端和Controller都集成改接口
优点:
- 简单
- 实现了代码共享
缺点:
- 服务提供方、服务消费方紧耦合
- 参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
方法2: 抽取方式
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。
例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。
基于抽取的方式实现耦合代码的合并
1)抽取
首先创建一个module,命名为feign-api
项目结构:
在feign-api中然后引入feign的starter依赖
1 | <dependency> |
然后,order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
2)在order-service中使用feign-api
首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。
在order-service的pom文件中中引入feign-api的依赖:
1 | <dependency> |
修改order-service中的所有与上述三个组件有关的导包部分,改成导入feign-api中的包
3)重启测试
重启后,发现服务报错了:
这是因为UserClient现在在cn.itcast.feign.clients包下,
而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient。
4)解决扫描包问题
1 | 方式一: |
Gateway服务网关
之前我们可以了解到 ,服务内部我们可以通过使用Feign代替RestTemplate来实现微服务之间的调用关系。从而实现微服务之前的访问, 当外部用户调用请求时, 微服务接口就会响应请求 ,调用数据库,从而实现数据的数据库的访问等等一系列操作 。
但是如果说每个请求不经过过滤, 而从直接达到服务器上, 那么再多的微服务也挡不住别人的恶意访问,服务迟早会奔溃; 同时有些业务是不能对外访问的。 所以我们就需要一个类似守卫的组件, 来实现对请求的身份进行核实。
为什么需要网关?
什么是网关? 网关的作用是什么 ?
网关就是阻拦一切请求的”门卫” , 对所有的请求进行身份核实过滤,从而将核实的请求放行到对应的微服务接口上,并对服务进行负载均衡。
网关的作用:
- 身份认证 和 权限校验
- 服务路由、负载均衡
- 请求限流
在SpringCloud中网关的实现包括两种:
- gateway(SpringCloudgateway)
- zuul(SpringCloudzuul)
Zuul是基于Servlet的实现,属于阻塞式编程。
SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
**
**
gateway基本使用
下面就是搭建网关的基本路由功能的步骤:
- 创建SpringBoot工程gateway,引入网关依赖
- 编写启动类
- 编写基础配置和路由规则
- 启动网关服务进行测试
1)创建gateway服务,引入依赖
创建服务:
引入依赖:
1 | <!--网关--> |
2)编写启动类
1 | package cn.itcast.gateway; |
3)编写基础配置和路由规则★★★★
创建application.yml文件 和实现请求的路由。
1 | server: |
我们将符合****Path 规则的一切请求,都代理到 uri****参数指定的地址。
网关流程图
网关搭建步骤:
- 创建项目,引入nacos服务发现和gateway依赖
- 配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
- 路由id:路由的唯一标示
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
- 路由断言(predicates):判断路由的规则,
- 路由过滤器(filters):对请求或响应做处理
断言工厂
predicates 断言 : 判断请求url 是否符合要求, 符合则转发到路由目的地。
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。
这样的断言工厂在SpringCloudGateway中还有十几个。(后续学习需要到官网查询基本使用)
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
目前只是掌握一种就可以了, 剩下的用到了继续学习。
过滤器工厂
GatewayFilter是网关中提供的一种过滤器, 可以对进入网关的请求和微服务返回的响应做处理
路由过滤器的种类
Spring提供了31种不同的路由过滤器工厂。例如:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
请求头过滤器
以AddRequestHeader 为例来讲解。
需求:给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
只需要修改gateway服务的application.yml文件,添加路由过滤即可:
1 | spring: |
当前过滤器写在userservice路由下,因此仅仅对访问userservice的请求有效。
在controller层就可以按照下面的
required = false 代表请求头不是必须加的,可以没有。
默认过滤器(假设对所有的路由都生效)
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
1 | spring: |
总结:
过滤器的作用是什么?
① 对路由的请求或响应做加工处理,比如添加请求头
② 配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
① 对所有路由都生效的过滤器
全局过滤器(GlobalFilter)
之前学习的几种过滤器都是由SpringCloudGateway给我们制定好让我们来用的,但是如果想要实现自己的业务逻辑, 使用公共的过滤器却没办法实现, 那么我们开发岂不是要停止。
所以SpringCloud为开发者提供了一个全局过滤器GlobalFilter, 区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口
1 | public interface GlobalFilter { |
在filter中编写自定义逻辑,可以实现下列功能:
- 登录状态判断
- 权限校验
- 请求限流等
自定义全局过滤器
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有authorization,
- authorization参数值是否为admin
如果同时满足则放行,否则拦截
实现:
1 | package cn.itcast.gateway.filters; |
几种过滤器的执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
排序的规则是什么呢?
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
GatewayFilter 和 GlobalFilter不一样如何进行比较 ? 他们也不是继承同一个接口呀。
在AddRequestHeaderGatewayFilterFactory类中, 有一个实现类叫GatewayFilter ,它实现了GatewayFilterAdapter, 它可以将GlobalFilter 适配成为GatewayFilter从而让他们之间可以进行比较。
所以所有的过滤器都可以通过GatewayFilterAdapter适配成为GatewayFilter。从而实现过滤器之间的排序。
跨域问题
什么是跨域问题 ?
跨域:域名不一致就是跨域,主要包括:
- 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
- 域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS : 学习网址: https://www.ruanyifeng.com/blog/2016/04/cors.html
解决跨域问题
在gateway服务的application.yml文件中,添加下面的配置:
1 | spring: |