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

news/2024/9/22 9:52:15/

从浅入深 学习 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.ymlserver:port: 9002 #端口
#  port: ${port:9002}  # 启动端口设置为动态传参,如果未传参数,默认端口为 9002tomcat: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/Shanghaiurl: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8username: 'root'password: '012311'application:name: service-order #服务名称jpa:database: MySQLshow-sql: trueopen-in-view: trueeureka:  # 配置 Eurekaclient: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 对象@Autowiredprivate RestTemplate restTemplate;@Autowired   //注入 feign 组件的接口类private ProductFeignClient productFeignClient;/***  注入 DiscoveryClient : springcloud 提供的获取元数据的工具类。*      调用方法获取服务的元数据信息。*/@Autowiredprivate 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);}@Overrideprotected Product run() throws Exception{System.out.println(Thread.currentThread().getName());return restTemplate.getForObject("http://127.0.0.1/product/" + id, Product.class);}//降级方法@Overrideprotected Product getFallback(){Product product = new Product();product.setProductName("不好意思,出错了");return product;}
}

3、在 order_service 子工程(子模块)中,修改 Controller 类

/***   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 对象@Autowiredprivate RestTemplate restTemplate;@Autowired   //注入 feign 组件的接口类private ProductFeignClient productFeignClient;/***  注入 DiscoveryClient : springcloud 提供的获取元数据的工具类。*      调用方法获取服务的元数据信息。*/@Autowiredprivate 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){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)


http://www.ppmy.cn/news/1442518.html

相关文章

【论文笔记】基于预训练模型的持续学习(Continual Learning)(增量学习,Incremental Learning)

论文链接&#xff1a;Continual Learning with Pre-Trained Models: A Survey 代码链接&#xff1a;Github: LAMDA-PILOT 持续学习&#xff08;Continual Learning, CL&#xff09;旨在使模型在学习新知识的同时能够保留原来的知识信息了&#xff0c;然而现实任务中&#xff…

Java23种设计模式-行为型模式之命令模式

命令模式&#xff08;Command Pattern&#xff09;&#xff1a;将一个请求封装为一个对象&#xff0c;从而允许用户使用不同的请求、队列或日志请求来参数化其他对象。命令模式也支持可撤销的操作。它通常用于解耦执行操作的对象与知道如何实现操作的对象。 基本组成&#xff…

uniapp问题归类

最近使用uniapp中&#xff0c;遇到了一些问题&#xff0c;这边mark下。 1. 启动页变形 设置启动页的时候发现在部分android手机上启动页被拉伸了&#xff0c;最后看了下官方建议使用9.png图 生成9.png地址&#xff0c;推荐图片大小为1080x2340 uniapp推荐官方地址传送门 我…

唯品会关键字搜索API接口:技术解析与应用实践

唯品会关键字搜索API接口深度解析&#xff1a;探索电商搜索新境界 在电商领域&#xff0c;搜索功能无疑是连接用户与商品的重要桥梁。唯品会作为国内知名的特卖电商平台&#xff0c;其关键字搜索API接口为开发者提供了高效、精准的搜索能力&#xff0c;助力商家提升用户体验、…

在VSCode中配置多个版本的Python环境,并设置PYTHONHOME环境变量

在VSCode中配置多个版本的Python环境&#xff0c;并设置PYTHONHOME环境变量&#xff0c;可以通过以下步骤进行&#xff1a; 安装多个版本的Python 首先&#xff0c;你需要在你的计算机上安装多个版本的Python。你可以从Python的官方网站下载不同版本的Python安装包&#xff0…

理解ROS2的动作

​ 1. 创建一个动作 目标&#xff1a; 在ROS 2软件包中定义一个动作。 1.1 新建包 设置一个 workspace 并创建一个名为 action_tutorials_interfaces 的包&#xff1a; mkdir -p ros2_ws/src #you can reuse existing workspace with this naming convention cd ros2_ws/s…

HarmonyOS ArkUI实战开发-NAPI 加载原理(下)

上一节笔者给大家讲解了 JS 引擎解释执行到 import 语句的加载流程&#xff0c;总结起来就是利用 dlopen() 方法的加载特性向 NativeModuleManager 内部的链接尾部添加一个 NativeModule&#xff0c;没有阅读过上节文章的小伙伴&#xff0c;笔者强烈建议阅读一下&#xff0c;本…

JAVA实操代码练习2

JAVA实操代码练习2 代码合集01(两个数的最小公倍数;n 个自然数的立方和;抽奖程序)代码合集02(纸张对折、5的阶乘、鸡鸡同笼、大小马匹、回文数、跳跃弹球)代码合集01(两个数的最小公倍数;n 个自然数的立方和;抽奖程序) import java.util.Scanner;public class lianxi0…