互聯網高併發解決方案-基於Hystrix實現服務隔離與降級

 

Hystrix簡介

使用Hystrix實現服務隔離

Hystrix 是一個微服務關於服務保護的框架,是Netflix開源的一款針對分佈式系統的延遲和容錯解決框架,目的是用來隔離分佈式服務故障。它提供線程和信號量隔離,以減少不同服務之間資源競爭帶來的相互影響;提供優雅降級機制;提供熔斷機制使得服務可以快速失敗,而不是一直阻塞等待服務響應,並能從中快速恢復。Hystrix通過這些機制來阻止級聯失敗並保證系統彈性、可用。

什麼是服務隔離

當大多數人在使用Tomcat時,多個HTTP服務會共享一個線程池,假設其中一個HTTP服務訪問的數據庫響應非常慢,這將造成服務響應時間延遲增加,大多數線程阻塞等待數據響應返回,導致整個Tomcat線程池都被該服務佔用,甚至拖垮整個Tomcat。因此,如果我們能把不同HTTP服務隔離到不同的線程池,則某個HTTP服務的線程池滿了也不會對其他服務造成災難性故障。這就需要線程隔離或者信號量隔離來實現了。

使用線程隔離或信號隔離的目的是爲不同的服務分配一定的資源,當自己的資源用完,直接返回失敗而不是佔用別人的資源。

Hystrix實現服務隔離兩種方案

Hystrix的資源隔離策略有兩種,分別爲:線程池和信號量。

1、線程池方式

1、 使用線程池隔離可以完全隔離第三方應用,請求線程可以快速放回。 2、 請求線程可以繼續接受新的請求,如果出現問題線程池隔離是獨立的不會影響其他應用。 
3、 當失敗的應用再次變得可用時,線程池將清理並可立即恢復,而不需要一個長時間的恢復。 
4、 獨立的線程池提高了併發性

缺點: 
線程池隔離的主要缺點是它們增加計算開銷(CPU)。每個命令的執行涉及到排隊、調度和上 下文切換都是在一個單獨的線程上運行的。

 

場景:客戶端請求 訂單服務,然後訂單服務請求會員服務,此時如果,會員服務處理一個請求需要2秒的話,那麼當大量訂單服務調用會員服務的時候,會導致 訂單服務堆積雪崩,響應很慢,也將導致 會員服務不可用。直到會員服務處理一部分訂單服務請求,給出空閒的線程纔會讓 服務繼續新的響應。  

實現目標:當大量訂單服務調用會員服務的時候,都在等待響應時,如果此時發起另外一個請求,直接調用 訂單服務,那麼訂單服務此時要立刻響應結果,而不是等 處理完部分訂單服務後,纔給出響應結果

實現方式:pom 引入

<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>
public class OrderHystrixCommand extends HystrixCommand<JSONObject> {
	@Autowired
	private MemberService memberService;

	/**
	 * @param group
	 */
	public OrderHystrixCommand(MemberService memberService) {
		super(setter());
		this.memberService = memberService;
	}

	/**
	 * 表示服務執行的代碼
	 */
	protected JSONObject run() throws Exception {
		JSONObject member = memberService.getMember();
		System.out.println("當前線程名稱:" + Thread.currentThread().getName() + ",訂單服務調用會員服務:member:" + member);
		return member;
	}

	/**
	 * @Description 配置服務隔離
	 */
	private static Setter setter() {

		// 服務分組
		HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("orders");
		// 服務標識
		HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("order");
		// 線程池名稱  保證每個服務有自己獨立的線程池
		HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("order-pool");
		// #####################################################
		// 線程池配置 線程池大小爲10,線程存活時間15秒   隊列等待的閾值爲100,超過100執行拒絕策略  配置服務熔斷  最多同時處理110個請求
		HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(10)
				.withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100);
		// ########################################################
		// 命令屬性配置Hystrix 開啓超時
		HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
				// 採用線程池方式實現服務隔離
				.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
				// 禁止
				.withExecutionTimeoutEnabled(false);
		return HystrixCommand.Setter.withGroupKey(groupKey).andCommandKey(commandKey).andThreadPoolKey(threadPoolKey)
				.andThreadPoolPropertiesDefaults(threadPoolProperties).andCommandPropertiesDefaults(commandProperties);

	}

	/**
	 *配置服務降級
	 */
	@Override
	protected JSONObject getFallback() {
		// 如果Hystrix發生熔斷,當前服務不可用,直接執行Fallback方法
		System.out.println("系統錯誤!");
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("code", 500);
		jsonObject.put("msg", "系統錯誤!");
		return jsonObject;
	}
	 
}

 

@RestController
@RequestMapping("/order")
public class OrderController {
	@Autowired
	private MemberService memberService;

	@RequestMapping("/orderIndex")
	public Object orderIndex() throws InterruptedException {
		JSONObject member = memberService.getMember();
		System.out.println("當前線程名稱:" + Thread.currentThread().getName() + ",訂單服務調用會員服務:member:" + member);
		return member;
	}

	//  
	@RequestMapping("/orderIndexHystrix")
	public Object orderIndexHystrix() throws InterruptedException {
		return new OrderHystrixCommand(memberService).execute();
	}


	@RequestMapping("/findOrderIndex")
	public Object findIndex() {
		System.out.println("當前線程:" + Thread.currentThread().getName() + ",findOrderIndex");
		return "findOrderIndex";
	}
}

使用httpclient模擬遠程調用 

@Service
public class MemberService {

	public JSONObject getMember() {

		JSONObject result = HttpClientUtils.httpGet("http://127.0.0.1:8081/member/memberIndex");
		return result;
	}

}

 下面的controller是另起一個會員服務,端口號不一致的。

@RestController
@RequestMapping("/member")
public class MemberController {

	@RequestMapping("/memberIndex")
	public Object memberIndex() throws InterruptedException {
		Map<String, Object> hashMap = new HashMap<String, Object>();
		hashMap.put("code", 200);
		hashMap.put("msg", "memberIndex");
		Thread.sleep(1500);
		return hashMap;
	}

}

然後使用 apache-jmeter 模擬測試

1、大量請求訪問  order/orderIndex請求,同時在 瀏覽器中訪問 /order/findOrderIndex 請求,會發現,需要瀏覽器一直轉圈圈,不會立即返回結果。需要等待處理前面的大量請求後,才返回結果

2、大量請求訪問  order/orderIndexHystrix 請求,同時在 瀏覽器中訪問 /order/findOrderIndex 請求,會發現,直接返回結果。而不是需要等待處理前面的大量請求後,才返回結果

package com.itmayiedu.utils;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * HttpClient4.3工具類
 * 
 */
public class HttpClientUtils {
	private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class); // 日誌記錄

	private static RequestConfig requestConfig = null;

	static {
		// 設置請求和傳輸超時時間
		requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();
	}

	/**
	 * post請求傳輸json參數
	 * 
	 * @param url
	 *            url地址
	 * @param json
	 *            參數
	 * @return
	 */
	public static JSONObject httpPost(String url, JSONObject jsonParam) {
		// post請求返回結果
		CloseableHttpClient httpClient = HttpClients.createDefault();
		JSONObject jsonResult = null;
		HttpPost httpPost = new HttpPost(url);
		// 設置請求和傳輸超時時間
		httpPost.setConfig(requestConfig);
		try {
			if (null != jsonParam) {
				// 解決中文亂碼問題
				StringEntity entity = new StringEntity(jsonParam.toString(), "utf-8");
				entity.setContentEncoding("UTF-8");
				entity.setContentType("application/json");
				httpPost.setEntity(entity);
			}
			CloseableHttpResponse result = httpClient.execute(httpPost);
			// 請求發送成功,並得到響應
			if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				String str = "";
				try {
					// 讀取服務器返回過來的json字符串數據
					str = EntityUtils.toString(result.getEntity(), "utf-8");
					// 把json字符串轉換成json對象
					jsonResult = JSONObject.parseObject(str);
				} catch (Exception e) {
					logger.error("post請求提交失敗:" + url, e);
				}
			}
		} catch (IOException e) {
			logger.error("post請求提交失敗:" + url, e);
		} finally {
			httpPost.releaseConnection();
		}
		return jsonResult;
	}

	/**
	 * post請求傳輸String參數 例如:name=Jack&sex=1&type=2
	 * Content-type:application/x-www-form-urlencoded
	 * 
	 * @param url
	 *            url地址
	 * @param strParam
	 *            參數
	 * @return
	 */
	public static JSONObject httpPost(String url, String strParam) {
		// post請求返回結果
		CloseableHttpClient httpClient = HttpClients.createDefault();
		JSONObject jsonResult = null;
		HttpPost httpPost = new HttpPost(url);
		httpPost.setConfig(requestConfig);
		try {
			if (null != strParam) {
				// 解決中文亂碼問題
				StringEntity entity = new StringEntity(strParam, "utf-8");
				entity.setContentEncoding("UTF-8");
				entity.setContentType("application/x-www-form-urlencoded");
				httpPost.setEntity(entity);
			}
			CloseableHttpResponse result = httpClient.execute(httpPost);
			// 請求發送成功,並得到響應
			if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				String str = "";
				try {
					// 讀取服務器返回過來的json字符串數據
					str = EntityUtils.toString(result.getEntity(), "utf-8");
					// 把json字符串轉換成json對象
					jsonResult = JSONObject.parseObject(str);
				} catch (Exception e) {
					logger.error("post請求提交失敗:" + url, e);
				}
			}
		} catch (IOException e) {
			logger.error("post請求提交失敗:" + url, e);
		} finally {
			httpPost.releaseConnection();
		}
		return jsonResult;
	}

	/**
	 * 發送get請求
	 * 
	 * @param url
	 *            路徑
	 * @return
	 */
	public static JSONObject httpGet(String url) {
		// get請求返回結果
		JSONObject jsonResult = null;
		CloseableHttpClient client = HttpClients.createDefault();
		// 發送get請求
		HttpGet request = new HttpGet(url);
		request.setConfig(requestConfig);
		try {
			CloseableHttpResponse response = client.execute(request);

			// 請求發送成功,並得到響應
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				// 讀取服務器返回過來的json字符串數據
				HttpEntity entity = response.getEntity();
				String strResult = EntityUtils.toString(entity, "utf-8");
				// 把json字符串轉換成json對象
				jsonResult = JSONObject.parseObject(strResult);
			} else {
				logger.error("get請求提交失敗:" + url);
			}
		} catch (IOException e) {
			logger.error("get請求提交失敗:" + url, e);
		} finally {
			request.releaseConnection();
		}
		return jsonResult;
	}

}

2、第二種方式是 信號量方式 (計數器)

不是很常用,

講前面的 setter 方法修改下就可以了

private static Setter setter() {
		// 服務分組
		HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("members");
		// 命令屬性配置 採用信號量模式
		HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
				.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
				// 使用一個原子計數器(或信號量)來記錄當前有多少個線程在運行,當請求進來時先判斷計數
				// 器的數值,若超過設置的最大線程個數則拒絕該請求,若不超過則通行,這時候計數器+1,請求返 回成功後計數器-1。
				.withExecutionIsolationSemaphoreMaxConcurrentRequests(50);
		return HystrixCommand.Setter.withGroupKey(groupKey).andCommandPropertiesDefaults(commandProperties);
	}

 

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