从浅入深 学习 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 类
/**
* 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){
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)