當大多數人在使用Tomcat時,多個HTTP服務會共享一個線程池,假設其中一個HTTP服務訪問的數據庫響應非常慢,這將造成服務響應時間延遲增加,大多數線程阻塞等待數據響應返回,導致整個Tomcat線程池都被該服務占用,甚至拖垮整個Tomcat。因此,如果我們能把不同HTTP服務隔離到不同的線程池,則某個HTTP服務的線程池滿了也不會對其他服務造成災難性故障。這就需要線程隔離或者信號量隔離來實現了。
使用線程隔離或信號隔離的目的是為不同的服務分配一定的資源,當自己的資源用完,直接返回失敗而不是占用別人的資源。
Hystrix實現服務隔離兩種方案
Hystrix的資源隔離策略有兩種,分別為:線程池和信號量。
線程池方式
1、 使用線程池隔離可以完全隔離第三方應用,請求線程可以快速放回。 2、 請求線程可以繼續接受新的請求,如果出現問題線程池隔離是獨立的不會影響其他應用。
3、 當失敗的應用再次變得可用時,線程池將清理並可立即恢復,而不需要一個長時間的恢復。
4、 獨立的線程池提高了並發性
缺點:
線程池隔離的主要缺點是它們增加計算開銷(CPU)。每個命令的執行涉及到排隊、調度和上 下文切換都是在一個單獨的線程上運行的。
每個服務接口都有自己獨立的線程池,管理運行當前自己的接口。但是cpu開銷比較大
在同一個線程池中,素有請求全部到一個服務進行訪問,這時候會導致其他服服務沒有線程接收請求訪問,所以就會產生服務雪崩效應
Member:
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itmayeidu</groupId> <artifactId>member</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> </dependencies> </project>
controller
package com.toov5.controller; import java.util.HashMap; import java.util.Map; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @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; } }
啟動類
package com.toov5.controller; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AppMember { public static void main(String[] args) { SpringApplication.run(AppMember.class, args); } }
yml:
server: port: 8081
Order
pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itmayeidu</groupId> <artifactId>order</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <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> </dependencies> </project>
controller
package com.toov5.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSONObject; import com.toov5.hystrix.OrderHystrixCommand; import com.toov5.hystrix.OrderHystrixCommand2; import com.toov5.service.MemberService; @RestController @RequestMapping("/order") public class OrderController { @Autowired private MemberService memberService; @RequestMapping("/orderIndex") public Object orderIndex() throws InterruptedException { JSONObject member = memberService.getMember(); //返回值與OrderHystrixCommand中的泛型對應 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"; } }
service
package com.toov5.service; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSONObject; import com.toov5.utils.HttpClientUtils; @Service public class MemberService { public JSONObject getMember() { JSONObject result = HttpClientUtils.httpGet("http://127.0.0.1:8081/member/memberIndex"); return result; } }
utils
package com.toov5.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工具類 * * @author hang.luo */ 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; } }
Hystrix:
package com.toov5.hystrix; import org.springframework.beans.factory.annotation.Autowired; import com.alibaba.fastjson.JSONObject; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.toov5.service.MemberService; @SuppressWarnings("rawtypes") 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; } private static Setter setter() { // 服務分組 相同的會員是一組 HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("members"); // 服務標識 HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("member"); // 線程池名稱 HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("member-pool"); // 線程池配置 線程池大小為10,線程存活時間15秒 隊列等待的閾值為100,超過100執行拒絕策略 HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(10) .withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100); // 命令屬性配置Hystrix 開啟超時 HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter() // 采用線程池方式實現服務隔離 .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) // 禁止 .withExecutionTimeoutEnabled(true); 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; } }
啟動類:
package com.toov5; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AppOrder { public static void main(String[] args) { SpringApplication.run(AppOrder.class, args); } }
yml:
server: port: 8080 tomcat: max-threads: 20
效果:
可以看到(開啟超時),不同的線程池,服務隔離,服務降級。