1 准備環境
1.1 准備商品微服務和訂單微服務
- 其中商品微服務的findById()方法設置休眠2秒,用來模擬網絡波動等情況:
package com.sunxiaping.product.controller;
import com.sunxiaping.product.domain.Product;
import com.sunxiaping.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/product")
public class ProductController {
@Autowired
private ProductService productService;
@Value("${server.port}")
private String port;
@Value("${spring.cloud.client.ip-address}")
private String ip;
@PostMapping(value = "/save")
public String save(@RequestBody Product product) {
productService.save(product);
return "新增成功";
}
@GetMapping(value = "/findById/{id}")
public Product findById(@PathVariable(value = "id") Long id) {
try {
//休眠2秒,用來模擬 網絡波動等情況
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = productService.findById(id);
product.setProductName("訪問的地址是:" + ip + ":" + port);
return product;
}
}
- 設置訂單微服務的Tomcat的最大線程數是10:
server:
port: 9002 # 微服務的端口號
tomcat:
max-threads: 10 # 最大線程數是10
spring:
application:
name: service-order # 微服務的名稱
datasource:
url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
jpa:
generate-ddl: true
show-sql: true
open-in-view: true
database: mysql
jmx:
unique-names: true
# 配置Eureka
eureka:
instance:
# 實例的名稱
instance-id: service-order:9002
# 顯示IP信息
prefer-ip-address: true
lease-renewal-interval-in-seconds: 5 # 發送心跳續約間隔(默認30秒)
lease-expiration-duration-in-seconds: 10 # Eureka Client發送心跳給Eureka Server端后,續約到期時間(默認90秒)
client:
healthcheck:
enabled: true
service-url: # Eureka Server的地址
# defaultZone: http://localhost:9000/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
# Ribbon的重試機制
service-product:
ribbon:
# 修改ribbon的負載均衡策略 服務名 - ribbon - NFLoadBalancerRuleClassName :負載均衡策略
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的負載均衡策略為權重策略
# Ribbon的重試機制參數
ConnectTimeout: 250 # Ribbon的連接超時時間
ReadTimeout: 1000 # Ribbon的數據讀取超時時間
OkToRetryOnAllOperations: true # 是否對所有操作都進行重試
MaxAutoRetriesNextServer: 50 # 切換實例的重試次數
MaxAutoRetries: 1 # 對當前實例的重試次數
# 微服務info內容詳細信息
info:
app.name: xxx
company.name: xxx
build.artifactId: $project.artifactId$
build.version: $project.version$
# 開啟日志debug
logging:
level:
root: info
- 訂單微服務中的SpringConfig.java
package com.sunxiaping.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class SpringConfig {
@Bean
// @LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 訂單微服務的OrderController.java
package com.sunxiaping.order.controller;
import com.sunxiaping.order.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
/**
* @param id
* @return
*/
@GetMapping(value = "/buy/{id}")
public Product buy(@PathVariable(value = "id") Long id) {
Product product = restTemplate.getForObject("http://localhost:9001/product/findById/" + id, Product.class);
return product;
}
@GetMapping(value = "/findOrder")
public String findOrder() {
return "商品查詢到了";
}
}
2 使用Jmeter測試接口
- 使用JMeter性能測試工具以50個線程每個線程循環50次測試:http://localhost:9002/order/buy/1接口,然后通過瀏覽器調用http://localhost:9002/order/findOrder接口,發現特別慢。
3 系統負載過高存在的問題
3.1 問題分析
-
在微服務架構中,我們將業務拆成一個個的服務,服務和服務之間可以相互調用,由於網絡原因或者自身的原因,服務並不能保證100%可用,如果單個服務出現問題,調用這個服務就會出現網絡延遲,此時如果有大量的網絡請求涌入,會形成任務累計,導致服務癱瘓。
-
換句話說,Tomcat等容器會以線程池的方式對所有的請求進行統一的管理,如果某個方法可能存着耗時問題,隨着外面積壓的請求越來越多,勢必會造成系統的崩潰、癱瘓等。
-
為了不影響其他接口的正常訪問:對多個服務之間進行隔離。
-
服務隔離的方式:
- 1️⃣線程池隔離。
- 2️⃣信號量隔離(計數器,就是對某個方法進行設置閾值,如果超過了閾值,直接報錯)。
4 線程池隔離的方式處理積壓問題
4.1 在訂單微服務中引入相關jar包的Maven坐標
<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>
4.2 配置線程池
- 配置HystrixCommand接口的實現類,在實現類中可以對線程池進行配置:
package com.sunxiaping.order.command;
import com.netflix.hystrix.*;
import com.sunxiaping.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執行拒絕策略
*/
HystrixThreadPoolProperties.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://localhost:9001/product/findById/" + id, Product.class);
}
/**
* 服務降級
*
* @return
*/
@Override
protected Product getFallback() {
Product product = new Product();
product.setProductName("不好意思,出錯了");
return product;
}
}
4.3 修改Controller
package com.sunxiaping.order.controller;
import com.sunxiaping.order.command.OrderCommand;
import com.sunxiaping.order.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
/**
* 使用OrderCommand調用遠程遠程服務
*
* @param id
* @return
*/
@GetMapping(value = "/buy/{id}")
public Product buy(@PathVariable(value = "id") Long id) {
return new OrderCommand(restTemplate, id).execute();
}
@GetMapping(value = "/findOrder")
public String findOrder() {
System.out.println(Thread.currentThread().getName());
return "商品查詢到了";
}
}