在之前我们学习微服务中的两个组件,一个是注册中心,一个负载均衡器。今天,我们主要学习三个内容,分别是:Nacos配置管理、Feign远程调用、Gateway服务网关。
目录
Nacos既可以作为注册中心,也可以用来作配置管理,微服务可以从nacos中进行注册发现和胚珠读取,具体如下所示:
在nacos的配置栏中点击➕添加配置,在弹出的列表中进行配置即可,并不是所有的都要配置,需要nacos统一管理的进行配置即可。
因为现在需要读取nacos配置文件,故需要知道nacos的地址,所以需要在bootstrap文件中配置nacos地址信息,如下所示。
需要知道去哪里拉取配置信息,并且需要知道配置信息的名称,故需要先导入配置坐标,然后在bootstrap.yml文件中输入配置的名称。
最后在表现层,拉去配置信息,并通过浏览器请求并显示配置信息,如下:
-
- //注入nacos中的配置属性
- @Value("${pattern.dateformat}")
- private String dateFormat ;
-
- @GetMapping("/now")
- public String now(){
- System.out.println(dateFormat);
- return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateFormat, Locale.CHINA)) ;
- }
从nacos拉取的配置信息,输出效果如下:
Nacos配置更新后,微服务不需要重启就能自动感知,需要通过下面两种方式实现。
方式1:在@Value注解上注入变量所在的类上添加@RefreshScope注解。
- import cn.itcast.user.pojo.User;
- import cn.itcast.user.service.UserService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.cloud.context.config.annotation.RefreshScope;
- import org.springframework.web.bind.annotation.*;
-
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.Locale;
-
-
- @Slf4j
- @RestController
- @RequestMapping("/user")
- @RefreshScope
- public class UserController {
-
-
- @Autowired
- private UserService userService;
-
- //注入nacos中的配置属性
- @Value("${pattern.dateformat}")
- private String dateFormat ;
-
- @GetMapping("/now")
- public String now(){
- System.out.println(dateFormat);
- return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateFormat, Locale.CHINA)) ;
- }
-
-
- @GetMapping("/{id}")
- public User queryById(@PathVariable("id") Long id) {
- return userService.queryById(id);
- }
- }
方式2:使用@ConfigurationProperties注解,可以自动刷新。
- import lombok.Data;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
-
-
- @Data
- @Component
- @ConfigurationProperties(prefix = "pattern")
- public class PatternProperties {
- private String dateformat;
- // private String envSharedValue;
- // private String name;
- }
在表现层将改配置作为bean注入进来,就可以使用了,如下:
-
- import cn.itcast.user.config.PatternProperties;
- import cn.itcast.user.pojo.User;
- import cn.itcast.user.service.UserService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.cloud.context.config.annotation.RefreshScope;
- import org.springframework.web.bind.annotation.*;
-
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.Locale;
-
-
- @Slf4j
- @RestController
- @RequestMapping("/user")
- @RefreshScope
- public class UserController {
-
-
- @Autowired
- private UserService userService;
-
- //注入nacos中的配置属性
- // @Value("${pattern.dateformat}")
- // private String dateFormat ;
- @Autowired
- private PatternProperties patternProperties ;
-
-
- @GetMapping("/now")
- public String now(){
- System.out.println(patternProperties.getDateformat());
- return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat(), Locale.CHINA)) ;
- }
-
-
- @GetMapping("/{id}")
- public User queryById(@PathVariable("id") Long id) {
- return userService.queryById(id);
- }
- }
微服务启动时会从nacos读取多个配置文件,无论怎么变都会读取多环境的共享配置文件,所以共享配置环境的公共配置。
在nacos中创建两个配置,一个是dev环境下的,另外一个是多环境共享的。
那么便可以在程序中加载配置中的nacos共享配置以便使用。
那么需要考虑的是,如果本地配置贺远程配置中用相同的配置,系统会选择哪一个配置呢?这就需要考虑配置优先级的问题。
优先级:远程带环境的配置>远程不带环境的配置>本地配置
我们假设有三台nacos节点,那么首先要实现的就是数据共享,我们实现Mysql集群,让nacos来访问mysql集群,进而实现数据共享。为了将多个服务按照负载均衡分发到不同的nacos节点,故使用Nginx实现负载均衡。
搭建nacos集群的基本步骤如下:
1)搭建数据库,初始化数据库表结构;2)下载nacos安装包;3)配置nacos;4)启动nacos集群;5)ngnix反向代理
首先我们看一下之前学的基于RestTemplate方式实现的服务的远程调用,该方法的代码可读性较差,参数复杂URL难以维护。
今天主要需要一个声明式的http客户端,以优雅的方式发送http请求。
首先需要在pom.xml加入坐标依赖;然后加入启动注解@EnableFeignClients;然后定义一个接口;最后使用接口中定义的方法替代RestTemplate实现远程调用。
-
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-openfeignartifactId>
- dependency>
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- import org.springframework.cloud.client.loadbalancer.LoadBalanced;
- import org.springframework.cloud.openfeign.EnableFeignClients;
- import org.springframework.context.annotation.Bean;
- import org.springframework.web.client.RestTemplate;
-
- @MapperScan("cn.itcast.order.mapper")
- @SpringBootApplication
- @EnableFeignClients
- public class OrderApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(OrderApplication.class, args);
- }
-
-
- @LoadBalanced
- @Bean //创建RestTemplate对象,并注入Spring容器
- public RestTemplate restTemplate(){
- return new RestTemplate() ;
- }
-
-
-
- }
-
- import cn.itcast.feign.pojo.User;
- import org.springframework.cloud.openfeign.FeignClient;
- import org.springframework.stereotype.Component;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
-
-
- @FeignClient("userservice")
- public interface UserClient {
- @GetMapping("/user/{id}")
- User getUserById(@PathVariable Long id) ;
- }
最后使用接口的getUserById方法替代RestTeplate完成远程调用。
-
- import cn.itcast.feign.pojo.User;
- import cn.itcast.order.clients.UserClient;
- import cn.itcast.order.mapper.OrderMapper;
- import cn.itcast.order.pojo.Order;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- @Service
- public class OrderService {
-
- @Autowired
- private OrderMapper orderMapper;
-
-
- @Autowired
- private UserClient userClient ;
-
- public Order queryOrderById(Long orderId) {
- // 1.查询订单
- Order order = orderMapper.findById(orderId);
- //用feign实现远程调用
- User user = userClient.getUserById(order.getUserId()) ;
- //封装到order中
- order.setUser(user);
- // 4.返回
- return order;
- }
-
- /*@Autowired
- private RestTemplate restTemplate;
- public Order queryOrderById(Long orderId) {
- // 1.查询订单
- Order order = orderMapper.findById(orderId);
- // 2.利用RestTemplate发起http请求,查询用户
- // 2.1.url路径
- String url = "http://userservice/user/" + order.getUserId();
- // 2.2.发送http请求,实现远程调用
- User user = restTemplate.getForObject(url, User.class);
- // 3.封装user到Order
- order.setUser(user);
- // 4.返回
- return order;
- }*/
- }
在Feign中一般会通过自定义的配置去覆盖默认的配置,通过可以进行修改的配置如下所示。
自定义配置feign 的方式有两种,一种是配置文件方式进行配置,具体如下:
第二种方式是使用java代码自定义feign配置文件,可以配置为全局配置,也可以为局部配置。
Feign的性能优化主要包含两个方面,第一:使用连接池代替默认的URLConnection,日志级别最好为none。
Feign性能优化:连接池优化,主要就是引入依赖,然后在连接池配置参数。
我们先来看feign的最佳实践分析的第一种方式,如下:但是一般情况下,不推荐这种方法,会导致紧耦合问题。
第二种最佳实践方式,是采用抽取的方式,不同服务直接从api中引入依赖即可,最后在表现层实现服务的远程调用。
下面我们演示一下上述的最佳实践的方式2,主要分为5步,首先需要创建一个名称为feign-api的module,然后引入feign的依赖,然后在将需要抽取的内容复制到feign-api中,最后直接在服务中引入feign-api依赖就可以,当然相应的包也需要换成feign-api中的包,最后重启服务,测试即可。
这种方法在最后注入UserClient的时候发现不能注入,因为没有扫描到包,应该在@EnableFeignClients注解中加入要扫描的包或者直接指定要扫描的类,我直接扫描类,具体如下:
@EnableFeignClients(clients = UserClient.class)
网关的主要功能有三个,分别为:身份认证和权限校验;服务路由与负载均衡;请求限流等。
网关技术的两种实现方式如下,一般我们使用满足响应式编程的Gateway实现。
下面我们使用gateway进行网关搭建,具体步骤如下:
第一,创建一个module作为网关,在pom.xml文件中引入nacos服务发现依赖贺网关依赖。需要编写该服务的启动类,则网关的搭建就完成了。
第二步,需要编写路由以及配置nacos地址,需要定义路由的id,路由的目标地址以及路由的断言。其中lb为loadbalance表示负载均衡。
我们看一下上面通过网关进行路由的过程,首先是用户通过浏览器向网关发起请求,然后通过网关的配置进行路由规则判断,从nacos注册中心拉取相应的服务,然后根据负载均衡原理,选取相应的服务处理请求。
其实路由的配置主要包括以下四个部分,路由的唯一标识id,路由的目的地,路由断言,路由过滤器,这里主要学习路由断言部分的路由断言工厂。
断言工厂就是处理断言的规则,具体的断言工厂有很多种。
我们看一下常见的断言工厂,一共11个断言规则,根据相应的断言规则进行配置即可。
路由过滤器是网关中提供的一种过滤器,主要用来都对请求的信息和微服务响应的信息进行处理。
一般通过名称就可以判断出过滤器的名称,根据自己的需求设置过滤器就可以了。
直接在网关中的.yaml文件中进行路由配置即可,我配置了一个 默认的过滤器,对于所有路由都进行过滤,如下:主要看最后一行的配置就可以 ,最后一行是
- server:
- port: 10010
- logging:
- #level:
- # cn.itcast: debug
- #pattern:
- # dateformat: MM-dd HH:mm:ss:SSS
- spring:
- application:
- name: gateway
- cloud:
- nacos:
- server-addr: localhost:8848 # nacos地址
- gateway:
- routes:
- - id: user-service # 路由标示,必须唯一
- uri: lb://userservice # 路由的目标地址
- predicates: # 路由断言,判断请求是否符合规则
- - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
- - id: order-service
- uri: lb://orderservice
- predicates:
- - Path=/order/**
- default-filters:
- - AddRequestHeader=Truth,Itcast is freaking awesome!
我们先了解一下什么是全局过滤器,全局过滤器和网关的过滤作用一样,都是对请求和响应进行处理的,但是全局过滤器可以自己写代码设置处理逻辑,即处理逻辑是不固定的。
下面编写一个全局过滤器实现相应的接口,在重写的方法中定义处理逻辑。
- import org.springframework.cloud.gateway.filter.GatewayFilterChain;
- import org.springframework.cloud.gateway.filter.GlobalFilter;
- import org.springframework.core.Ordered;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.stereotype.Component;
- import org.springframework.util.MultiValueMap;
- import org.springframework.web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
-
- // @Order(-1)
- @Component
- public class AuthorizeFilter implements GlobalFilter, Ordered {
- @Override
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // 1.获取请求参数
- ServerHttpRequest request = exchange.getRequest();
- MultiValueMap
params = request.getQueryParams(); - // 2.获取参数中的 authorization 参数
- String auth = params.getFirst("authorization");
- // 3.判断参数值是否等于 admin
- if ("admin".equals(auth)) {
- // 4.是,放行
- return chain.filter(exchange);
- }
- // 5.否,拦截
- // 5.1.设置状态码
- exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
- // 5.2.拦截请求
- return exchange.getResponse().setComplete();
- }
-
- @Override
- public int getOrder() {
- return -1;
- }
- }
最后在浏览器测试,必须要加上设置的参数才能通过网关访问服务。
我们之前学过三种过滤器,那么这三种过滤器的执行 顺序如何呢,怎么进行排序呢?
过滤器的执行顺序的逻辑如下,对于order值来说,越小执行顺序越靠前,当order值一样时,会按照默认过滤器->路由过滤器->全局过滤器的顺序执行。
我们先回顾一下跨域问题,跨域问题就是浏览器禁止发起者与服务端产生ajax请求,请求被浏览器拦截的问题,一般来说使用CORS跨域解决跨域问题。
网关处理跨域问题也是CORS方案,并且只需要简单的如下配置即可实现。一般要配置的参数如下所示。