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