SpringCloud笔记

在这里插入图片描述

https://cloud.tencent.com/developer/article/1468241

文章目录

在这里插入图片描述

一、微服务概述


前言

微服务架构是一种架构模式,它提倡将单体应用拆分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务之间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够独立的部署到生产环境、或者类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。(扩展:云原生 )

总结:把单体项目拆分成微服务,让我们的服务可以在多台服务器上部署,完成他们相互之间的调用,以及让它拥有更好的性能,去解决遇到的技术和业务问题 ,适合做敏捷开发(需求变更、发版)。

在这里插入图片描述


单体架构:项目初期我们都会先构建一个单体项目(首先用户群体不那么多,以及单台服务器性能足以够用),单体架构的优缺点:

  • 优点:架构简单、部署成本低(将项目打成jar包丢到服务器上即可),适合小型项目。
  • 缺点:耦合度高(维护困难、可扩展性差)
  • 不同项目模块间通信,方案1将模块安装到maven本地仓库,方案2使用Rest发送http请求实现远程调用(负载均衡会出问题)。

分布式架构:根据业务功能对系统进行拆解,每个业务功能模块都被称为一项服务,可以单独构建和部署。(粒度比较大)

  • 优点:降低服务耦合、有利于服务升级和扩展。
  • 缺点:架构复杂、难度大,适合大型互联网项目。

分布式架构虽然降低了服务耦合,但是服务拆分时也需要考虑一些问题:

  • 服务的拆分粒度。
  • 服务集群地址如何维护。(注册中心)
  • 服务之间如何实现远程调用。(Feign、Dubbo)
  • 服务健康是如何感知。
  • 以及如何访问我们的服务。(服务网关)
  • 总之需要制定一套行之有效的标准来约束分布式架构。

分布式系统(distributed system):由多台计算机和通信的软件组件通过计算机网络连接(本地网络或广域网)组成。


微服务:是一种良好的分布式架构解决方案。

  • 优点:拆分粒度更小,服务更独立,耦合度更低。
  • 缺点:架构非常复杂,运维、监控、部署随服务增加难道变大。

微服务架构具有以下特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责。
  • 自治:团队独立、技术独立、数据独立,独立部署和交付。
  • 面向服务:服务提供统一的标准接口,与语言和技术无关。
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题。

微服务技术对比

微服务组件Dubbo(阿里的)SpringCloud(整合了NetFlix的开源组件)SpringCloudAlibaba(完善的技术栈)
注册中心zookeeper、RedisEureka(优瑞卡)、ConsulNacos、Eureka
服务远程调用Dubbo协议Feign(http协议,降低学习成本)Dubbo、Feign(费嗯)
配置中心SpringCloudConfigSpringCloudConfig、Nacos
服务网关SpringCloudGateway(支持响应式编程、吞吐能力非常强)、ZuulSpringCloudGateway、Zuul
服务监控和保护dubbo-admin,功能较弱HystixSentinel

微服务使用场景:

  • SpringCloud+Feign:使用SpringCloud技术栈、服务接口采用Restful风格、服务调用采用Feign方式。
  • SpringCloudAlibaba+Feign:使用SpringCloudAlibaba技术栈、服务接口采用Restful风格、服务调用采用Feign方式。
  • SpringCloudAlibaba+Dubbo:使用SpringCloudAlibaba技术栈、服务接口采用Dubbo协议标准、服务调用采用Dubbo方式。
  • Dubbo体系:基于Dubbo的技术栈(zookeeper等等)、服务接口采用Dubbo协议标准、服务调用采用Dubbo方式。

二、SpringCloud


SpringCloud官网地址:https://spring.io/projects/spring-cloud

SpringCloud中文网:https://www.springcloud.cc/

SpringCloud中文文档:https://springcloud.cc/spring-cloud-dalston.html

在这里插入图片描述

SpringCloud是基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现、配置中心、全链路监控、服务网关、负载均衡、熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。

SpringCloud利用SpringBoot的开发便利性巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括服务服务注册与发现(Eureka、Nacos、Consul)、服务远程调用(OpenFeign、Dubbo)、统一网关路由(SpringCloudGateway、Zuul)、服务链路监控(Zipkin、Sleuth)、流控-降级-保护(断路器,Hystix、Sentinel) 等等,它们都可以用Spring Boot的开发风格做到一键启动和部署。(SpringBoot优点:自动装配)

SpringCloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过SpringBoot风格进行封装处理,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

SpringCloud是分布式微服务架构的一站式解决方案,集成了各种优秀微服务功能组件,江湖人称:微服务全家桶。


SpringCloud与SpringBoot兼容版本如下:

SpringBootSpringCloud关系
1.2.xAngel版本 (天使)兼容Spring Boot 1.2.x
1.3.xBrixton版本 (布里克斯顿)兼容Spring Boot 1.3.x,也兼容Spring Boot 1.4.x
1.4.xCamden版本 (卡姆登)兼容SpringBoot 1.4.x,也兼容SpringBoot 1.5.x
1.5.xDalston版本 (多尔斯顿)兼容SpringBoot 1.5.x,不兼容SpringBoot 2.0.x
1.5.xEdgware版本 (埃奇韦尔)兼容SpringBoot 1.5.x,不兼容SpringBoot 2.0.x
2.0.xFinchley版本 (芬奇利)兼容SpringBoot 2.0.x,不兼容SpringBoot 1.5.x
2.1.xGreenwich版本 (格林威治)兼容SpringBoot 2.1.x
2.2.x、2.3.x(starting with SR5)Hoxton版本 (霍斯顿)兼容SpringBoot 2.2.x、兼容SpringBoot 2.3.x
2.4.x2020.0.x aka llford兼容SpringBoot 2.4.x

boot对应cloud的版本地址:https://start.spring.io/actuator/info

在这里插入图片描述


SpringCloud五大组件:

  • 注册中心(服务注册与发现):Eureka、Nacos

  • 服务网关:SpringCloudGateway(支持响应式编程)、Zuul

  • 配置中心:SpringCloudConfig、Nacos

  • 远程调用:RestTemplate、Feign、OpenFeign

  • 服务保护:Hystrix(豪猪哥)、Sentinel (实现服务隔离防止雪崩)

在这里插入图片描述


cloud组件升级方案:(组件替换)

自2018年底,Netflix(奈飞)公司陆续宣布Eureka、Feign、Hystrix等框架进入维护状态,不再进行新功能的开发。

① 服务注册中心:

  • Eureka(Netflix)
  • Zookeeper(老项目:Dubbo做服务调用,Zookeeper做服务注册中心)
  • Consul(基于Go语言开发的,也是做服务注册中心,保守路线)
  • Nacos(阿里开源,推荐使用)

② 服务调用:(负载均衡)

  • 客户端:
    • Ribbon(Netflix)
    • LoadBalancer(SpringCloud,会慢慢取代Ribbon)
  • 服务端:
    • Feign(Netflix)
    • OpenFeign(SpringCloud)

③ 服务降级、熔断框架:

  • Hystrix (Netflix,豪猪哥)(设计理念被后人使用:服务熔断、服务降级、服务限流、服务隔离等等)
  • resilience4j
  • Sentinel (代替豪猪哥,阿里开源,推荐使用)

④ 服务网关:

  • Zuul(Netflix)
  • gateway(SpringCloud)

⑤ 服务配置:

  • Config(SpringCloud)
  • apolo(上海携程开源)
  • Nacos

⑥ 服务总线:

  • Bus(SpringCloud)
  • Nacos

三、Rest微服务构建


基于springboot模拟用户-订单模块微服务。(Maven聚合工程)

  • Consumer消费者(Client)通过REST调用Provider提供者(Server)中的服务。
  • 消费者:订单模块。
  • 提供者:用户模块。

前期准备:(MySQL数据库环境)

cloud_user.sql:

CREATE DATABASE cloud_user DEFAULT CHARACTER SET utf8mb4;

DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人',
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

INSERT INTO `tb_user` VALUES (1, '潘掌柜', '黑龙江省牡丹江市');
INSERT INTO `tb_user` VALUES (2, '文二狗', '陕西省西安市');
INSERT INTO `tb_user` VALUES (3, '华沉鱼', '湖北省十堰市');
INSERT INTO `tb_user` VALUES (4, '张必沉', '天津市');
INSERT INTO `tb_user` VALUES (5, '郑爽爽', '辽宁省沈阳市大东区');
INSERT INTO `tb_user` VALUES (6, 'kunkun', '山东省青岛市');

cloud_order.sql:

CREATE DATABASE cloud_user DEFAULT CHARACTER SET utf8mb4;

DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `user_id` bigint(20) NOT NULL COMMENT '用户id',
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称',
  `price` bigint(20) NOT NULL COMMENT '商品价格',
  `num` int(10) NULL DEFAULT 0 COMMENT '商品数量',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

INSERT INTO `tb_order` VALUES (101, 1, 'Apple 苹果 iPhone 12 ', 699900, 1);
INSERT INTO `tb_order` VALUES (102, 2, '雅迪 yadea 新国标电动车', 209900, 1);
INSERT INTO `tb_order` VALUES (103, 3, '骆驼(CAMEL)休闲运动鞋女', 43900, 1);
INSERT INTO `tb_order` VALUES (104, 4, '小米10 双模5G 骁龙865', 359900, 1);
INSERT INTO `tb_order` VALUES (105, 5, 'OPPO Reno3 Pro 双模5G 视频双防抖', 299900, 1);
INSERT INTO `tb_order` VALUES (106, 6, '美的(Midea) 新能效 冷静星II ', 544900, 1);
INSERT INTO `tb_order` VALUES (107, 2, '西昊/SIHOO 人体工学电脑椅子', 79900, 1);
INSERT INTO `tb_order` VALUES (108, 3, '梵班(FAMDBANN)休闲男鞋', 31900, 1);

在这里插入图片描述


1. 创建父工程

约定 > 配置 > 编码

1、创建maven工程。

在这里插入图片描述

在这里插入图片描述


2、配置字符编码

在这里插入图片描述


3、注解激活生效

在这里插入图片描述


4、配置java编译版本

在这里插入图片描述

maven编译版本:

在这里插入图片描述

在这里插入图片描述


5、将src目录删掉。

在这里插入图片描述


6、在父工程pom文件中,设置打包方式(聚合工程)

<!-- maven打包方式:jar(默认)、war(web项目)、pom(聚合工程) -->
<packaging>pom</packaging>

在这里插入图片描述


7、在父工程pom文件中,统一管理jar包的版本:

在这里插入图片描述

<!-- 统一管理jar包版本 -->
<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring-cloud.version>Hoxton.SR10</spring-cloud.version>
    <mysql.version>5.1.47</mysql.version>
    <mybatis.version>2.1.1</mybatis.version>
</properties>

8、在父工程pom文件中,导入springboot依赖:

在这里插入图片描述

<!--父工程-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.9.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

boot对应cloud版本地址:https://start.spring.io/actuator/info

在这里插入图片描述


9、在父工程pom文件中,锁定子工程使用的依赖版本:

在这里插入图片描述

<!--锁定依赖版本,一般都在父工程中操作-->
<!--这样子模块继承之后,使用父工程中的可选依赖仅需提供群组id和项目id,无需提供版本号-->
<!--dependencyManagement里面只是声明依赖版本,并没有引入该依赖,具体实现让子模块操作-->
<dependencyManagement>
    <dependencies>
        <!-- springCloud -->
        <dependency>
            <!--GAV-->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

10、在父工程pom文件中,引入跳过单元测试插件:

<build>
    <plugins>
        <!--打包跳过单元测试-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <skipTests>true</skipTests>
            </configuration>
        </plugin>
    </plugins>
</build>

在这里插入图片描述


2. 创建用户模块

微服务开发流程:建模块、改pom、写yml、启动类、业务类、测试

1、创建maven模块

在这里插入图片描述

在这里插入图片描述

2、导入依赖坐标:

<dependencies>
    <!--web开发场景-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

<!--打包插件-->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3、编写配置文件

server:
  port: 8081 # 服务端口号


spring:
  application:
    name: userservice #服务名称

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
    username: root
    password: 123456

mybatis:
  type-aliases-package: com.baidou.user.pojo #类型别名,实体类所在包
  configuration:
    map-underscore-to-camel-case: true #开启驼峰映射
  mapper-locations: classpath:mapper/*.xml #指定mapper映射文件的位置(resources/mapper/xxx.xml)

#日志级别
logging:
  level:
    com.baidou: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS # 格式化输出日期

4、编写启动类

package com.baidou.user;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 用户模块启动类
@SpringBootApplication
@MapperScan("com.baidou.user.mapper")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

5、编写业务

实体类:

package com.baidou.user.pojo;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String username;
    private String address;
}

mapepr接口:

package com.baidou.user.mapper;

import com.baidou.user.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

    //通过id获取用户信息
    @Select("select * from tb_user where id=#{id}")
    User selectById(Long id);
}

service接口:

package com.baidou.user.service;

import com.baidou.user.pojo.User;

public interface UserService {
    User findById(Long id);
}
package com.baidou.user.service.impl;

import com.baidou.user.mapper.UserMapper;
import com.baidou.user.pojo.User;
import com.baidou.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User findById(Long id) {
        return userMapper.selectById(id);
    }
}

controller:

package com.baidou.user.controller;

import com.baidou.user.pojo.User;
import com.baidou.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    // 访问路径: /user/102
    @GetMapping("/{id}")
    public User findById(@PathVariable("id") Long id) {
        return userService.findById(id);
    }
}

在这里插入图片描述


6、测试

在这里插入图片描述


3. 创建订单模块

1、创建maven模块(order_service)

2、导入依赖坐标:

<dependencies>
    <!--web开发场景-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

<!--打包插件-->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3、编写配置文件

server:
  port: 8080 # 服务端口号


spring:
  application:
    name: orderservice #服务名称

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 123456

mybatis:
  type-aliases-package: com.baidou.order.pojo #类型别名,实体类所在包
  configuration:
    map-underscore-to-camel-case: true #开启驼峰映射
#  mapper-locations: classpath:mapper/*.xml #指定mapper映射文件的位置(resources/mapper/xxx.xml)

#日志级别
logging:
  level:
    com.baidou: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS # 格式化输出日期

4、编写启动类

package com.baidou.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

// 订单模块启动类
@SpringBootApplication
@MapperScan("com.baidou.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    //发送http请求的工具类(spring提供)
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

5、编写业务

实体类:

//用户实体
@Data
public class User {
    private Long id;
    private String username;
    private String address;
}

// 订单实体
@Data
public class Order {
    private Long id;
    private String name;
    private Long price;
    private Integer num;
    private Long userId;
    private User user;
}

mapepr接口:

package com.baidou.order.mapper;

import com.baidou.order.pojo.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface  OrderMapper {

    //通过id获取用户信息
    @Select("select * from tb_order where id=#{id}")
    Order selectById(Long id);
}

service接口:

package com.baidou.order.service;

import com.baidou.order.pojo.Order;
import com.baidou.order.pojo.User;

public interface OrderService {
    Order findById(Long id);
}
package com.baidou.order.service.impl;

import com.baidou.order.mapper.OrderMapper;
import com.baidou.order.pojo.Order;
import com.baidou.order.pojo.User;
import com.baidou.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RestTemplate restTemplate;

    @Override
    public Order findById(Long id) {
        Order order = orderMapper.selectById(id);
       
        //实现远程调用
        String url = "http://localhost:8081/user/"+order.getUserId();
        User user = restTemplate.getForObject(url, User.class);
        
        order.setUser(user);
        return order;
    }
}

controller:

package com.baidou.order.controller;

import com.baidou.order.pojo.Order;
import com.baidou.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/{orderId}")
    public Order findById(@PathVariable("orderId") Long orderId) {
        return orderService.findById(orderId);
    }
}

在这里插入图片描述


6、测试

在这里插入图片描述


四、Eureka注册中心


Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

1. Eureka概述

Eureka是Netflix开发的服务注册中心框架,Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件,功能类似于Dubbo体系的注册中心(如 Zookeeper)。

SpringCloud将NetFlix公司开发的Eureka模块集成在子项目SpringCloudNetflix中,用来实现服务注册与发现。

在这里插入图片描述

Eureka采用C-S的架构设计,Eureka包含两个组件:EurekaServer和EurekaClient,EurekaServer提供服务注册与发现功能(可以看做服务中间商),使用EurekaClient连接EurekaServer便可完成服务节点的注册和远程调用。

服务应用启动后并维持向EurekaServer发送心跳连接(默认周期为30秒),如果EurekaServer在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。

这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行。

EurekaServer之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的EurekaServer都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。


Eureka的作用:

1、消费者如何获取服务提供者的具体信息?

  • 服务提供者启动时向Eureka注册自己的信息。(包括服务地址)
  • Eureka会将这些信息保存。
  • 服务消费者根据服务名称向Eureka拉取服务提供者信息。

2、如果有多个服务提供者,服务消费者该如何调用?

  • 服务消费者利用负载均衡算法,从服务列表中挑选一个。

3、服务消费者如果感知服务提供者的健康状态?(检测服务提供者是否挂掉)

  • 服务提供者会每隔30秒向EurekaServer发送心跳请求,并报告健康状态。
  • 之后Eureka会更新记录的服务列表信息,心跳不正常的服务会被剔除。
  • 然后服务消费者就可以拉到最新的服务信息。

Eureka和Dubbo对比:

Eureka架构图:

在这里插入图片描述

Dubbo架构图:

在这里插入图片描述



CAP定理(帽子)

在计算机科学中,CAP定理(CAP theorem), 又被称作 布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:

  • 一致性(Consistency):所有节点在同一时间具有相同的数据。
  • 可用性(Availability):保证每个请求不管成功或者失败都有响应。
  • 分区容错性(Partition Tolerance):系统中任意信息的丢失或失败不会影响系统的继续运作。

CAP理论的核心是:在一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时满足两点。

因此,根据CAP定理(理论)会分成 CA 原则、满足CP原则和满足AP原则:

  • CA :单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
  • CP :满足一致性,分区容忍性的系统,通常性能不是特别高。(zookeeper)
  • AP :满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。(Eureka)

2. 搭建eureka-server

基于上个项目,创建eureka-server模块,它是一个独立的微服务。

1、创建模块(eureka-server)

2、导入依赖

<!--eureka服务端-->
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

在这里插入图片描述


3、编写启动类

package com.baidou.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer //开启eureka注册中心功能
public class EurekaServerApp {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApp.class, args);
    }
}

在这里插入图片描述


4、编写application.yml配置文件

server:
  port: 10086 #eureka服务端口
spring:
  application:
    name: eureka-server #eureka服务名称
eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka/ #设置服务注册中心的URL,用于client和server端交互

5、启动服务,访问eureka注册中心:http://localhost:10086/、http://127.0.0.1:10086/

在这里插入图片描述

在这里插入图片描述

如果显示这个页面表示eureka启动成功了。


3. 服务注册

在之前的user_service模块中配置

1、向user_service模块的pom文件中,导入eureka客户端依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

在这里插入图片描述


2、在配置文件中添加服务名称、eureka地址

spring:
  application:
  	name: userservice #服务名称
    
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka/ #eureka注册中心地址

在这里插入图片描述


3、启动多个user_service实例(8081、8082)

① 打开springboot面板

在这里插入图片描述

② 复制user_service启动配置:

在这里插入图片描述

③ 修改服务名称以及服务端口:

在这里插入图片描述

在这里插入图片描述

  • UserApplication:8081
  • UserApplication2:8082

4、启动eureka,以及这两个userservice服务(shift选中服务,点击debug或run一键启动)

在这里插入图片描述

5、查看eureka-server管理页面:

在这里插入图片描述

ok,userservice服务已经注册成功了。


4. 服务发现

在之前的order_service模块中配置

1、向order_service模块的pom文件中,导入eureka客户端依赖

<!--eureka客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、编写yml配置

spring:
  application:
  	name: orderservice #服务名称
    
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka/ #eureka注册中心地址

3、服务拉取和负载均衡

① 负载均衡

我们的服务消费者orderservice要去eureka-server中拉取userservice服务的实例列表,并且实现负载均衡。

只需在启动类上给RestTemplate这个Bean添加一个@LoadBalanced注解即可。

@SpringBootApplication
@MapperScan("com.baidou.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    //发送http请求的工具类(spring提供)
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

② 服务拉取

修改之前远程访问userservice的url路径

在这里插入图片描述

用服务名代替ip和端口:

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RestTemplate restTemplate;

    @Override
    public Order findById(Long id) {
        Order order = orderMapper.selectById(id);
        // url地址,用服务名代替ip
        //  String url = "http://localhost:8081/user/"+order.getUserId();
        String url = "http://userservice/user/"+order.getUserId();
        // 发起调用
        User user = restTemplate.getForObject(url, User.class);
        order.setUser(user);
        return order;
    }
}

spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。


4、启动orderservice服务

在这里插入图片描述

5、测试订单服务的接口

在这里插入图片描述


五、Ribbon负载均衡


1. Ribbon概述

SpringCloudRibbon是基于Netflix Ribbon实现的一套客户端(消费者)负载均衡的工具。(瑞本)

Ribbon主要功能是提供客户端的软件负载均衡算法,将NetFlix的中间层服务连接在一起。Ribbon的客户端组件提供一系列完整的配置项如:连接超时、重试等等。简单的说,就是在配置文件中列出LoadBalancer(简称LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法!

客户端负载均衡:客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。(Ribbon)

服务端负载均衡:例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。

spring-cloud-starter-netflix-eureka-client中已经引入了ribbon,使用的时候就不需要我们再单独引入Ribbon啦:

在这里插入图片描述


Ribbon简单的说就是负载均衡+RestTemplate调用。(RestTemplate由spring提供)

在这里插入图片描述

  • RestTemplate文档地址:https://docs.spring.io/spring-framework/docs/4.3.9.RELEASE/spring-framework-reference/html/remoting.html#rest-resttemplate

  • RestTemplate的API文档地址:https://docs.spring.io/spring-framework/docs/5.2.10.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html


使用RestTemplate实现远程调用:(get读、post写)

  • getForObject:返回对象为响应体中数据转化成的对象,可以理解为Json串。
  • getForEntity:返回对象为ResponseEntity对象,包含响应中的一些重要信息,比如响应头、响应状态码、响应体等。

在这里插入图片描述

注:Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心ip地址和端口号。


2. Ribbon负载均衡原理

SpringCloud底层使用Ribbon组件,实现负载均衡功能。

负载均衡流程:

在这里插入图片描述


2.1 LoadBalancerIntercepor


LoadBalancerInterceptor会对RestTemplate的请求进行拦截,然后根据服务id(服务名称)从Eureka中获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

package org.springframework.cloud.client.loadbalancer;

import java.io.IOException;
import java.net.URI;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    //重写了ClientHttpRequestInterceptor接口中的方法
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
}

在这里插入图片描述

这个类的intercept方法拦截了用户的HttpRequest请求,然后做了以下几件事:

  • request.getURI():获取请求uri,例如 http://user-service/user/8
  • originalUri.getHost():获取uri路径的主机名,也就是服务id,例如 userservice
  • this.loadBalancer.execute():处理服务id,和用户请求。

这里的this.loadBalancerLoadBalancerClient类型,需要继续向下调用。


2.2 LoadBalancerClient


LoadBalancerClient是一个接口,execute方法的具体实现在RibbonLoadBalancerClient类中:

在这里插入图片描述

  • getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
  • getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个服务。本案例中,可以看到获取了8082端口的服务。

在这里插入图片描述
在这里插入图片描述


2.3负载均衡策略IRule


在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


总结:

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对服务地址做了修改。[外链图在这里插入图片描述

基本执行流程如下:

  • 拦截我们的RestTemplate请求http://userservice/user/2
  • RibbonLoadBalancerClient会从请求url中获取服务名称userservice
  • DynamicServerListLoadBalancer根据userservice到eureka拉取服务列表
  • eureka返回列表,localhost:8081、localhost:8082
  • IRule利用内置负载均衡规则,从列表中选择一个(例如localhost:8081)
  • RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求。

3. 负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:

在这里插入图片描述

不同规则的含义如下:

内置负载均衡规则类规则描述
RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule对以下两种服务器进行忽略:
(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何倍增加。
(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule随机选择一个可用的服务器。
RetryRule重试机制的选择逻辑

默认实现就是ZoneAvoidanceRule,是一种轮询方案。


自定义负载均衡策略

通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式:在orderservice中的启动类中(或者配置类),定义一个新的IRule:(负载均衡规则)
// 随机负载均衡策略
// 对所有实例都有效 
@Bean
public IRule randomRule(){
    return new RandomRule();
}

  1. 配置文件方式:在application.yml文件中,也可以修改Ribbon负载均衡规则:
# 给某个微服务配置负载均衡规则(只针对某一个服务有效)
# 这里对userservice服务配置负载均衡规则
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 (随机访问)

在这里插入图片描述

注意:一般用默认的负载均衡规则(轮询),不做修改。


4. 饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true  # 开启饥饿加载
    clients: userservice  # 指定对userservice服务进行饥饿加载

在这里插入图片描述


六、Nacos注册中心


Nacos官网:https://nacos.io/

Nacos是阿里的产品,也是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

在这里插入图片描述


1. Naos的安装与下载

以nacos1.4.1为例,进行安装操作。

1.1 下载Naos

Nacos项目地址:https://github.com/alibaba/nacos

GitHub的Release版本下载地址:https://github.com/alibaba/nacos/releases

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

windows版nacos下载地址:https://github.com/alibaba/nacos/releases/download/1.4.1/nacos-server-1.4.1.zip

linux版nacos下载地址:https://github.com/alibaba/nacos/releases/download/1.4.1/nacos-server-1.4.1.tar.gz


1.2 Windows上安装Naos


1、将下载好的安装包解压到指定目录:

在这里插入图片描述

  • bin目录:里面有nacos启动脚本。
  • conf目录:配置文件。

2、配置访问端口和ip

Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。

# 查询占用端口的进程号(pid)
netstat -ano | findstr [端口号]

# 关闭进程
taskkill /f /pid [进程号]

如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:

在这里插入图片描述

修配置内容:

server.port=8848  #nacos端口号
nacos.inetutils.ip-address=127.0.0.1  #此ip一定要指定,防止本机有多ip时,自动使用到其它ip

在这里插入图片描述


3.启动测试(在nacos的bin目录下执行)

// 单机启动
startup.cmd -m standalone

在这里插入图片描述


4、测试访问:http://127.0.0.1:8848/nacos

默认用户名和密码都是nacos。

在这里插入图片描述

在这里插入图片描述


1.3 Linux上安装Naos


//todo


2. Nacos服务注册与发现

Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。

因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。

主要差异在于:

  • 依赖不同。(使用Nacos需要在父工程中引入spring-cloud-alibaba-dependencies)
  • 服务地址不同。(eureka服务地址、nacos服务地址)

1、将之前的项目拷贝一份,然后把eureka的配置全部干掉。

2、向父工程的pom文件中,导入spring-cloud-alibaba管理依赖。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.6.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

在这里插入图片描述


3、向user_service和order_service中的pom文件,导入nacos客户端依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

在这里插入图片描述


4、配置nacos地址

在user_service和order_service的application.yml中,添加nacos服务地址:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848

在这里插入图片描述


5、启动服务

在这里插入图片描述


6、登录nacos管理页面,查看服务信息:http://127.0.0.1:8848/nacos

在这里插入图片描述

注意:别忘了注掉eureka的相关配置。


3. 服务分级存储模型

一个服务可以有多个实例,例如我们的user-service,可以有:

  • 127.0.0.1:8081
  • 127.0.0.1:8082
  • 127.0.0.1:8083

假如这些实例分布于全国各地的不同机房,例如:

  • 127.0.0.1:8081,在杭州机房
  • 127.0.0.1:8082,在杭州机房
  • 127.0.0.1:8083,在上海机房

Nacos就会将同一机房内的实例,划分为一个集群

也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型。

在这里插入图片描述

微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。

在这里插入图片描述

杭州机房内的order-service应该优先访问同机房的user-service。

3.1 给user-service配置集群


修改user-service的application.yml文件,添加nacos集群配置:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

在这里插入图片描述


重启两个user-service实例后,之后我们可以在nacos控制台看到下面结果:

在这里插入图片描述

我们再次复制一个user-service启动配置,添加属性:

-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH

配置如图所示:

在这里插入图片描述


启动UserApplication3后再次查看nacos控制台:

在这里插入图片描述
在这里插入图片描述


3.2 同集群优先的负载均衡


默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。

因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。

1、给order-service配置集群信息:

修改order-service的application.yml文件,添加集群配置:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

2、修改负载均衡规则:

修改order-service的application.yml文件,修改负载均衡规则:

userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 

在这里插入图片描述


4. 权重配置

实际部署中会出现这样的场景:

服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望让性能好的机器承担更多的用户请求。

但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。

因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。

在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:

在这里插入图片描述

在弹出的编辑窗口,修改权重:

在这里插入图片描述
在这里插入图片描述

注意:如果权重修改为0,则该实例永远不会被访问。


5. 环境隔离

Nacos提供了namespace来实现环境隔离功能。

  • nacos中可以有多个namespace。
  • namespace下可以有group、service等。
  • 不同namespace之间相互隔离,例如不同namespace的服务互相不可见。

在这里插入图片描述

5.1 创建namespace

默认情况下,所有service、data、group都在同一个namespace,名为public:

在这里插入图片描述

我们可以点击页面新增按钮,添加一个namespace:

在这里插入图片描述

然后,填写空间名称和描述:

在这里插入图片描述

在页面中就能看到一个新的namespace:

在这里插入图片描述


5.2 给微服务配置namespace(命名空间)


给微服务配置namespace只能通过修改配置来实现。

例如,修改order-service的application.yml文件:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
        namespace: 7ea21277-2a11-4be0-a7d9-9f4ddfcf7074 # 填名称空间的ID

在这里插入图片描述

重启order-service后,访问nacos控制台,可以看到下面的结果:

在这里插入图片描述

在这里插入图片描述

此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:

在这里插入图片描述

在这里插入图片描述


6. Nacos与Eureka的区别

Nacos的服务实例分为两种类型:

  • 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。

  • 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。

配置一个服务实例为永久实例:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false # 设置为非临时实例

Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:

在这里插入图片描述

1、Nacos与eureka的共同点:

  • 都支持服务注册和服务拉取。
  • 都支持服务提供者心跳方式做健康检测。

2、Nacos与Eureka的区别:

  • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式。
  • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除。
  • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时。
  • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式。

七、Nacos配置管理


Nacos除了可以做注册中心,同样可以做配置管理、服务总线来使用。

1. 统一配置管理

随着微服务部署的实例越来越多,当实例达到数十、数百个时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

在这里插入图片描述

首先在Nacos中将配置集中管理,当Nacos中的配置变更时,会及时通知微服务,实现配置的热更新。


1.1 在Nacos中创建配置文件


1、进入Nacos管理页面:http://localhost:8848/nacos/

2、添加一个配置文件

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

注意:一般会把项目中需要热更新的配置放到nacos中管理。而数据库等参数只需在微服务本地application.yml配置即可。


1.2 从Nacos中拉取配置


微服务要拉取nacos中管理的配置,要与本地的application.yml配置合并,才能完成项目启动。

如果没有读取到application.yml,又如何得知nacos地址呢?

  • spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取。

在这里插入图片描述


1、在user_service模块中,导入nacos配置管理依赖

<!--nacos配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、在user_service模块中,创建bootstrap.yaml文件:

# 读取nacos配置:userservice-dev.yaml
spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #多环境配置,当前是开发环境
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名

在这里插入图片描述

3、删掉重复nacos配置:(在bootstrap.yaml中已经配置好了nacos服务地址)

在这里插入图片描述


4、读取nacos配置

使用@Value("${xxx}")拿到配置信息

package com.baidou.user.controller;

import com.baidou.user.pojo.User;
import com.baidou.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Value("${pattern.date}") //读取配置信息
    private String date;

    // 访问路径: /user/102
    @GetMapping("/{id}")
    public User findById(@PathVariable("id") Long id) {
        return userService.findById(id);
    }

    @GetMapping("getDate")
    public String getDate() {
        return date;
    }
}

在这里插入图片描述

ok,成功从nacos中获取配置。


2. 配置热更新

如果有一天Nacos配置变更了,而微服务用的还是之前拉取配置,这时需要重启微服务才能拉取最新的配置。(重启微服务会发生各种诡异的问题哈哈哈)

解决方案:可以通过以下两种方式实现热更新,这样微服务无需重启就可以感知。

  • 方式一:通过@Value注解获取配置信息,结合@RefreshScope注解实现刷新配置。
  • 方式二:实体类 + @ConfigurationProperties注解,完成属性注入,实现自动刷新。(推荐使用)

方式1:@Value+@RefreshScope

在使用@Value注入的变量的类上,添加@RefreshScope注解,实现配置热更新。

在这里插入图片描述


修改nacos配置:

在这里插入图片描述

再次访问:

在这里插入图片描述


方式2:实体类 + @ConfigurationProperties

实体类 + @ConfigurationProperties注解,完成属性注入,实现自动刷新。

package com.baidou.user.config;

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 date;
}

在这里插入图片描述

注意事项:

  • 不是所有的配置都适合放到配置中心,这样维护起来比较麻烦。
  • 建议将一些关键参数、需要运行时调整的参数放到nacos配置中心。(一般都是自定义配置)

3. 配置文件共享

微服务启动时,会从nacos配置中心读取多个配置文件:

  • [spring.application.name]-[spring.profiles.active].yaml,即 服务名称-环境.yaml,例如:userservice-dev.yaml。

  • [spring.application.name].yaml,即 服务名称.yaml,例如:userservice.yaml。

而==[spring.application.name].yaml==不包含环境(profiles,例如dev、test),因此可以被多个环境共享。

总结:微服务启动时,会从nacos配置中心读取多个配置文件(带环境的配置文件和不带环境的配置文件),不带环境的配置文件可以被多个环境共享。


3.1 添加一个环境共享配置


在nacos上,创建userservice.yaml文件:

在这里插入图片描述

在这里插入图片描述


3.2 在user_service中读取共享配置


在user_service服务中,修改PatternProperties类,读取新添加的属性:

package com.baidou.user.config;

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 date;
    private String envSharedValue;
}

在user_service服务的UserController中,添加一个prop方法:

package com.baidou.user.controller;

import com.baidou.user.config.PatternProperties;
import com.baidou.user.pojo.User;
import com.baidou.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private PatternProperties patternProperties;

    @GetMapping("prop")
    public PatternProperties prop() {
        return patternProperties;
    }
 
}

3.3 使用不同的profile,运行两个用户服务


修改UserApplication2这个启动项,修改profile值为test:

在这里插入图片描述

在这里插入图片描述

  • UserApplication :8081 (profile是dev)
  • UserApplication2 :8082 (profile是test)

将这两个服务启动:

在这里插入图片描述


测试

UserApplication测试访问:http://localhost:8081/user/prop

在这里插入图片描述

UserApplication2测试访问:http://localhost:8082/user/prop

在这里插入图片描述


3.4 配置共享的优先级


当nacos、服务本地同时出现相同属性时,会有优先级加载顺序:

在这里插入图片描述


4. 搭建Nacos集群

//todo


八、Feign远程调用


Feign是Netflix公司提供服务调用组件,单独使用Feign比较麻烦。SpringCloud对Feign做了集成封装,提供了声明式服务调用组件Open-Feign。

在这里插入图片描述


RestTemplate发起远程调用会存在以下问题:

  • 代码可读性差,编程体验不统一。
  • 参数复杂URL难以维护。

在这里插入图片描述

Feign和OpenFeign

Open-Feign支持SpringMVC注解。是Spring Cloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。

Feign默认集成了Ribbon,所以使用Feign默认就具备负载均衡的效果。

maven依赖坐标如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

OpenFeign:是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

maven依赖坐标如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

1. 使用Feign替代RestTemplate

使用步骤:

① 导入openfeign起步依赖

② 在启动类上添加@EnableFeignClients注解,开启Feign功能

③ 编写FeignClient接口(Feign客户端)

④ 使用FeignClient中定义的方法代替RestTemplate。


1、在消费者order_service模块的pom中,导入feign的依赖:

<!--openfeign客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、在消费者order_service模块的启动类上,添加EnableFeignClients注解,表示开启Feign的功能。

在这里插入图片描述


3、编写Feign的客户端(在order_service中创建一个接口)

package com.baidou.order.client;

import com.baidou.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 *  用户服务Feign客户端
 */
@FeignClient("userservice")//指定要调用的服务名称
public interface UserClient {

    /*
        这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
            - 服务名称:userservice
            - 请求方式:GET
            - 请求路径:/user/{id}
            - 请求参数:Long id
            - 返回值类型:User
     */

    // 定义远程调用方法
    // 通过id查用户
    @GetMapping("/user/{id}") //调用对应controller的方法路径
    User findById(@PathVariable("id") Long id);// @PathVariable注解一定要指定参数名称,否则会报错
}

4、修改order_service中远程调用代码

package com.baidou.order.service.impl;

import com.baidou.order.client.UserClient;
import com.baidou.order.mapper.OrderMapper;
import com.baidou.order.pojo.Order;
import com.baidou.order.pojo.User;
import com.baidou.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;//用户服务客户端

    @Override
    public Order findById(Long id) {
        //  查询订单
        Order order = orderMapper.selectById(id);

        // 发起远程调用,使用Feign客户端代替RestTemplate:
        User user = this.userClient.findById(order.getUserId());
        //  封装数据
        order.setUser(user);
        return order;
    }
}

在这里插入图片描述

5、测试

在这里插入图片描述


2. 自定配置

Feign可以支持很多的自定义配置,可以修改的配置如下:(我们一会以修改日志级别为例)

类型作用说明
feign.Logger.Level修改日志级别包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析json字符串为java对象
feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送
feign. Contract支持的注解格式默认是SpringMVC的注解
feign. Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

一般情况下,Feign的默认配置就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。


2.1 基于配置文件方式修改Feign的日志级别


Feign有四种类型的日志:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间。
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息。
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

① 局部配置,只对单个服务生效(application.yml)

feign:  
  client:
    config: 
      userservice: # 如果这块写的是服务名称,那他针对某个微服务的配置,例如userservice
        loggerLevel: FULL #  日志级别 

② 全局配置,对所有服务都生效(application.yml)

feign:  
  client:
    config: 
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

在这里插入图片描述

测试:

在这里插入图片描述

2.2 基于Java代码方式修改Feign的日志级别


将之前的yml中feign日志级别配置直接注掉即可。

在这里插入图片描述

然后创建一个类,在类中声明一个Logger.Level的Bean对象:

package com.baidou.order.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;

/**
 * Feign客户端配置类
 *
 * @author 白豆五
 * @version 2023/2/17
 * @since JDK8
 */
public class FeignClientConfig {

    @Bean
    public Logger.Level feignLogLevel() {
        return Logger.Level.BASIC; // 日志级别为BASIC
        //仅记录请求的方法,URL以及响应状态码和执行时间
    }
}

在这里插入图片描述


① 如果要全局生效,需要在启动类上的@EnableFeignClients注解,通过属性defaultConfiguration指向我们刚才的配置:

@EnableFeignClients(defaultConfiguration = FeignClientConfig.class) 

在这里插入图片描述

测试:

在这里插入图片描述


② 如果想要它局部生效,只需在对应的@FeignClient注解的configuration属性中指定配置类即可:

注:局部生效,不需要在启动类上的@EnableFeignClients注解配置defaultConfiguration啦。

@FeignClient(value = "userservice",configuration= FeignClientConfig.class)

在这里插入图片描述


3. Feign的性能优化

Feign底层的客户端实现:(Feign底层发起http请求,依赖于其它的框架)

  • Feign 默认使用 HttpURLConnection 去发送请求,每次请求都会建立、关闭连接,很消耗时间。(不支持连接池)
  • Feign 还支持使用Apache的HttpClient以及OKHttp去发送请求,其中HTTPClient和OKHttp都是支持连接池的。
  • http连接池省去了tcp的3次握手和4次挥手的时间,可以节约大量的时间。

为什么要使用连接池,或者池化的技术?

  • 不使用连接池的情况:首先我们要创建连接,使用完毕后还要关闭连接。这两个操作都比较耗时,以及消耗系统资源。
  • 连接池好处:它是一种池化技术,池化技术的思想是实现资源的复用,避免资源的重复创建和销毁带来的性能开销。(池化、复用)
    • JUC并发编程:https://www.bilibili.com/video/BV1B7411L7tE

因此提高Feign的性能主要手段就是使用http连接池代替默认的HttpURLConnection 。


通过以下手段优化Feign的性能:

  • 使用http连接池代替默认的HttpURLConnection 。
  • 修改日志级别,最好使用BASIC或NONE。(不开日志效率更好)

1、在消费者order_service模块的pom中,导入HttpClient依赖

<!--httpclient依赖-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

在这里插入图片描述

2、改配置(application.yml,修改feign日志级别、开启httpClient功能,设置连接池参数)

feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

在这里插入图片描述

3、在启动类、用户服务Feign客户端上,注掉上个章节的Feign日志级别全局配置。

// 订单模块启动类

//开启Feign功能
// @EnableFeignClients(defaultConfiguration = FeignClientConfig.class) //全局配置日志级别
@EnableFeignClients
@SpringBootApplication
@MapperScan("com.baidou.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    //发送http请求的工具类(spring提供)
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
/**
 *  用户服务Feign客户端
 */
//指定要调用的服务名称
// @FeignClient(value = "userservice",configuration= FeignClientConfig.class)
@FeignClient(value = "userservice")
public interface UserClient {

    /*
        这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
            - 服务名称:userservice
            - 请求方式:GET
            - 请求路径:/user/{id}
            - 请求参数:Long id
            - 返回值类型:User
     */

    // 远程调用的方法
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

4、Debug重启订单服务

在这里插入图片描述


5、在远程调用处打断点,并访问order接口调试:http://localhost:8080/order/101

在这里插入图片描述
在这里插入图片描述

然后继续进方法打断点往下走:

在这里插入图片描述
在这里插入图片描述

最后放行:

在这里插入图片描述

别忘了删除之前打的断点,否则每次远程调用都影响程序执行:

在这里插入图片描述


4. Feign的最佳使用方式

我们知道在FeignClient中定义的远程调用方法,与提供者的controller代码非常相似,如何能简化这种重复的代码编写。

  • 有两种使用方式:继承、抽取。

4.1 继承方式


给消费者的FeignClient和提供者的Controller,提供一个父接口作为标准。

  • FeignClient继承这个接口后,就会拥有该方法,就不需要自己手动编写该方法啦。
  • Controller实现该接口,就会重写(实现)对应接口中的方法。

在这里插入图片描述

优点:简单、实现代码共享。

缺点:服务紧耦合、父接口参数列表中的映射不会被继承。


4.2 抽取方式


将FeignClient抽取成独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,供所有消费者使用。

例如,将UserClientUserFeign的默认配置都抽取到feign-api模块中,所有微服务引用该依赖包,就可以完成相关的远程调用操作。

在这里插入图片描述


4.3 基于抽取方式使用Feign


1、在父工程cloud_demo中创建一个子模块,取名为feign-api,然后引入openfeign的起步依赖。

在这里插入图片描述

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

2、将order-service中的UserClient、User、DefaultFeignConfiguration,抽取到feign-api模块中。

在这里插入图片描述


3、在order-service中,引入feign-api的依赖。

在这里插入图片描述


4、修改order-service中,所有使用FeignClient、POJO、Feign默认配置的地方,改成feign-api中包导入。(import)

在这里插入图片描述

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient是无法使用的。有两种方式解决:

  • 方式一(指定FeignClient所在包):@EnableFeignClients(basePackages = "com.baidou.feign")
  • 方式二(指定FeignClien字节码):@EnableFeignClients(clients={UsrerClient.class}) [硬编码]

在order的启动类上配置包扫描:

在这里插入图片描述


5、重启测试

在这里插入图片描述

在这里插入图片描述

测试:

在这里插入图片描述


九、Gateway网关


1. 为什么要使用网关

网关是一个组件,可以写很多代码,完成很多业务。

在这里插入图片描述

网关的作用:

  • 对用户请求做身份认证、权限校验。
  • 将用户请求路由到微服务,并实现负载均衡。
  • 对用户请求做限流。

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。


网关的技术实现:

在SpringCloud中网关的实现包括两种:

  • Zuul(Netflix),Zuul是基于Servlet的实现,属于阻塞式编程。
  • Gateway(SpringCloud),Gateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

2. Gateway快速入门

搭建网关服务:

1、在父工程中,创建一个module,取名为gateway,并引入nacos和springcloudgateway的依赖。

在这里插入图片描述

<dependencies>
    <!--gateway网关-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--nacos服务发现-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

2、编写路由配置和nacos服务地址(application.yml)

在这里插入图片描述

server:
  port: 10010 # 网关的服务端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服务地址
    # 网关的配置
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言(也就是拦截指定的路径请求),也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/** 

路由配置包括:(routes)

  • 路由id:路由的唯一标识(随便起,只要唯一即可,通常与服务名称对应)。

  • 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名称负载均衡。

  • 路由断言(predicates):判断路由的匹配规则。(拦截请求)


3、编写网关启动类

在这里插入图片描述

package com.baidou.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

4、启动项目、测试

在这里插入图片描述

在这里插入图片描述

测试:(走网关的端口号10010)

在这里插入图片描述


引入网关之后的调用流程:

在这里插入图片描述


小结

网关搭建步骤:

  • 创建项目,引入nacos服务发现和gateway的依赖。
  • 编写application.yml,包括服务的基本信息(服务名称和端口号),nacos服务地址,以及网关路由配置。

路由配置包括:

  • 路由id:路由的唯一标识(随便起,只要唯一即可,通常与服务名称对应)。

  • 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名称负载均衡。

  • 路由断言(predicates):判断路由的匹配规则。(拦截请求)

  • 路由过滤器(filters):对请求或响应做处理。


3. 断言工厂

路由断言工厂 Route Predicate Factory

网关路由可以配置的内容包括:

  • 路由id:路由的唯一标识。

  • uri:路由的目的地,支持lb和http两种。(lb负载均衡)

  • predicates:路由断言,判断请求是否符合要求,符号则转发到路由的目的地。(匹配规则)

  • 路由过滤器(filters):处理请求和响应。


我们在yml配置文件中编写的断言规则只是字符串,这些字符串会被PredicateFactory(断言工厂)读取并处理,转变为路由判断的条件

例如- Path=/user/**是按照路径匹配(如 路径以/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权重处理

我们只需掌握Path这种断言工厂就可以了。


示例:使用Path和Before配置断言工厂

在网关中,对user服务配置在某个时间点之前的判断规则:

- Before=2021-04-13T15:14:47.433+08:00[Asia/Shanghai]

在这里插入图片描述

重启网关、测试:http://localhost:10010/user/1

在这里插入图片描述


将之前在2021年改成2031年后,就可以正常访问了:

- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]

重启测试:http://localhost:10010/user/1

在这里插入图片描述


小结:

  • 断言工厂的作用:读取用户定义的断言条件,对请求做出判断。
  • 配置- Path=/user/xxx有什么作用?
    • 路径以/user开头的就认为是符合的。

4. 过滤器工厂

路由过滤器 GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

在这里插入图片描述

Spring提供了31种不同的路由过滤器工厂。例如:

名称说明
AddRequestHeader给当前请求添加一个请求头
RemoveRequestHeader移除请求中的一个请求头
AddResponseHeader给响应结果中添加一个响应头
RemoveResponseHeader从响应结果中移除有一个响应头
RequestRateLimiter限制请求的流量

4.1 请求头过滤器


下面我们以AddRequestHeader 为例,给所有进入userservice的请求添加一个请求头:Truth=HelloWorld!

实现步骤:

1、在gateway服务的application.yml文件,给userservice的路由添加过滤器:

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
          - Path=/user/** 
        filters: # 过滤器
          - AddRequestHeader=Truth,HelloWorld! # 添加请求头

在这里插入图片描述

注:当前过滤器是写在userservice路由下,因此仅仅对访问userservice的请求有效。(然后进入的请求就会被添加一个请求头)


2、在用户服务的controller中,定义请求fang’fa方法:

@GetMapping("getHeader")
public String getHeader(@RequestHeader("Truth") String header) {
    return header;
}

重启网关和用户服务、测试:http://localhost:10010/user/1

在这里插入图片描述


4.2 默认过滤器


如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:(书写时与routes属性平级)

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/**
      default-filters: # 默认过滤项
      - AddRequestHeader=Truth,HelloWorld!

在这里插入图片描述

重启网关、测试:
在这里插入图片描述


5. 全局过滤器

5.1 全局过滤器的作用


全局过滤器( GlobalFilter)的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。

两则的区别是:GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。

全局过滤器定义方式是实现GlobalFilter接口:

public interface GlobalFilter {
    /**
     *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
     *
     * @param exchange 请求上下文,里面可以获取Request、Response等信息
     * @param chain 用来把请求委托给下一个过滤器 
     * @return {@code Mono<Void>} 返回标示当前过滤器业务结束
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

在filter方法中编写自定义逻辑,可以实现下列功能:

  • 登录状态判断。
  • 权限校验。
  • 请求限流等。

5.2 自定义全局过滤器

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

  • 参数中是否有authorization;

  • authorization参数值是否为admin;

  • 如果同时满足前两个条件则放行,否则拦截。

1、在gateway中创建一个全局过滤器:(自定义类实现GlobalFilter接口)

package cn.itcast.gateway.filters;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

//全局自定义过滤器

@Order(-1) //过滤器的执行顺序
@Component
public class AuthorizeFilter implements GlobalFilter {
    
    /**
     *  全局过滤方法(编写处理逻辑)
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // 2.获取authorization参数
        String auth = params.getFirst("authorization");
        // 3.校验
        if ("admin".equals(auth)) {
            // 放行
            return chain.filter(exchange);
        }
        // 4.拦截
        // 4.1.禁止访问,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        // 4.2.结束处理
        return exchange.getResponse().setComplete();
    }
}

在这里插入图片描述

2、debug调试网关服务

在这里插入图片描述

访问网关:http://localhost:10010/user/getHeader?authorization=admin

在这里插入图片描述
在这里插入图片描述


5.3 过滤器的执行顺序


请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

在这里插入图片描述

过滤器执行的规则:

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定。
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

可以参考下面几个类的源码:

  • org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

  • org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链


6. 跨域问题

在发送请求时,如果出现以下情况中的任意一种,那么它就是跨域请求:

  • 协议不同,如 http 和 https;

  • 域名不同,如 www.taobao.com、www.jd.com、www.baidu.com

  • 端口不同,如 http:localhost:8080、http:localhost:8081

常见的跨域问题如下图所示:

在这里插入图片描述

在这里插入图片描述

跨域问题:指的是不同站点之间,使用 ajax 无法相互调用的问题。**跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。**但这个保护机制也带来了新的问题,它的问题是给不同站点之间的正常调用,也带来的阻碍。

解决方案:CORS,需要服务端进行响应的修改。资料:https://www.ruanyifeng.com/blog/2016/04/cors.html、https://blog.csdn.net/m0_71777195/article/details/126830773


解决跨域问题

在gateway服务的application.yml文件中,添加如下配置:

spring:
  cloud:
    gateway:
      # 全局的跨域处理
      globalcors:
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        # 跨域问题的解决方案: CORS
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
          #   - "http://localhost:8090"
          #   - "http://127.0.0.1:8090"
              - "*"  # *表示运行所有网站的跨域请求
              
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期(秒)

在这里插入图片描述

未来的路还很长:

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白豆五

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值