之前的做文章講述了一些常用的組件,這次我們來聊聊基於上面組件的高併發問題。我們先以高併發時,項目程序出現的現象入手。
一、修改項目
order-service
這裏面我們不採用feign,而是採用普通的http請求的方式,用restTemplate。這裏有兩個接口。
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ProductFeginClient productFeginClient;
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
// Product product = productFeginClient.findById(id);
Product product = restTemplate.getForObject("http://localhost:9001/product/1",Product.class);
return product;
}
@RequestMapping(value = "/order", method = RequestMethod.GET)
public String findByIdHei() {
return "hehhe";
}
修改配置文件(設置最大線程數):
product-service
利用線程模擬一個耗時的操作。
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = productService.findById(id);
product.setProductName("訪問的服務地址:"+ip + ":" + port);
return product;
}
不要糾結端口了好吧,一定是能夠調用的。
我們來訪問一下這兩個端口。6.38s,證明耗時操作是可以得。
293ms,訪問很快。
二、搞一個壓力測試工具
下載一個jmeter
配置線程組(每次20個線程訪問,循環50個)
配置請求得地址
啓動測試
啓動之後訪問/order/order接口。訪問變成了6s多。這就是高併發出現的問題,影響了其他接口的正常訪問
三、問題分析
我們看這張圖,如果用戶很多的話,併發量很高,我們的tomcat設置最大併發數是10。tomcat底層處理請求是一個線程池(要知道),最大線程數就是10,其餘的請求多餘,都會進入一個阻塞的有界隊列中,之後等某個線程空閒,再從隊列中取出,處理。
一個接口高併發的時候,我們發現另一個接口會變慢,主要原因就是起初所有的線程資源都被上一個接口占用。導致訪問下面接口需要進入隊列。如果併發量特別巨大的時候,可能會導致我們的程序崩潰或者服務器癱瘓。
如何解決?
有一種方案是,給兩個接口都設立一個線程池,分別處理對應的接口請求。
四、實現用線程池隔離
引入依賴:
<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>
引入新類。這個就是我們單獨配置的線程隔離。設置的線程數5很重要。(因爲tomcat一共有10個,給了其中最大5個,剩餘的自然是下一個接口的了)
package com.springcloud.demo.command;
import com.netflix.hystrix.*;
import com.springcloud.demo.entity.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(5)
.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 {
return restTemplate.getForObject("http://localhost:9001/product/"+id, Product.class);
}
/**
* 降級方法!!
*/
@Override
protected Product getFallback(){
Product product = new Product();
product.setProductName("不好意思,出錯了!!!");
return product;
}
}
修改接口方法。
@RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
// Product product = productFeginClient.findById(id);
// Product product = restTemplate.getForObject("http://localhost:9001/product/1",Product.class);
Product product = new OrderCommand(restTemplate,id).execute();
return product;
}
重新啓動就可以了。再次測試,訪問接口時間