三天学完微服务其二

Nacos注册中心

启动Nacos

配置更新步骤

是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是**配置热更新**。

方式一

在@Value注入的变量所在类上添加注解@RefreshScope

方式二

使用@ConfigurationProperties注解代替@Value注解。

Nacos快速入门

引入依赖

后端输入账号Miami访问 点击加号新建配置

点击加号新建配置

在父工程的pom文件中引入SpringCloudAlibaba依赖

然后在user-service和order-service中引入nacos-discovery依赖

复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

Feign远程调用

引入依赖

java 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

添加注解

复制代码
@EnableFeignClients

编写Feign的客户端

在order-service中新建一个接口,内容如下:

复制代码
package cn.itcast.order.client;
​
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
​
@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:userservice

  • 请求方式:GET

  • 请求路径:/user/{id}

  • 请求参数:Long id

  • 返回值类型:User

总结

使用Feign的步骤:

① 引入依赖

② 添加@EnableFeignClients注解

③ 编写FeignClient接口

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

Gateway服务网关

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Gateway网关是我们服务的守门神,所有微服务的统一入口。

网关的核心功能特性:

  • 请求路由

  • 权限控制

  • 限流

权限控制

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

路由和负载均衡

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

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

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

  • gateway

  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

断言工厂

断言工厂是一种用于创建和管理**断言(Assertion)**的设计模式或工具。断言是软件测试中的重要技术,用于验证程序在运行过程中是否符合预期的行为和结果。断言工厂主要负责动态地生成不同类型的断言,简化测试代码的编写,提高代码的复用性和可维护性。

断言工厂(Assertion Factory)概述

断言工厂是一种用于创建和管理**断言(Assertion)**的设计模式或工具。断言是软件测试中的重


1. 为什么需要断言工厂?

在测试过程中,测试用例会包含大量的断言,验证程序的输入输出、逻辑和状态是否正确。随着测试规模的增大:

  • 重复代码多:不同测试用例中的断言代码可能非常相似。
  • 灵活性不足:直接在测试代码中硬编码断言逻辑,不便于扩展和修改。
  • 代码复杂性高:断言逻辑嵌套,测试代码难以阅读和维护。

断言工厂通过将断言逻辑抽象成统一的接口或工具,按需动态生成断言,可以有效解决这些问题。


2. 断言工厂的应用场景

  • 单元测试:在测试框架中,如 JUnit、TestNG 等,断言工厂可以封装常用的断言逻辑。
  • 集成测试:动态生成复杂的断言,验证系统级的行为和数据一致性。
  • API 测试:对 API 的返回值(如 JSON)进行动态的结构化断言。
  • 自动化测试:结合断言工厂与测试工具(如 Selenium、Appium),验证页面元素的状态或行为。
  • 自定义验证规则:当系统需要复杂或定制化的断言逻辑时,断言工厂可以提供可扩展的接口。

3. 断言工厂的核心思想

断言工厂的核心是将断言逻辑进行抽象和封装,动态创建断言实例,并提供一致的调用方式。主要包括:

  1. 统一接口:定义不同类型的断言行为。
  2. 工厂类:根据断言类型或参数动态生成断言对象。
  3. 参数化支持:通过输入参数决定生成何种断言。
  4. 高可扩展性:支持用户扩展自定义断言类型。

4. 断言工厂的实现步骤

以下是一个简单的断言工厂实现思路,基于 Java 语言和常见断言逻辑。

(1) 定义断言接口
public interface Assertion {
    void assertValue(Object actual, Object expected);
}
(2) 实现具体的断言逻辑
// 判断两个对象是否相等
public class EqualsAssertion implements Assertion {
    @Override
    public void assertValue(Object actual, Object expected) {
        if (!actual.equals(expected)) {
            throw new AssertionError("Expected: " + expected + ", but got: " + actual);
        }
    }
}

// 判断对象是否为 null
public class NullAssertion implements Assertion {
    @Override
    public void assertValue(Object actual, Object expected) {
        if (actual != null) {
            throw new AssertionError("Expected null, but got: " + actual);
        }
    }
}

// 判断数值是否在范围内
public class RangeAssertion implements Assertion {
    @Override
    public void assertValue(Object actual, Object expected) {
        if (!(actual instanceof Integer) || !(expected instanceof int[])) {
            throw new IllegalArgumentException("Invalid types for range assertion");
        }
        int actualValue = (Integer) actual;
        int[] range = (int[]) expected;
        if (actualValue < range[0] || actualValue > range[1]) {
            throw new AssertionError("Value: " + actualValue + " is not in range: [" + range[0] + ", " + range[1] + "]");
        }
    }
}
(3) 创建断言工厂类
public class AssertionFactory {

    public static Assertion getAssertion(String type) {
        switch (type) {
            case "equals":
                return new EqualsAssertion();
            case "null":
                return new NullAssertion();
            case "range":
                return new RangeAssertion();
            default:
                throw new IllegalArgumentException("Unsupported assertion type: " + type);
        }
    }
}
(4) 使用断言工厂
public class AssertionTest {
    public static void main(String[] args) {
        // 获取 "equals" 断言实例
        Assertion equalsAssertion = AssertionFactory.getAssertion("equals");
        equalsAssertion.assertValue(5, 5); // 测试通过
        // equalsAssertion.assertValue(5, 10); // 测试失败

        // 获取 "null" 断言实例
        Assertion nullAssertion = AssertionFactory.getAssertion("null");
        nullAssertion.assertValue(null, null); // 测试通过
        // nullAssertion.assertValue("not null", null); // 测试失败

        // 获取 "range" 断言实例
        Assertion rangeAssertion = AssertionFactory.getAssertion("range");
        rangeAssertion.assertValue(15, new int[]{10, 20}); // 测试通过
        // rangeAssertion.assertValue(25, new int[]{10, 20}); // 测试失败
    }
}

5. 优点与扩展性

优点

  1. 复用性高:断言工厂将断言逻辑封装后,可在不同测试用例中复用。
  2. 扩展性强:可以通过新增断言实现类快速扩展断言类型。
  3. 可维护性好:断言逻辑与测试用例分离,修改断言逻辑不影响测试用例代码。

扩展方向

  • 参数化断言:支持复杂参数配置,如阈值、正则表达式等。
  • 多语言支持:将断言工厂的实现推广到其他语言(如 Python、JavaScript)。
  • 集成测试框架:将断言工厂与测试框架(如 JUnit、TestNG)结合,实现统一的测试工具链。
  • 日志记录:断言失败时自动记录详细日志,便于问题排查。

6. 总结

断言工厂是一种简化和优化测试断言的设计方法,可以将常用的断言逻辑统一封装,减少测试代码冗余,提高可读性和维护性。在实际项目中,根据测试需求,灵活扩展断言工厂的功能,可以大幅提升测试的效率和质量。

路由过滤器

路由过滤器概述

路由过滤器是网关或服务路由过程中执行的一种拦截机制,用于对进入或流经的请求和响应进行预处理、后处理、验证、修改等操作。它常用于微服务架构中,通过统一网关来集中处理服务的路由和请求。

在 Spring Cloud Gateway 中,路由过滤器主要分为:

  1. 全局过滤器(Global Filter):对所有请求生效。
  2. 路由过滤器(Route Filter):只对特定路由生效。

1. 路由过滤器的作用

  • 请求验证与认证:过滤器可以检查用户的认证信息(如 Token),对非法请求进行拦截。
  • 动态路由:根据业务规则动态地修改路由目标地址。
  • 日志记录:记录请求或响应的日志。
  • 流量控制:限制请求的流量,比如限流、熔断等。
  • 修改请求或响应:对请求头、请求体、响应头、响应体进行加工。
  • 统一处理异常:拦截异常并返回标准的错误信息。

2. 路由过滤器的分类

在 Spring Cloud Gateway 中,过滤器主要有以下两类:

(1) GatewayFilter(路由过滤器)
  • 对指定的路由生效。
  • 在配置文件或代码中为某个路由配置过滤器。
(2) GlobalFilter(全局过滤器)
  • 对所有的路由生效。
  • 通常用于全局的请求处理逻辑。

3. 路由过滤器的使用方式

(1) 配置式路由过滤器

通过 application.yml 配置文件,直接为某个路由指定过滤器。

示例:

spring:
  cloud:
    gateway:
      routes:
        - id: user_service_route
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            - AddRequestHeader=X-Request-Id, 12345  # 添加请求头
            - StripPrefix=1                        # 移除路径前缀
            - Hystrix=default                      # 配置熔断

常用过滤器说明

  • AddRequestHeader:向请求中添加头部信息。
  • StripPrefix:去除请求路径的前缀。
  • Hystrix:配置熔断器,避免服务不可用时影响整个系统。
  • RewritePath:重写请求路径。
  • SetResponseHeader:向响应中添加头部信息。
  • RateLimiter:限流操作。
(2) 编程式路由过滤器

通过代码动态添加过滤器。

示例:

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("user_service_route", r -> r.path("/user/**")
                        .filters(f -> f.addRequestHeader("X-Request-Id", "12345")
                                       .stripPrefix(1))
                        .uri("lb://user-service"))
                .build();
    }
}

4. 全局过滤器

全局过滤器对所有路由都生效,通常用来实现全局的逻辑,如日志、认证等。

(1) 实现 GlobalFilter 接口

全局过滤器需要实现 GlobalFilter 接口和 Ordered 接口(定义过滤器的优先级)。

示例:

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行全局过滤器:请求路径 = " + exchange.getRequest().getPath());
        
        // 在这里可以对请求做预处理
        exchange.getRequest().mutate().header("X-Global-Filter", "Active").build();

        // 调用下一个过滤器
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // 在这里可以对响应做后处理
            System.out.println("全局过滤器执行完成");
        }));
    }

    @Override
    public int getOrder() {
        return 0; // 数值越小,优先级越高
    }
}
(2) 注册全局过滤器

将全局过滤器标记为 @Component,Spring Boot 会自动扫描并注册。


5. 自定义路由过滤器

如果默认的过滤器不能满足需求,可以通过实现 GatewayFilter 接口来自定义路由过滤器。

示例:

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomFilterFactory extends AbstractGatewayFilterFactory<CustomFilterFactory.Config> {

    public CustomFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            System.out.println("执行自定义过滤器:参数 = " + config.getName());
            
            // 过滤器逻辑
            exchange.getRequest().mutate().header("X-Custom-Filter", config.getName()).build();
            
            return chain.filter(exchange);
        };
    }

    public static class Config {
        private String name;

        // Getter and Setter
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}
配置自定义过滤器:

application.yml 中引用自定义过滤器:

spring:
  cloud:
    gateway:
      routes:
        - id: custom_filter_route
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - name=CustomFilter
              args:
                name: "TestFilter"

6. 路由过滤器的执行顺序

过滤器的执行顺序由其优先级决定:

  1. GatewayFilter:先执行局部的路由过滤器。
  2. GlobalFilter:后执行全局过滤器。

默认情况下:

  • GlobalFilter 优先级由 Ordered 接口的 getOrder() 方法控制。
  • GatewayFilter 的顺序在配置文件中按定义的顺序执行。

7. 常见问题和注意事项

  1. 异步处理 :Spring Cloud Gateway 基于 Reactor 框架,过滤器必须是非阻塞的,返回 Mono<Void>
  2. 路径冲突:多个路由可能匹配同一个请求,需注意路由优先级。
  3. 错误处理:可以通过全局异常处理器捕获和处理过滤器中的异常。
  4. 性能优化:对耗时的操作(如日志写入)需异步处理,避免阻塞网关。

8. 总结

路由过滤器是 Spring Cloud Gateway 中的核心功能,用于增强网关的灵活性和控制能力。通过路由过滤器,可以轻松实现请求预处理、响应后处理、动态路由、认证授权、日志记录等功能。在实际项目中,根据需求选择合适的过滤器类型,并结合全局过滤器与路由过滤器的特性,能够极大地提高微服务架构的可维护性和扩展性。

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

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

Docker

Docker 是一个开源的容器化平台,主要用于快速构建、测试和部署应用程序。它通过容器技术将应用程序及其所有依赖打包在一起,能够确保应用程序在任何环境中都能一致运行。


1. Docker 的核心概念

1.1. 镜像(Image)
  • 定义:镜像是一个只读的模板,包含应用程序和运行时环境。

  • 作用:用来创建 Docker 容器。

  • 特点:轻量级、便携,支持分层构建。

  • 命令

    docker pull <image-name>       # 拉取镜像
    docker images                 # 查看本地镜像
    docker rmi <image-id>         # 删除镜像
    
1.2. 容器(Container)
  • 定义:容器是镜像的运行实例,它是一个轻量、独立的可执行软件环境。

  • 作用:隔离应用程序和运行环境,支持应用的跨平台部署。

  • 特点:启动快、占用资源少。

  • 命令

    docker run <image-name>                  # 启动容器
    docker ps                                # 查看运行的容器
    docker ps -a                             # 查看所有容器
    docker stop <container-id>               # 停止容器
    docker rm <container-id>                 # 删除容器
    
1.3. Dockerfile
  • 定义:Dockerfile 是用来定义镜像的文件,包含一组指令,用于描述如何构建镜像。

  • 示例

    FROM openjdk:8-jdk-alpine
    COPY app.jar /app.jar
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    
  • 构建镜像命令

    docker build -t <image-name>:<tag> .
    
1.4. 仓库(Repository)
  • 定义:Docker 仓库用来存储镜像,分为本地仓库和远程仓库(如 Docker Hub)。

  • 相关命令

    docker login                   # 登录 Docker Hub
    docker tag <image> <repo-name> # 给镜像打标签
    docker push <repo-name>        # 推送镜像到远程仓库
    docker pull <repo-name>        # 从仓库拉取镜像
    

2. Docker 的主要优势

  1. 一致性:开发、测试和生产环境一致,避免"在我电脑上没问题"的现象。
  2. 轻量级:相比虚拟机更轻量,一个容器只需要几 MB 的存储空间。
  3. 快速启动:容器的启动时间通常以秒为单位。
  4. 高效资源利用:多个容器共享操作系统内核,降低资源开销。
  5. 易于扩展:通过 Docker Compose 和 Docker Swarm 快速实现多容器编排。
  6. 便携性:容器镜像可以轻松迁移到任何支持 Docker 的平台。

3. Docker 的基本操作

3.1. 镜像管理
docker pull <image-name>                 # 拉取镜像
docker images                            # 查看本地镜像列表
docker rmi <image-id>                    # 删除镜像
docker tag <image> <new-repo-name:tag>   # 为镜像打标签
3.2. 容器管理
docker run -d --name <container-name> <image-name>    # 后台运行容器
docker ps                                            # 查看运行中的容器
docker exec -it <container-id> bash                 # 进入容器
docker stop <container-id>                          # 停止容器
docker rm <container-id>                            # 删除容器
3.3. 数据管理
  • 数据卷(Volume) : 数据卷用于持久化容器中的数据,支持容器间数据共享。

    docker run -v /path/on/host:/path/in/container <image-name>
    
  • 数据卷相关命令

    docker volume create <volume-name>    # 创建数据卷
    docker volume ls                      # 查看所有数据卷
    docker volume rm <volume-name>        # 删除数据卷
    
3.4. 网络管理
  • 网络模式

    • bridge(默认):桥接网络,容器通过虚拟网卡与主机通信。
    • host:容器使用主机的网络栈。
    • none:容器没有网络。
  • 网络操作命令

    docker network create <network-name>        # 创建网络
    docker network ls                           # 查看网络
    docker network connect <network> <container> # 将容器连接到网络
    

4. Docker Compose

Docker Compose 是一个用于定义和运行多容器应用的工具。

4.1. 基本操作
  1. 创建 docker-compose.yml 文件:

    version: '3.8'
    services:
      web:
        image: nginx
        ports:
          - "8080:80"
      app:
        image: my-app
        depends_on:
          - web
    
  2. 启动服务:

    docker-compose up -d
    
  3. 停止服务:

    docker-compose down
    

5. Docker 的实际应用场景

  1. 持续集成和交付(CI/CD)

    • 构建、测试和部署在相同的 Docker 容器中运行,减少环境差异。
    • 与 Jenkins、GitLab CI 等工具集成。
  2. 微服务架构

    • 每个微服务运行在独立的容器中,独立开发、测试和部署。
    • 使用 Docker Compose 或 Kubernetes 进行服务编排和管理。
  3. 环境隔离

    • 为不同项目创建独立的运行环境。
    • 容器之间互不干扰。
  4. 云原生应用

    • 容器化应用部署在云环境中,快速扩展和迁移。
    • 与 Kubernetes 等容器编排工具结合使用。

6. Docker 常见问题

  1. 容器无法启动

    • 检查镜像是否正确。
    • 查看容器日志:docker logs <container-id>
  2. 网络连接问题

    • 确认容器是否连接到正确的网络。
    • 检查防火墙或主机端口映射。
  3. 容器数据丢失

    • 数据存储应使用数据卷或挂载主机目录。
  4. 镜像体积过大

    • 优化 Dockerfile,如减少层数、清理缓存。
  5. 端口冲突

    • 启动容器时,确保主机端口未被占用。

7.什么是数据卷

**数据卷(volume)**是一个虚拟目录,指向宿主机文件系统中的某个目录。

通过给 Nginx 容器挂载数据卷,可以方便地管理和持久化 Nginx 的配置文件、静态资源以及日志。


8. 总结

Docker 是现代软件开发的重要工具,通过容器化技术提供了快速、轻量、便携的环境隔离方案。它在开发、测试和生产部署中广泛应用,尤其适合微服务架构和云原生应用。结合 Docker Compose 和 Kubernetes,可以进一步增强服务的管理和扩展能力。

容器保护三个状态:
  • 运行:进程正常运行

  • 暂停:进程暂停,CPU不再运行,并不释放内存

  • 停止:进程终止,回收进程占用的内存、CPU等资源

其中:
  • docker run:创建并运行一个容器,处于运行状态

  • docker pause:让一个运行的容器暂停

  • docker unpause:让一个容器从暂停状态恢复运行

  • docker stop:停止一个运行的容器

  • docker start:让一个停止的容器再次运行

  • docker rm:删除一个容器

练习

需求:去DockerHub搜索并拉取一个Redis镜像

目标:

1)去DockerHub搜索Redis镜像

2)查看Redis镜像的名称和版本

3)利用docker pull命令拉取镜像

4)利用docker save命令将 redis:latest打包为一个redis.tar包

5)利用docker rmi 删除本地的redis:latest

6)利用docker load 重新加载 redis.tar文件

1. 拉取 Redis 镜像

docker pull redis:latest

2. 查看镜像

docker images

3. 导出 Redis 镜像为 redis.tar

docker save -o redis.tar redis:latest

4. 删除本地 Redis 镜像

docker rmi redis:latest

5. 重新加载 Redis 镜像

docker load -i redis.tar

6. 再次查看镜像

docker images

docker案例-进入容器,修改文件

java 复制代码
 docker run -d --name mn -p 8080:80 nginx

PS C:\Users\l\Desktop\ppp> docker run -d --name mn -p 8080:80 nginx

进入容器
java 复制代码
docker exec -it mn bash
进入nginx的HTML所在目录 /usr/share/nginx/html
html 复制代码
cd /usr/share/nginx/html

访问本地浏览器

由于本地终端输入不了中文用本地终端docker操作

点击file
根据/usr/share/nginx/html路径找到html.index
修改index.html文件

修改代码,保存,重启
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>早发白帝城</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 50px;
            background-color: #f5f5f5;
        }
        h1 {
            color: #333;
            font-size: 2em;
        }
        p {
            font-size: 1.5em;
            color: #666;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <h1>早发白帝城</h1>
    <p>朝辞白帝彩云间,</p>
    <p>千里江陵一日还。</p>
    <p>两岸猿声啼不住,</p>
    <p>轻舟已过万重山。</p>
</body>
</html>

访问

html 复制代码
http://localhost:8080/
相关推荐
苹果酱05671 小时前
Golang的文件加密技术研究与应用
java·vue.js·spring boot·mysql·课程设计
xweiran3 小时前
CAS操作的底层原理(总线锁定机制和缓存锁定机制 )
java·cas·处理器·总线锁定·缓存锁定
Miraitowa_cheems3 小时前
[JavaEE] Spring IoC&DI
java·spring·java-ee
V+zmm101343 小时前
基于微信小程序的水果销售系统的设计与实现springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计·springboot
头发那是一根不剩了3 小时前
java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
java
Archy_Wang_14 小时前
ASP.NET Core实现微服务--什么是微服务
后端·微服务·asp.net
huiyunfei4 小时前
MinorGC FullGC
java·jvm·算法
XWM_Web4 小时前
JavaAPI.02.包装类与正则表达式
java·开发语言·学习·eclipse
PangPiLoLo4 小时前
架构学习——互联网常用架构模板
java·学习·微服务·云原生·架构·系统架构·nosql