用例spring_cloud_hystrix 項目地址:傳送門
通過注冊中心已經實現了微服務的服務注冊和服務發現,並且通過Ribbon實現了負載均衡,已經借助Feign可以優雅的進行微服務調用。那么我們編寫的微服務的性能怎么樣呢,是否存在問題呢?
一、測試工程准備
注意:我們只使用order_service作為我們這章的教程用例。
1、新建一個微服務,集成前面幾章的訂單服務模塊,以及修改訂單模塊的服務端口如下:


2、商品服務模塊
修改商品的controller的請求findById方法,添加線程延遲返回的代碼
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
//線程睡眠,模擬請求反饋延遲
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = productService.findById(id);
return product;
}
}
修改application.yml文件,添加tomcat配置,控制線程訪問線程最大個數
server:
port: 9002 #端口
tomcat: #控制容器最大接收線程-壓力測試
max-threads: 10
3、order_service的 Controller類中添加壓力測試代碼
@RestController @RequestMapping("/order") public class OrderController { /** * 壓力測試用 * @param id * @return */ @RequestMapping(value = "/{id}",method = RequestMethod.GET) public String findOrder(@PathVariable Long id) { System.out.println(Thread.currentThread().getName()); return "壓力測試-根據id查詢訂單"+id; } }
4、啟動測試

二、性能工具Jmetter
為了模擬服務在高並發時出現的延遲問題,我們使用Jmeter工具進行壓力測試

Apache JMeter 是Apache組織開發的基於Java的壓力測試工具。用於對軟件做壓力測試,它最初被設計用於Web應用測試,但后來擴展到其他測試領域。 它可以用於測試靜態和動態資源,例如靜態文件、Java 小服務程序、CGI 腳本、Java 對象、數據庫、FTP 服務器, 等等。JMeter 可以用於對服務器、網絡或對象模擬巨大的負載,來自不同壓力類別下測試它們的強度和分析整體性能。另外JMeter能夠對應用程序做功能/回歸測試,通過創建帶有斷言的腳本來驗證你的程序返回了你期望的結果。為了最大限度的靈活性,JMeter允許使用正則表達式創建斷言。
1、安裝Jmetter
官網下載地址: http://jmeter.apache.org/download_jmeter.cgi
Jmetter安裝十分簡單,下載最新安裝包,解壓找到安裝目錄下的bin/jmeter.bat 以管理員身份啟動即可,如下:

2、 配置Jmetter參數
(1)創建新的測試計划

( 2)測試計划下創建發起請求的線程組

線程組包含很多屬性,目前我們只關注線程屬性那一塊。
-
線程數:代表訪問的並發數,默認是1。
-
Ramp-UpPeriod:表示多長時間內容啟動所有線程,如果時間很短,會造成網站的瞬間高並發,默認值是1秒。
-
循環次數:表示執行多少次,默認值為1,表示執行一次結束,這里可以勾選永遠,讓其一直運行下去。
(3)創建http請求模板


(4)添加監聽器(結果樹)

用於展示壓力測試中http請求的情況,如下:

(5)壓力測試過程
操作過程
-
做完上面的操作后,啟動壓力測試。此時壓力測試20個http請求 http://localhost:9002/order/buy/1 地址
-
瀏覽器訪問訂單服務的另一個http: http://localhost:9002/order/2
出現問題
瀏覽器訪問訂單服務的另一個http: http://localhost:9002/order/2 時,加載了一段時間,沒有馬上返回。

三、系統負載過高存在的問題
1、問題分析
在微服務架構中,我們將業務拆分成一個個的服務,服務與服務之間可以相互調用,由於網絡原因或者 自身的原因,服務並不能保證服務的100%可用,如果單個服務出現問題,調用這個服務就會出現網絡延遲,此時若有大量的網絡涌入,會形成任務累計,導致服務癱瘓。
在SpringBoot程序中,默認使用內置tomcat作為web服務器。單tomcat支持最大的並發請求是有限的,如果某一接口阻塞,待執行的任務積壓越來越多,那么勢必會影響其他接口的調用。

如上圖所示,我們的壓力測試訂單服務中。我們設置了order_service的訂單服的tomcat容器最大可接收10個線程。如果超過10個就是出現其他請求等待的現象(我們通過在商品服務中線程睡眠2s實現這個現象)。因此當壓力測試的http請求20個線程都去訪問【下單方法】造成了tomcat線程超過10個就要等待。后面用瀏覽器去訪問【查詢訂單方法】時tomcat線程已經達到10個最大值,因此瀏覽器一直顯示加載中。
2、問題解決
在高並發訪問某個接口時造成線程排隊等待時,為了不影響其他接口的正常訪問,我們可以采用對服務多個接口之間進行隔離。
-
線程池隔離(推薦):以上圖例子說明,為高並發的接口方法分別都配置線程池,這樣【下單方法】接口可以把等待的線程放入此方法對應的線程池中就不會影響其他接口的調用。其他接口請求就不會收影響。
-
信號量隔離,計數器:為存在高並發的接口方法配置一個“最大閾值”,當請求這個接口方法的請求數超過這個閾值,則直接報錯。
3、線程池的形式實現服務隔離
(1) 配置坐標
為了方便實現線以線程池的形式完成資源隔離,需要引入如下hystrix依賴
<!--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>
(2) 配置線程池
配置HystrixCommand接口的實現類,再實現類中可以對線程池進行配置
public class OrderCommand extends //調用商品服務方法Command<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://127.0.0.1:9001/product/"+id, Product.class); } /** * 降級方法 */ @Override protected Product getFallback(){ Product product = new Product(); product.setProductName("熔斷降級:不好意思,出錯了"); return product; } }
可以看到,線程池隔離代碼實現起來有些復雜麻煩,后面會介紹Hystrix來簡單的實現實現此功能。這里只做測試考用。
(3) 配置調用
修改 OrderController ,使用自定義的OrderCommand完成調用
@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
Product product =new OrderCommand(restTemplate,id).execute();
//Product product = restTemplate.getForObject("http://localhost:9001/product/"+id,Product.class);
return product;
}
(4)重新進行壓力測試
