在分布式的的环境下,服务之间相互依赖调用,一个服务往往会依赖于其他几个服务,所以,当一个服务不可用时,就会影响到其它服务的正常工作。例如,在抢购系统中,当有大量并发请求调用商品服务,订单服务可能会资源耗尽,无法对外提供服务,并且这种不可用还会影响到其他的服务,就像雪崩一样。
Tomcat的底层使用了线程池技术,并且默认是提供1个线程池的,所有的请求都是在一个线程池中被处理。假设线程池中只有最多创建20个线程,那么当第21个请求来的时候就必须等待,所以会产生阻塞,从而导致服务堆积,影响其他服务的正常使用。
服务隔离就是要减少服务与服务之间的依赖关系,当然这种依赖关系并不是指业务依赖。通过服务隔离,防止服务雪崩效应,最终以服务降级、熔断、限流等手段,提高系统的高可用。
服务隔离的两种方式:线程池、信号量
使用线程池是比较常用的一种方式,它为每一个请求都单独开辟一个线程池处理,与其他请求完全隔离,线程池内部的阻塞不会影响到其他线程池,但是对CPU的开销非常大。
服务熔断类似现实世界中的“保险丝“,当某个异常条件被触发,直接熔断整个服务,而不是一直等到此服务超时。 目的就是为了保护系统的可用。为不是因为一个异常的发生影响到整个系统的运行。例如当电压过大时,保险丝会烧掉,导致不能用电,但是可以防止火灾的发生。
所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。 这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强。目的就是提升用户的体验,防止雪崩效应。例如可以返回一个友好的提示,而不是暴露系统错误给用户。
Hystrix是由Netflix开源的一个延迟和容错库,是SOA/微服务架构中提供服务隔离、熔断、降级机制的工具/框架。Netflix Hystrix是断路器的一种实现,用于高微服务架构的可用性,是防止服务出现雪崩的利器。可以提升系统的可用性与容错性。
案例:搭建一套分布式rpc远程通讯,订单服务调用会员服务以线程池的方式实现服务隔离,防止雪崩效应案例
案例分析:搭建两个SpringBoot项目——hystrix_member(会员服务)、hystrix_order(订单服务)。在订单服务中调用会员服务,会员服务设置休眠时间模拟请求处理时间。order中有三个接口:“/order/orderIndex”(未解决雪崩效应)、“/order/orderIndexHystrix”(解决了雪崩效应)、“/order/findOrderIndex”(用于测试雪崩效应)。
项目结构:
Member服务:
导入依赖
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.apache.httpcomponents
httpclient
com.alibaba
fastjson
1.2.47
com.netflix.hystrix
hystrix-metrics-event-stream
1.5.12
com.netflix.hystrix
hystrix-javanica
1.5.12
MemberController.java
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 会员控制层,供order调用
* @author johson
*
*/
@RestController
@RequestMapping("/member")
public class MemberController {
@GetMapping("/getMember")
public Object getMember() throws InterruptedException{
Map map = new HashMap();
map.put("name", "张三");
map.put("age", 5);
Thread.sleep(1500);
return map;
}
}
order服务
导入依赖
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.apache.httpcomponents
httpclient
com.alibaba
fastjson
1.2.47
com.netflix.hystrix
hystrix-metrics-event-stream
1.5.12
com.netflix.hystrix
hystrix-javanica
1.5.12
application.yml,设置tomcat最大线程数为20
server:
port: 8080
tomcat:
max-threads: 20
HttpClientUtils.java,发送http请求工具类,超时时间可自行设置
/**
* HttpClient4.3工具类
* @author johson
*/
public class HttpClientUtils {
private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class); // 日志记录
private static RequestConfig requestConfig = null;
static {
// 设置请求和传输超时时间
requestConfig = RequestConfig.custom().setSocketTimeout(20000).setConnectTimeout(20000).build();
}
/**
* 发送get请求
*/
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;
}
}
OrderService.java,在这里调用Member服务
/**
* 在这里通过rpc通信调用会员服务
* @author johson
*
*/
@Service
public class OrderService {
public JSONObject getMember(){
JSONObject result = HttpClientUtils.httpGet("http://127.0.0.1:8081/member/getMember");
return result;
}
}
OrderHystrixCommand .java
/**
* 使用Hystrix解决雪崩效应
* @author johson
*
*/
public class OrderHystrixCommand extends HystrixCommand {
@Autowired
private OrderService orderService;
/**
* @param group
*/
public OrderHystrixCommand(OrderService orderService) {
super(setter());
this.orderService = orderService;
}
protected JSONObject run() throws Exception {
JSONObject member = orderService.getMember();
System.out.println("当前线程名称:" + Thread.currentThread().getName() + ",订单服务调用会员服务:member:" + member);
return member;
}
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执行拒绝策略
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);
}
/**
* 发生熔断,则fallback
*/
@Override
protected JSONObject getFallback() {
// 如果Hystrix发生熔断,当前服务不可用,直接执行Fallback方法
System.out.println("系统错误!");
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 500);
jsonObject.put("msg", "系统错误!");
return jsonObject;
}
}
OrderController.java
/**
* 订单控制层,用于测试Hystrix解决雪崩效应
* @author johson
*
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 未解决雪崩效应
*/
@RequestMapping("/orderIndex")
public Object orderIndex(){
JSONObject member = orderService.getMember();
System.out.println("当前线程名称为:"+Thread.currentThread()
.getName()+",订单服务调用会员服务:member="+member);
return member;
}
/**
* 解决雪崩效应
*/
@RequestMapping("/orderIndexHystrix")
public Object orderIndexHystrix(){
return new OrderHystrixCommand(orderService).execute();
}
/**
* 用于测试雪崩效应
*/
@RequestMapping("/findOrderIndex")
public Object findOrderIndex(){
System.out.println("当前线程为:"+Thread.currentThread().getName()+",findOrderIndex");
return "findOrderIndex";
}
}
测试工具:jmeter
测试方法:分别向“/order/orderIndex”(未解决雪崩效应)、“/order/orderIndexHystrix”(解决了雪崩效应)发起大量http请求,在浏览器中访问“/order/findOrderIndex”(用于测试雪崩效应)接口,观察响应时间。
测试结果:向“/order/orderIndex”发起大量请求时,在浏览器中访问“/order/findOrderIndex”响应时间很长;
向“/order/orderIndexHystrix”发起大量请求时,“/order/findOrderIndex”响应时间短,不受影响。
PS:分别在浏览器中访问这三个接口,对比控制台输出的线程名。可以发现“/order/orderIndex”与“/order/findOrderIndex”在同一个线程池处理,而“/order/orderIndexHystrix”在单独的线程池处理。