# 从浅入深 学习 SpringCloud 微服务架构(六)Feign(3)

从浅入深 学习 SpringCloud 微服务架构(六)Feign(3)

一、组件的使用方式总结

1、注册中心

1) Eureka

搭建注册中心

引入依赖 spring-cloud-starter-netflix-eureka-server。
配置 EurekaServer。
通过 @EnableEurekaServer 激活 Eureka Server 端配置。

服务注册

服务提供者引入 spring-cloud-starter-netflix-eureka-client 依赖。
通过 eureka.client.serviceur1.defaultZone 配置注册中心地址。

2)consul

搭建注册中心

下载安装 consul
命令形式启动 consul consul agent -dev

服务注册

服务提供者引入 spring-cloud-starter-consul-discovery 依赖。
通过 spring.c1oud.consul.host 和 spring.cloud.consul.port 指定 Consul Server 的请求地址。

2 服务调用

1)Ribbon

通过 Ribbon 结合 RestTemplate 方式进行服务调用只需要在声明 RestTemplate 的方法上添加注解	@LoadBalanced 即可。
可以通过{服务名称}.ribbon.NFLoadBalancerRuleClassName 配置负载均衡策略。

2)Feign

服务消费者引入 spring-cloud-starter-openfeign 依赖。
通过 @FeignClient 声明一个调用远程微服务接口。
启动类上通过 @EnableFeignclients 激活 Feign。

二、高并发问题:模拟环境

1、在消费者子工程(子模块) order_service 中,application.yml 配置文件中,添加 tomcat 最大连接数量,用以模拟高并发环境问题。


##  spring_cloud_demo\order_service\src\main\resources\application.yml

server:
  port: 9002 #端口
#  port: ${port:9002}  # 启动端口设置为动态传参,如果未传参数,默认端口为 9002
  tomcat:
    max-threads: 10  # 设置 tomcat 最大连接数量,用以模拟高并发环境问题。

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
#    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
    username: 'root'
    password: '012311'
  application:
    name: service-order #服务名称
  jpa:
    database: MySQL
    show-sql: true
    open-in-view: true

eureka:  # 配置 Eureka
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true  # 使用 ip 地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port}

# 配置 feign 日志的输出:
# 日志配置:NONE:不输出日志,BASIC:适用于生产环境追踪问题,HEADERS:在BASIC基础上记录请求和响应头信息,FULL:记录所有。
feign:
  client:
    config:
      service-product:  # 需要调用的服务名称
        loggerLevel: FULL
logging:
  level:
    djh.it.order.feign.ProductFeignClient: debug


2、在 消费者子工程(子模块) order_service 中,OrderController.java 文件中,添加一个方法,用以模拟高并发环境问题。


/**
 *   spring_cloud_demo\order_service\src\main\java\djh\it\order\controller\OrderController.java
 *
 *  2024-4-24 订单的 controller 类 OrderController.java
 */
package djh.it.order.controller;

import djh.it.order.command.OrderCommand;
import djh.it.order.domain.Product;
import djh.it.order.feign.ProductFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

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

    // 注入 restTemplate 对象
    @Autowired
    private RestTemplate restTemplate;

    @Autowired   //注入 feign 组件的接口类
    private ProductFeignClient productFeignClient;

    /**
     *  注入 DiscoveryClient : springcloud 提供的获取元数据的工具类。
     *      调用方法获取服务的元数据信息。
     */
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     *  使用 基于 ribbon 的形式调用远程微服务:
     *  1、使用 @LoadBalanced 注解 声明 RestTemplate
     *  2、使用服务名称 service-product 替换 IP 地址 。
     *
     * @param id
     * @return
     */
    @RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
    public Product findById(@PathVariable Long id){
        
        Product product = null;
//        //product = restTemplate.getForObject("http://127.0.0.1:9001/product/1", Product.class);
//        product = restTemplate.getForObject("http://service-product/product/1", Product.class);

        //通过 Feign 自动的接口调用远程微服务
        product = productFeignClient.findById(id);

        return product;
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String findOrder(@PathVariable Long id){
        System.out.println(Thread.currentThread().getName());
        return "添加一个方法,用以模拟高并发环境问题。--- 根据id查询订单";
    }

//    @RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
//    public Product findById(@PathVariable Long id){
//
//        //调用 discoveryClient 方法,已调用服务名称获取所有的元数据。
//        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
//        for (ServiceInstance instance : instances) {
//            System.out.println(instance);
//        }
//
//        //获取唯一的一个元数据
//        ServiceInstance instance = instances.get(0);
//
//        Product product = null;
//
//        //根据元数据中的主机地址和端口号拼接请求微服务的 URL
//        product = restTemplate.getForObject("http://" + instance.getHost() + ":" + instance.getPort() + "/product/1", Product.class);
//
//
//        /**
//         *  调用商品服务(将微服务的请求路径硬编码到 java 代码中)
//         *  存在问题:对微服务调用的负载均衡,加入API网关,配置的统一管理,链路追踪。
//         */
//        //product = restTemplate.getForObject("http://127.0.0.1:9001/product/1", Product.class);
//        return product;
//    }
}


3、浏览器地址栏输入:http://127.0.0.1:9002/order/buy/1

就会有打开较慢的问题。
http://127.0.0.1:9002/order/1

三、高并发问题:使用 jmetter 模拟高负载存在的问题

1、性能小工具 Jmeter 介绍:

Apache JMeter 是 Apache 组织开发的基于 Java 的压力测试工具。
用于对软件做压力测试,它最初被设计用于 Web 应用测试,但后来扩展到其他测试领域。

它可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP服务器,等等。

JMeter 可以用于对服务器、网络或对象模拟巨大的负载,来自不同压力类别下测试它们的强度和分析整体性能。

另外 JMeter 能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。

为了最大限度的灵活性,JMeter 允许使用正则表达式创建断言。

2、下载安装 apache jmeter 工具。

1)apache-jmeter-3.1.zip 下载地址:

https://www.jb51.net/softs/350339.html

2)下载完成,解压到没有中文的任意路径即可。

3、用 apache jmetter 工具 进行测试

1)打开 apache-jmetter 小工具

…\apachejmeter(jb51.net)\bin\jmeter.bat

2)依次点击:

右上角【新建】,【测试计划】。

右键【测试计划】,点击【添加】,【Threads Users】,【线程组】。
线程数:(20)
循环次数:(50)

右键【线程组】,依次点击【添加】,【Sampler】,【HTTP请求】。
服务器名称或IP:(127.0.0.1),端口号:(9002)
协议:(http)方法:(GET)
路径:(/order/buy/1)

右键【HTTP请求】,依次点击:【添加】,【监听器】,【察看结果树】。

设置好后,点击绿色【运行】图标。

在这里插入图片描述

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

4、浏览器地址栏输入:http://127.0.0.1:9002/order/buy/1

浏览器地址栏输入:http://127.0.0.1:9002/order/buy/1

会发现访问两个方法打开都较慢。

四、高并发问题:问题分析

1、tomcat 会以线程池的方式对所有的请求进行统一管理,

这样就会对于某个方法可能存在耗时问题。

2、tomcat 有最大并发设置,我们模拟环境设置了10上限。

3、随着外面请求越来越多,积压的请求数量势必会造成系统的崩溃。

4、高并发问题,解决方案:

为了不影响其他接口(方法)的正常访问,对多个服务之间进行隔离,

隔离方法有:1)线程池隔离,2)信号量隔离(即采用计数器隔离)。

五、高并发问题:线程池隔离的方式处理请求积压问题

1、在 order_service 子工程(子模块)中,修改 pom.xml 配置文件,引入 hystrix 依赖坐标。


<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>spring_cloud_demo</artifactId>
        <groupId>djh.it</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order_service</artifactId>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
            <!--            <version>8.0.23</version>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- 引入 EurekaClient 依赖坐标 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
         <!-- springcloud 整合的openFeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- hystrix -->
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-metrics-event-stream</artifactId>
            <version>1.5.12</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
            <version>1.5.12</version>
        </dependency>
    </dependencies>

</project>
<!-- spring_cloud_demo\order_service\pom.xml -->

2、在 order_service 子工程(子模块)中,创建 数据服务隔离类 OrderCommand.java

/**
 *   spring_cloud_demo\order_service\src\main\java\djh\it\order\command\OrderCommand.java
 *
 *   2024-4-25 数据服务隔离类 OrderCommand.java
 */
package djh.it.order.command;

import com.netflix.hystrix.*;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import djh.it.order.domain.Product;
import org.springframework.web.client.RestTemplate;

public class OrderCommand extends HystrixCommand<Product> {

    private RestTemplate restTemplate;
    private Long id;

    public OrderCommand(RestTemplate restTemplate, Long id) {
        super(setter());
        this.restTemplate = restTemplate;
        this.id = id;
    }

    private static Setter setter() {
        // 服务分组
        HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("order_product");
        // 服务标识
        HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("product");
        // 线程池名称
        HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("order_product_pool");

        /**
         *线程池配置
         withCoreSize :线程池大小为10
         *
         withKeepAliveTimeMinutes:线程存活时间15秒
         withQueueSizeRejectionThreshold:队列等待的阈值为100,超过100执行拒绝策略
         */
        HystrixThreadPoo1Properties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(50)
                .withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100);

        // 命令属性配置Hystrix开启超时
        HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
                //采用线程池方式实现服务隔离
                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                //禁止
                .withExecutionTimeoutEnabled(false);
        return Setter.withGroupKey(groupKey).andCommandKey(commandKey).andThreadPoolKey(threadPoolKey)
                .andThreadPoolPropertiesDefaults(threadPoolProperties).andCommandPropertiesDefaults(commandProperties);
    }
    @Override
    protected Product run() throws Exception{
        System.out.println(Thread.currentThread().getName());
        return restTemplate.getForObject("http://127.0.0.1/product/" + id, Product.class);
    }
    //降级方法
    @Override
    protected Product getFallback(){
        Product product = new Product();
        product.setProductName("不好意思,出错了");
        return product;
    }
}


3、在 order_service 子工程(子模块)中,修改 Controller 类 ,调用 数据服务隔离类 OrderCommand.java 的执行 方法

/**
 *   spring_cloud_demo\order_service\src\main\java\djh\it\order\controller\OrderController.java
 *
 *  2024-4-24 订单的 controller 类 OrderController.java
 */
package djh.it.order.controller;

import djh.it.order.command.OrderCommand;
import djh.it.order.domain.Product;
import djh.it.order.feign.ProductFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

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

    // 注入 restTemplate 对象
    @Autowired
    private RestTemplate restTemplate;

    @Autowired   //注入 feign 组件的接口类
    private ProductFeignClient productFeignClient;

    /**
     *  注入 DiscoveryClient : springcloud 提供的获取元数据的工具类。
     *      调用方法获取服务的元数据信息。
     */
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     *  使用 基于 ribbon 的形式调用远程微服务:
     *  1、使用 @LoadBalanced 注解 声明 RestTemplate
     *  2、使用服务名称 service-product 替换 IP 地址 。
     *
     * @param id
     * @return
     */
    @RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
    public Product findById(@PathVariable Long id){
    	// 调用 数据服务隔离类 OrderCommand.java 的 执行方法
        return new OrderCommand(restTemplate,id).execute();
//        Product product = null;
//        //product = restTemplate.getForObject("http://127.0.0.1:9001/product/1", Product.class);
//        product = restTemplate.getForObject("http://service-product/product/1", Product.class);

//         //通过 Feign 自动的接口调用远程微服务
//        product = productFeignClient.findById(id);

//        return product;
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String findOrder(@PathVariable Long id){
        System.out.println(Thread.currentThread().getName());
        return "添加一个方法,用以模拟高并发环境问题。--- 根据id查询订单";
    }

//    @RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
//    public Product findById(@PathVariable Long id){
//
//        //调用 discoveryClient 方法,已调用服务名称获取所有的元数据。
//        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
//        for (ServiceInstance instance : instances) {
//            System.out.println(instance);
//        }
//
//        //获取唯一的一个元数据
//        ServiceInstance instance = instances.get(0);
//
//        Product product = null;
//
//        //根据元数据中的主机地址和端口号拼接请求微服务的 URL
//        product = restTemplate.getForObject("http://" + instance.getHost() + ":" + instance.getPort() + "/product/1", Product.class);
//
//
//        /**
//         *  调用商品服务(将微服务的请求路径硬编码到 java 代码中)
//         *  存在问题:对微服务调用的负载均衡,加入API网关,配置的统一管理,链路追踪。
//         */
//        //product = restTemplate.getForObject("http://127.0.0.1:9001/product/1", Product.class);
//        return product;
//    }
}


4、再次用 apache-jmetter 小工具进行压力测试,

浏览器地址栏输入:http://127.0.0.1:9002/order/buy/1

浏览器地址栏输入:http://127.0.0.1:9002/order/1

会发现访问第二个方法已经很快了。

六、高并发问题:服务容错的核心知识

1、雪崩效应

1)在微服务架构中,一个请求需要调用多个服务是非常常见的。

如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,

由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。

此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。

服务与服务之间的依赖性,故障会传播,造成连锁反应,
会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

2)雪崩:是系统中的蝴蝶效应导致其发生的原因多种多样,
有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。

从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,
所以我们可以提前评估,做好熔断,隔离,限流。

2、服务隔离

顾名思义,它是指将系统按照一定的原则划分为若干个服务模块,
各个模块之间相对独立,无强依赖。

当有故障发生时,能将问题和影响隔离在某个模块内部,
而不扩散风险,不波及其它模块,不影响整体的系统服务。

3、熔断降级

1)熔断:这一概念来源于电子工程中的断路器(Circuit Breaker)。
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,
上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。
这种牺牲局部,保全整体的措施就叫做熔断。

2)所谓降级:就是当某个服务熔断之后,服务器将不再被调用,
此时客户端可以自己准备一个本地的 fallback 回调,
返回一个缺省值。也可以理解为兜底。

4、服务限流

限流:可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。

一般来说系统的吞吐量是可以被测算的,为了保证系统的稳固运行,
一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
比方:推迟解决,拒绝解决,或者者部分拒绝解决等等。

上一节链接:
# 从浅入深 学习 SpringCloud 微服务架构(六)Feign(2)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

段子手-168

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

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

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

打赏作者

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

抵扣说明:

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

余额充值