SpringCloud学习

news2024/11/19 1:32:39

由于Spring Cloud基于Spring Boot构建,而Spring Cloud Alibaba又基于Spring Cloud Common的规范实现,所以当我们使用Spring Cloud Alibaba来构建微服务应用的时候,需要知道这三者之间的版本关系。

目前Spring Cloud Alibaba的版本与Spring Boot、Spring Cloud版本的兼容关系:

Spring BootSpring CloudSpring Cloud Alibaba
2.1.xGreenwich0.9.x
2.0.xFinchley0.2.x
1.5.xEdgware0.1.x
1.5.xDalston0.1.x

以上版本对应内容根据当前情况实时调整修改,以方便用户了解他们的对应关系变化情况

YML配置文件缺陷

注意:YAML目前还有一些不足,它无法通过@PropertySource注解来加载配置。但是,YAML加载属性到内存中保存的时候是有序的,所以当配置文件中的信息需要具备顺序含义时,YAML的配置方式比起properties配置文件更有优势。

@Value注解加载属性值的时候可以支持两种表达式来进行配置:

  • 一种是我们上面介绍的PlaceHolder方式,格式为 ${...},大括号内为PlaceHolder
  • 另外还可以使用SpEL表达式(Spring Expression Language), 格式为 #{...},大括号内为SpEL表达式

单体架构缺陷

eg:当访问http://127.0.0.1:8080/products时页面展示为(视图服务)

id名称价格
1苹果12.8
2香蕉12.5
3橘子12.2

product-service代码实现为(数据服务):

controller:return productService.listProducts();

service:return productMapper.listProducts();

public class Product {
    private int id;
    private String name;
    private int price;
}

这个项目很简单,就做两件事: 1. 提供数据 2. 展示数据。 这就是一个典型的单体结构。

它把两个服务 提供数据和展示数据 放在了一起,这就会出现固有的缺点。

    1. 如果要修改数据部分的代码, 那么必须把整个项目重新编译打包部署。 虽然展示部分,什么都没变但是也会因为重新部署而暂时不能使用,要部署完了,才能使用。
    2. 如果提供数据部分出现了问题,比如抛出了异常,会导致整个项目不能使用,展示数据部分也因此受到影响。
    3. 性能瓶颈难以突破

以上就是单体结构的问题,接下来把这个单体结构的项目,改造成为springcloud 微服务分布式架构,通过观察和参与改造过程,掌握和理解 springcloud。

接下来,通过分布式和集群的思路,对其改造。

微服务介绍

微服务概念

简单来说,一个 springboot 就是一个 微服务,并且这个 springboot 做的事情很单纯。 比如 product-service 这个项目,就可以拆成两个微服务,分别是 数据微服务,和视图微服务,其实就是俩 springboot, 只是各自做的事情都更单纯~

服务注册

注册中心可以使用:Eureka、Nacos…

有了微服务,就存在如何管理这个微服务,以及这两个微服务之间如何通信的问题,所以就要引入一个 微服务注册中心概念,这个微服务注册中心在 springcloud 里就叫做 eureka server, 通过它把就可以把微服务注册起来,以供将来调用。

在这里插入图片描述

服务访问

在业务逻辑上, 视图微服务 需要 数据微服务 的数据,所以就存在一个微服务访问另一个微服务的需要。
而这俩微服务已经被注册中心管理起来了,所以 视图微服务 就可以通过 注册中心定位并访问 数据微服务了。

在这里插入图片描述

分布式概念

按所述步骤改造后,就是分布式啦~ 简单说,原来是在一个 springboot里就完成的事情,现在分布在多个 springboot里做,这就是初步具备 分布式雏形了。
那么分布式有什么好处呢?

  1. 如果我要更新数据微服务,视图微服务是不受影响的
  2. 可以让不同的团队开发不同的微服务,他们之间只要约定好接口,彼此之间是低耦合的。
  3. 如果视图微服务挂了,数据微服务依然可以继续使用等等
服务集群

原来数据微服务只有这一个springboot, 现在做同样数据微服务的,有两个 springboot, 他们提供的功能一模一样,只是端口不一样,这样就形成了集群。
那么集群有什么好处呢?

  1. 比起一个 springboot, 两个springboot 可以分别部署在两个不同的机器上,那么理论上来说,能够承受的负载就是 x 2. 这样系统就具备通过横向扩展而提高性能的机制。
  2. 如果 8001 挂了,还有 8002 继续提供微服务,这就叫做高可用 。

在这里插入图片描述

分布式和集群周边服务

以上是很简单的分布式结构,围绕这个结构,我们有时候还需要做如下事情:

  1. 那些微服务是如何彼此调用的? sleuth 服务链路追踪
  2. 如何在微服务间共享配置信息?配置服务 Config Server
  3. 如何让配置信息在多个微服务之间自动刷新? RabbitMQ 总线 Bus
  4. 如果数据微服务集群都不能使用了, 视图微服务如何去处理? 断路器 Hystrix
  5. 视图微服务的断路器什么时候开启了?什么时候关闭了? 断路器监控 Hystrix Dashboard
  6. 如果视图微服务本身是个集群,那么如何进行对他们进行聚合监控? 断路器聚合监控 Turbine Hystrix Dashboard
  7. 如何不暴露微服务名称,并提供服务? Zuul 网关

搭建项目环境

项目目录

springcloud(父模块:删除没用的src目录)
— eureka-server(子模块)

springcloud父模块依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.how2j.springcloud</groupId>
    <artifactId>springcloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud</name>
    <description>springcloud</description>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.3.1</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>
创建子项目

eureka-server子模块依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.how2j.springcloud</groupId>
        <artifactId>springcloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>eurekaServer</artifactId>

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

</project>

EurekaServerApplication(eureka-server子模块启动类)

import cn.hutool.core.util.NetUtil;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        //8761 这个端口是默认的,就不要修改了,后面的子项目,都会访问这个端口。
        int port = 8761;
        if(!NetUtil.isUsableLocalPort(port)) {
            System.err.printf("端口%d被占用了,无法启动%n", port );
            System.exit(1);
        }
        new SpringApplicationBuilder(EurekaServerApplication.class).properties("server.port=" + port).run(args);
    }
}

EurekaServer 启动类。
这是一个 EurekaServer ,它扮演的角色是注册中心,用于注册各种微服务,以便于其他微服务找到和访问。
EurekaServer 本身就是个 Springboot 微服务, 所以它有 @SpringBootApplication 注解。
@EnableEurekaServer 表示这是个 EurekaServer 。

application.yml(eureka-server子模块配置信息)

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
 
spring:
  application:
    name: eureka-server

配置文件,提供 eureka 的相关信息。
hostname: localhost 表示主机名称。
registerWithEureka:false. 表示是否注册到服务器。 因为它本身就是服务器,所以就无需把自己注册到服务器了。
fetchRegistry: false. 表示是否获取服务器的注册信息,和上面同理,这里也设置为 false。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 自己作为服务器,公布出来的地址。 比如后续某个微服务要把自己注册到 eureka server, 那么就要使用这个地址: http://localhost:8761/eureka/

name: eurka-server 表示这个微服务本身的名称是 eureka-server

启动并访问

运行 EurekaServerApplication,并访问: http://127.0.0.1:8761/
主要看 :Instances currently registered with Eureka, 可以发现信息是:No instances available。
这表示 暂时还没有微服务注册进来。

创建子项目

springcloud(父模块:删除没用的src目录)
— eureka-server(子模块)
— product-data-service(子模块)

product-data-service子模块依赖

修改 pom.xml 为如下:
spring-cloud-starter-netflix-eureka-client 表示这是个 eureka 客户端。
spring-boot-starter-web: 表示这是个web服务,会提供控制层

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.how2j.springcloud</groupId>
        <artifactId>springcloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>productDataservice</artifactId>

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

</project>

Product(product-data-service子模块实体类)

@Data
public class Product {
    private int id;
    private String name;
    private int price;
}

ProductService(product-data-service子模块数据服务类)

服务类提供一个 Product 集合。
需要注意的是,这里把 端口号 放进了产品信息里。 这个数据服务会做成集群,那么访问者为了分辨到底是从哪个数据微服务取的数据,就需要提供个端口号,才能意识到是从不同的微服务得到的数据。

@Service
public class ProductService {
    @Value("${server.port}")
    String port;

    public List<Product> listProducts(){
        List<Product> ps = new ArrayList<>();
        ps.add(new Product(1,"product a from port:"+port, 50));
        ps.add(new Product(2,"product b from port:"+port, 150));
        ps.add(new Product(3,"product c from port:"+port, 250));
        return ps;
    }
}

ProductController(product-data-service子模块控制器)

@RestController
public class ProductController {
    @Autowired 
    private ProductService productService;

    @RequestMapping("/products")
    public Object products() {
        return productService.listProducts();
    }
}

ProductDataServiceApplication(product-data-service子模块启动类)

启动类, 考虑到要做集群。 所以让用户自己输入端口,推荐 8001,8002,8003.
但是每次测试都要输入端口号又很麻烦,所以做了个 Future类,如果5秒不输入,那么就默认使用 8001端口。

@SpringBootApplication
@EnableEurekaClient
public class ProductDataServiceApplication {
    public static void main(String[] args) {
        int port = 0;
        int defaultPort = 8001;
        Future<Integer> future = ThreadUtil.execAsync(() ->{
            int p = 0;
            System.out.println("请于5秒钟内输入端口号, 推荐  8001 、 8002  或者  8003,超过5秒将默认使用 " + defaultPort);
            Scanner scanner = new Scanner(System.in);
            while(true) {
                String strPort = scanner.nextLine();
                if(!NumberUtil.isInteger(strPort)) {
                    System.err.println("只能是数字");
                    continue;
                }
                else {
                    p = Convert.toInt(strPort);
                    scanner.close();
                    break;
                }
            }
            return p;
        });
        try{
            port=future.get(5,TimeUnit.SECONDS);
        }
        catch (InterruptedException | ExecutionException | TimeoutException e){
            port = defaultPort;
        }
        if(!NetUtil.isUsableLocalPort(port)) {
            System.err.printf("端口%d被占用了,无法启动%n", port );
            System.exit(1);
        }
        new SpringApplicationBuilder(ProductDataServiceApplication.class).properties("server.port=" + port).run(args);
    }
    /*@Bean
    public Sampler defaultSampler() {
        return Sampler.ALWAYS_SAMPLE;
    } */
}

application.yml(product-data-service子模块配置信息)

设置微服务的名称: product-data-service
设置注册中心的地址: http://localhost:8761/eureka/ , 与 eureka-server 中的配置 application.yml 遥相呼应

#   server:
#   port: 因为会启动多个 product-data-service, 所以端口号由用户自动设置,推荐 8001,8002,8003
 
spring:
  application:
    name: product-data-service
  zipkin:
    base-url: http://localhost:9411       
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
启动两个 微服务

启动两次 ProductDataServiceApplication, 分别输入 8001和8002.
可以在注册中心 http://127.0.0.1:8761/ 看到, product-data-service 这个微服务,有两个实例,分别是8001和8002端口。

服务访问

可以如此访问: http://127.0.0.1:8001/products ,http://127.0.0.1:8002/products,并看到如图所示的数据。
但是这种方式是通过 http 协议 访问微服务本身,和注册中心没有关系,也观察不到集群的效果,接下来我们就会讲如何用微服务,访问另一个微服务。

创建子项目

springcloud(父模块:删除没用的src目录)
— eureka-server(子模块)
— product-data-service(子模块)
— product-view-service-ribbon(子模块*后续不在使用)

product-view-service-ribbon子模块依赖

包含以下jar:
spring-cloud-starter-netflix-eureka-client: eureka 客户端
spring-boot-starter-web: springmvc
spring-boot-starter-thymeleaf: thymeleaf 做服务端渲染

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.how2j.springcloud</groupId>
        <artifactId>springcloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>product-view-service-ribbon</artifactId>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

    </dependencies> 

</project>

Product(product-view-service-ribbon子模块实体类)

@Data
public class Product {
    private int id;
    private String name;
    private int price;
}

Ribbon 客户端

Ribbon 客户端, 通过 restTemplate 访问 http://PRODUCT-DATA-SERVICE/products , 而 product-data-service 既不是域名也不是ip地址,而是 数据服务在 eureka 注册中心的名称。

这里只是指定了要访问的 微服务名称,但是并没有指定端口号到底是 8001, 还是 8002.

@Component
public class ProductClientRibbon {
    @Autowired
    private RestTemplate restTemplate;
    public List<Product> listProdcuts() {
        return restTemplate.getForObject("http://PRODUCT-DATA-SERVICE/products",List.class);
    }
}

ProductClientRibbon(product-view-service-ribbon子模块服务类)

服务类,数据从 ProductClientRibbon 中获取

@Service
public class ProductService {
    @Autowired ProductClientRibbon productClientRibbon;
    public List<Product> listProducts(){
        return productClientRibbon.listProdcuts();
    }
}

ProductController(product-view-service-ribbon子模块控制器)

@Controller
public class ProductController {
    @Autowired 
    private ProductService productService;
    @RequestMapping("/products")
    public Object products(Model m) {
        List<Product> ps = productService.listProducts();
        m.addAttribute("ps", ps);
        return "products";
    }
}

products.html(product-view-service-ribbon子模块数据渲染)

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>products</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <style>
            table {
                border-collapse:collapse;
                width:400px;
                margin:20px auto;
            }
            td,th{
                border:1px solid gray;
            }
        </style>       
    </head>
    <body>
        <div class="workingArea">
            <table>
                <thead>
                    <tr>
                        <th>id</th>
                        <th>产品名称</th>
                        <th>价格</th>
                    </tr>
                </thead>
                <tbody>
                    <tr th:each="p: ${ps}">
                        <td th:text="${p.id}"></td>
                        <td th:text="${p.name}"></td>
                        <td th:text="${p.price}"></td>
                    </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

ProductViewServiceRibbonApplication(product-view-service-ribbon子模块启动类)

启动类, 注解多了个 @EnableDiscoveryClient, 表示用于发现eureka 注册中心的微服务。

还多了个 RestTemplate,就表示用 restTemplate 这个工具来做负载均衡

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class ProductViewServiceRibbonApplication {
    public static void main(String[] args) {
        int port = 0;
        int defaultPort = 8010;
        Future<Integer> future = ThreadUtil.execAsync(() ->{
            int p = 0;
            System.out.println("请于5秒钟内输入端口号, 推荐  8010  超过5秒将默认使用 " + defaultPort);
            Scanner scanner = new Scanner(System.in);
            while(true) {
                String strPort = scanner.nextLine();
                if(!NumberUtil.isInteger(strPort)) {
                    System.err.println("只能是数字");
                    continue;
                }
                else {
                    p = Convert.toInt(strPort);
                    scanner.close();
                    break;
                }
            }
            return p;
        });
        try{
            port=future.get(5,TimeUnit.SECONDS);
        }
        catch (InterruptedException | ExecutionException | TimeoutException e){
            port = defaultPort;
        }
        if(!NetUtil.isUsableLocalPort(port)) {
            System.err.printf("端口%d被占用了,无法启动%n", port );
            System.exit(1);
        }
        new SpringApplicationBuilder(ProductViewServiceRibbonApplication.class).properties("server.port=" + port).run(args);

    }
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

application.yml(product-view-service-ribbon子模块配置信息)

配置类,指定了 eureka server 的地址,以及自己的名称。 另外是一些 thymeleaf 的默认配置。

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: product-view-service-ribbon
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
    encoding: UTF-8
    content-type: text/html
    mode: HTML5   
启动并访问

运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products

多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。

调用用例图

如图所示:

  1. 首先数据微服务和视图微服务都被 eureka 管理起来了。
  2. 数据服务是由两个实例的集群组成的,端口分别是 8001 , 8002
  3. 视图微服务通过 注册中心调用微服务, 然后负载均衡到 8001 或者 8002 端口的应用上。

在这里插入图片描述

Feign 概念

Feign 是什么呢? Feign 是对 Ribbon的封装,使用注解的方式,调用起来更简单。。。 也是主流的方式~

Ribbon方式调用

public List<Product> listProdcuts() {
    return restTemplate.getForObject("http://PRODUCT-DATA-SERVICE/products",List.class);
}

Feign方式调用

@FeignClient(value = "PRODUCT-DATA-SERVICE")
public interface ProductClientFeign {
 
    @GetMapping("/products")
    public List<Product> listProdcuts();
}
创建子项目

springcloud(父模块:删除没用的src目录)
— eureka-server(子模块)
— product-data-service(子模块)
— product-view-service-ribbon(子模块*后续不在使用)
— product-view-service-feign(子模块)

product-view-service-feign子模块依赖

jar 包多了个 spring-cloud-starter-openfeign,就是用来支持 Feign 方式的。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.how2j.springcloud</groupId>
        <artifactId>springcloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>product-view-service-feign</artifactId>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency> 
    </dependencies> 

</project>

Product(product-view-service-feign子模块实体类)

Feign 客户端

Feign 客户端, 通过 注解方式 访问 访问PRODUCT-DATA-SERVICE服务的 products路径, product-data-service 既不是域名也不是ip地址,而是 数据服务在 eureka 注册中心的名称。

注意看,这里只是指定了要访问的 微服务名称,但是并没有指定端口号到底是 8001, 还是 8002.

@FeignClient(value = "PRODUCT-DATA-SERVICE")
//@FeignClient(value = "PRODUCT-DATA-SERVICE",fallback = ProductClientFeignHystrix.class)
public interface ProductClientFeign {
 
    @GetMapping("/products")
    public List<Product> listProdcuts();
}
服务类获取数据

服务类,数据从 productClientFeign 中获取数据

@Service
public class ProductService {
    @Autowired ProductClientFeign productClientFeign;
    public List<Product> listProducts(){
        return productClientFeign.listProdcuts();
    }
}
控制器

控制器,把数据取出来放在 product.html 中

@Controller
public class ProductController {
    @Autowired ProductService productService;
    @RequestMapping("/products")
    public Object products(Model m) {
        List<Product> ps = productService.listProducts();
        m.addAttribute("ps", ps);
        return "products";
    }
}
启动类

启动类, 注解多了个 @EnableFeignClients, 表示用于使用 Feign 方式。

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
public class ProductViewServiceFeignApplication {

    public static void main(String[] args) {
        int port = 0;
        int defaultPort = 8012;
        Future<Integer> future = ThreadUtil.execAsync(() ->{
            int p = 0;
            System.out.println("请于5秒钟内输入端口号, 推荐  8012 、 8013  或者  8014,超过5秒将默认使用"+defaultPort);
            Scanner scanner = new Scanner(System.in);
            while(true) {
                String strPort = scanner.nextLine();
                if(!NumberUtil.isInteger(strPort)) {
                    System.err.println("只能是数字");
                    continue;
                }
                else {
                    p = Convert.toInt(strPort);
                    scanner.close();
                    break;
                }
            }
            return p;
        });
        try{
            port=future.get(5,TimeUnit.SECONDS);
        }
        catch (InterruptedException | ExecutionException | TimeoutException e){
            port = defaultPort;
        }
        if(!NetUtil.isUsableLocalPort(port)) {
            System.err.printf("端口%d被占用了,无法启动%n", port );
            System.exit(1);
        }
        new SpringApplicationBuilder(ProductViewServiceFeignApplication.class).properties("server.port=" + port).run(args);

    }

    /*@Bean
    public Sampler defaultSampler() {
        return Sampler.ALWAYS_SAMPLE;
    }*/  
}
配置信息

配置类,指定了 eureka server 的地址,以及自己的名称。 另外是一些 thymeleaf 的默认配置。

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: product-view-service-feign
  zipkin:
    base-url: http://localhost:9411   
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
    encoding: UTF-8
    content-type: text/html
    mode: HTML5 
# 断路器配置    
# feign.hystrix.enabled: true    
启动并访问

运行 ProductViewServiceFeignApplication以启动 微服务,然后访问地址:
http://127.0.0.1:8012/products

多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。

服务链路追踪

product-data-service和product-view-service 需要进行改造以使其可以被追踪到。

添加依赖

product-data-service和product-view-service添加以下依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
添加配置

product-data-service和product-view-service添加配置

spring:
  zipkin:
    base-url: http://localhost:9411
修改启动类

product-data-service和product-view-service的启动类上添加配置

@Bean
public Sampler defaultSampler() {
    return Sampler.ALWAYS_SAMPLE;
}  
  1. 需要启动链路追踪服务器,这个启动办法是下载 zipkin-server-2.10.1-exec.jar, 然后用如下命令启动:java -jar zipkin-server-2.10.1-exec.jar
  2. 挨个启动 eureka-server, 改造后的 product-data-service 和 product-view-service-feign 。 ( product-view-service-ribbon 后续不再使用)
  3. 访问一次 http://127.0.0.1:8012/products 通过 视图微服务去访问数据微服务,这样链路追踪服务器才知道有这事儿发生~
  4. 然后打开链路追踪服务器 http://localhost:9411/zipkin/dependency/ 就可以看到如图所示的 视图微服务调用数据微服务 的图形了。

断路器 HYSTRIX

所谓的断路器,就是当被访问的微服务无法使用的时候,当前服务能够感知这个现象,并且提供一个备用的方案出来。
当数据微服务无法使用了,如果有了断路器,那么视图微服务就能够知道此事,并且展示给用户相关的信息。 而不会报错或者一直卡在那里~

添加依赖

product-view-service-feign添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency> 
修改客户端

注解由原来的 @FeignClient(value = “PRODUCT-DATA-SERVICE”)
修改为 @FeignClient(value = “PRODUCT-DATA-SERVICE”,fallback = ProductClientFeignHystrix.class)

这就表示,如果访问的 PRODUCT-DATA-SERVICE 不可用的话,就调用 ProductClientFeignHystrix 来进行反馈信息。

ProductClientFeign

@FeignClient(value = "PRODUCT-DATA-SERVICE",fallback = ProductClientFeignHystrix.class)
public interface ProductClientFeign {
 
    @GetMapping("/products")
    public List<Product> listProdcuts();
}
配置消息

ProductClientFeignHystrix 实现了 ProductClientFeign 接口,并提供了 listProdcuts() 方法。
这个方法就会固定返回包含一条信息的集合~

ProductClientFeignHystrix

@Component
public class ProductClientFeignHystrix implements ProductClientFeign{
    public List<Product> listProdcuts(){
        List<Product> result = new ArrayList<>();
        result.add(new Product(0,"产品数据微服务不可用",0));
        return result;
    }
}

网关 ZUUL

我们现在有两种微服务,分别是数据微服务和视图微服务。
他们有可能放在不同的 ip 地址上,有可能是不同的端口。
为了访问他们,就需要记录这些地址和端口。 而地址和端口都可能会变化,这就增加了访问者的负担。
所以这个时候,我们就可以用网关来解决这个问题。
如图所示,我们只需要记住网关的地址和端口号就行了。
如果要访问数据服务,访问地址 http://ip:port/api-data/products 即可。
如果要访问视图服务,访问地址 http://ip:port/api-view/products 即可。

在这里插入图片描述

创建子项目 zuul
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.how2j.springcloud</groupId>
        <artifactId>springcloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>productServiceZuul</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>
</project>
配置启动类

启动类,主要是 @EnableZuulProxy

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableDiscoveryClient
public class ProductServiceZuulApplication {
    public static void main(String[] args) {
        int port = 8040;
        if(!NetUtil.isUsableLocalPort(port)) {
            System.err.printf("端口%d被占用了,无法启动%n", port );
            System.exit(1);
        }
        new SpringApplicationBuilder(ProductServiceZuulApplication.class).properties("server.port=" + port).run(args);

    }
}
配置文件信息

配置文件,进行了路由映射

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: product-service-zuul
zuul:
  routes:
    api-a:
      path: /api-data/**
      serviceId: PRODUCT-DATA-SERVICE
    api-b:
      path: /api-view/**
      serviceId: PRODUCT-VIEW-SERVICE-FEIGN
  1. 首先挨个运行 EurekaServerApplication, ProductDataServiceApplication, ProductViewServiceFeignApplication。
  2. 然后启动 ProductServiceZuulApplication
  3. 接着访问地址:http://localhost:8040/api-data/products、http://localhost:8040/api-view/products

这样就可以访问数据微服务和视微服务集群了,并且无需去记住那么多ip地址和端口号了。 ( 虽然这里集群只有一个实例。。。,有兴趣的同学可以多启动几个不同端口号的实例)

…。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/195020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1-1MySql复习

MySql复习 一 数据类型 数值 字符串 ​ char(5) 定长字符串 varchar(5) 可变长度字符串 日期 ​ timestamp 记录行数据的最后修改事件 二 基本查询 1 聚合函数 avg count sum max min 2 排序 order by ​ asc ​ desc 3 分组 group by … having … 分组通常跟…

Python语言的重要性(模式识别与图像处理课程作业)

Python语言的重要性&#xff08;模式识别与图像处理课程作业&#xff09;Python语言的重要性1 Python的优点主要有&#xff1a;1.1、简单1.2、易学1.3、速度快1.4、免费1.5、高层语言1.6、解释性1.7、面向对象1.8、可扩展性1.9、可嵌入性1.10、丰富的库1.11、规范的代码2 Pytho…

TCP/IP网络编程——套接字的多种可选项

完整版文章请参考&#xff1a; TCP/IP网络编程完整版文章 文章目录第 9 章 套接字的多种可选项9.1 套接字可选项和 I/O 缓冲大小9.1.1 套接字多种可选项9.1.2 getsockopt & setsockopt9.1.3 SO_SNDBUF & SO_RCVBUF9.2 SO_REUSEADDR9.2.1 发生地址分配错误&#xff08;B…

高效学 C++|编程实例之计算器

本节将实现一个能进行实数间加、减、乘、除运算的简易计算器。首先创建一个基于QWidget带界面的Qt项目&#xff0c;然后按照如下步骤进行操作&#xff1a; 01、计算器界面设计 在界面中拖入两个单行文本框和十七个按钮&#xff0c;按钮上显示的文字、按钮对象和单行文本框对象…

百分百拿捏offer的自动化测试面试题全套教程

最近很多咨询我&#xff0c;有没有软件测试方面的面试题&#xff0c;尤其是Python自动化测试相关的最新面试题&#xff0c;所以今天给大家整理了一份&#xff0c;希望能帮助到你们。 接口测试基础 1、公司接口测试流程是什么&#xff1f; 从开发那边获取接口设计文档、分析接口…

VUE3 指令 插槽

指令 指令是 Vue 模板语法里的特殊标记&#xff0c;在使用上和 HTML 的 data-* 属性十分相似&#xff0c;统一以 v- 开头&#xff08; e.g. v-html &#xff09;。 它以简单的方式实现了常用的 JavaScript 表达式功能&#xff0c;当表达式的值改变的时候&#xff0c;响应式地…

1x1卷积、Inception网络

目录1.1x1卷积(1x1 convolution)又称网络中的网络(network in network)池化层只能压缩图像的宽和高&#xff0c;1x1卷积能压缩通道数量&#xff0c;减少计算成本。如上图&#xff0c;输入维度的通道数为192&#xff0c;用32个1x1x192的filters&#xff0c;就能将输出的通道数压…

java基础—面试题一

文章目录1.和equals区别是什么&#xff1f;2.Java中的 <<、>>、>>> 是什么3.if-else-if-else与switch的区别4.while和do-while的区别5.switch 是否能作用在 byte 上&#xff0c;是否能作用在 long 上&#xff0c;是否能作用在String上6.&和&&…

大数据技术架构(组件)16——Hive:内置UDTF函数

1.4.11、内置UDTF函数1.4.11.1、explodeselect explode(array(100,200,300));Array<int> myCol[100,200,300][400,500,600]得到的结果如下&#xff1a;(int) myNewCol1002003004005006001.4.11.2、posexplodeselect posexplode(array(A,B,C));1.4.11.3、parse_url_tuples…

2023云原生安全值得关注的3个方向

如果说过去几年教会了我们什么的话&#xff0c;那就是云原生和开源环境中安全的重要性。 Log4j 等漏洞产生的重大影响&#xff0c;在无数的行业中浮现&#xff0c;对于云原生环境中的其他安全问题也越来越受到重视。 组织不再质疑是否要迁移到云端&#xff0c;而是在寻找最快、…

centos下安装docker 并通过docker安装gitlab

一:安装docker1、若之前安过docker&#xff0c;可以先卸载yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-engine-selinux \docker-engine \docker-ce2、更新yum…

软件测试基础(四) 之 软件测试的覆盖率

一、什么是软件测试的覆盖率&#xff1f;软件测试覆盖率是软件测试技术有效性的一个度量手段&#xff0c;用来度量测试完整性。意思概括的说&#xff0c;软件测试的工作中会有非常非常多的item&#xff08;任务&#xff09;&#xff0c;执行过的任务和总任务数的一个比值&#…

尚医通 (二)项目搭建

目录一、工程结构介绍1、工程结构2、模块说明二、创建父工程1、创建sprigboot工程yygh_parent2、删除 src 目录3、配置 pom.xml4、在pom.xml中添加依赖的版本三、搭建model模块1、在父工程yygh_parent下面创建模块model2、添加项目需要的依赖3、复制项目实体类和VO类四、搭建se…

require和important区别

1.require是赋值过程&#xff0c;就是把一个值赋值给另一个&#xff0c;important是对这个值的引用 2 . require 是赋值过程并且是运行时才执行&#xff0c;也就是同步加载&#xff0c;import 是解构过程并且是编译时执行&#xff0c;理解为异步加载 3.require 的性能相对于 im…

Linux部署达梦数据库超详细教程

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;国产数据库-达梦数据库&#xff08;主要讲一些达梦数据库相关的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下达梦数据库的下载与安装教程&#xff08;Linux版&#x…

百度网盘秒传链接生成及提取方法

百度网盘秒传链接生成及提取方法 1.认识秒传链接 首先&#xff0c;我们认识一下秒传链接的格式&#xff1a; 秒传链接是由标准提取码文件名组成。例如下面的格式&#xff1a; fd00338387f50ee5919eb3df4cfce6e3#5048587008#/影视/电影/救火奶爸.mp4 百度网盘秒传链接的提取主…

FISSURE:一款功能强大的RF和逆向工程框架

关于FISSURE FISSURE是一款功能强大的RF和逆向工程框架&#xff0c;该工具适用于不同技能水平的安全研究人员&#xff0c;并提供了信号检测、信号分类、协议发现、渗透测试、IQ操作、漏洞分析、自动化和AI/机器学习等功能。该框架旨在促进软件模块、无线电、协议、信号数据、脚…

2023年怎么开通一个抖音小店?店铺开通后做什么?开店指南!

大家好&#xff0c;我是王路飞。 2023年都已经过去一个月了&#xff0c;你开通抖音小店了吗&#xff1f; 作为目前最受欢迎的创业和副业项目&#xff0c;开通抖音小店的商家数量与日俱增&#xff0c;都是为了蹭一下抖音流量的红利&#xff0c;毕竟直播带货如今正处在风口。 …

50条必背JAVA知识点(三)

31.面向对象中两个重要的概念&#xff1a;类&#xff1a;对一类事物的描述&#xff0c;是抽象的、概念上的定义对象&#xff1a;是实际存在的该类事物的每个个体&#xff0c;因而也称为实例(instance) 32.虚拟机栈&#xff0c;即为平时提到的栈结构。局部变量存储在栈结构中&a…

专利的申请和驳回

说明书和权利要求的区别 说明书里面会写这个新方案的具体内容&#xff0c;实施方案&#xff0c;解释说明等&#xff0c;权利要求书的话&#xff0c;就是对这些具体的内容进行概括 说明书应当补充说明该发明相比有技术的优势 就投屏举例: 到底怎么写 三篇 阿里 楼x投屏方法及装…