SpringCloud学习

18 篇文章 0 订阅
3 篇文章 0 订阅

由于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地址和端口号了。 ( 虽然这里集群只有一个实例。。。,有兴趣的同学可以多启动几个不同端口号的实例)

…。。。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值