微服務:SpringCloud 高併發出現的問題以及解決辦法(一篇文章給你安排的明明白白的!!!)

之前的做文章講述了一些常用的組件,這次我們來聊聊基於上面組件的高併發問題。我們先以高併發時,項目程序出現的現象入手。

一、修改項目

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;
    }

重新啓動就可以了。再次測試,訪問接口時間

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章