Hystrix
1,Hyxtrix 是什么?
Netflix的一套开源框架,可以理解为高并发的一套解决方案。可以提供服务隔离,服务熔断,服务降级。
2,服务隔离
介绍服务隔离前,需要先了解下,服务雪崩效应。
服务雪崩效应指的是:因为一个服务产生了阻塞堆积(可能有大量的访问请求),而导致其他的服务不可用。
雪崩效应产生的原因:在一个服务中,Tomcat 默认的线程池是1个,比如最大线程数目是50 个,阻塞队列里面的线程数据是100个,当访问A
的请求比如来了1000个,那么线程池中的线程就会去处理请求A,并且还有大量的请求在阻塞队列中进行等待。。。。
若这时候有了请求B,因为线程池中的已经没有可用线程,所以请求B 就会访问不了,因为A 影响到了请求B。这就是雪崩效应。
怎么解决?
Hyxtrix中提供了解决方案-------服务隔离
什么是服务隔离?
就是服务与服务接口之间互不影响,服务隔离的实现方法,原子计数器隔离(当服务接口A 访问到了多少之后,不可访问,这个处理不是很好),线程池隔离,每个服务接口有自己的线程池,这样就不共用一个线程池了。
例子:服务隔离之前
本地有两个服务,member(8081) order(8080),order 里面的接口通过rpc 远程调用member 里面的接口,实现了两个服务间的通信。
rpc 其实就是通过http 协议,底层是通过socket 技术完成的通信。代码如下:
member 代码:
service:
package com.aiyuesheng.service; import java.util.concurrent.ConcurrentHashMap; import org.springframework.stereotype.Service; @Service public class MemberService { public ConcurrentHashMap<String, String> searchBankInfo() throws InterruptedException{ ConcurrentHashMap<String, String> bankInfo = new ConcurrentHashMap<String, String>(); bankInfo.put("NAME", "China Bank"); bankInfo.put("LOCATION", "SHANGHAI"); Thread.sleep(1500); //为了效果更加明显 return bankInfo; } public ConcurrentHashMap<String, String> searchCustomerInfo(){ ConcurrentHashMap<String, String> cusInfo = new ConcurrentHashMap<String, String>(); cusInfo.put("NAME", "CHRIS"); cusInfo.put("SEX", "MALE"); return cusInfo; } }
controller:
@RestController public class Index { @Autowired private MemberService memberService; @RequestMapping("/searchBankInfo") public Object searchBankInfo() throws InterruptedException { return memberService.searchBankInfo(); } @RequestMapping("/searchCustomerInfo") public Object searchCustomerInfo() { return memberService.searchCustomerInfo(); } }
启动类:
package com.aiyuesheng; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
order 的代码:
service: rpc远程调用member 的接口
package com.aiyuesheng.service; import org.springframework.stereotype.Service; import com.aiyuesheng.utils.HttpClientUtils; import com.alibaba.fastjson.JSONObject; @Service public class OrderService { public JSONObject searchBankInfo() { JSONObject result = HttpClientUtils.httpGet("http://127.0.0.1:8081/searchBankInfo"); return result; } public JSONObject searchCustomerInfo() { JSONObject result = HttpClientUtils.httpGet("http://127.0.0.1:8081/searchCustomerInfo"); return result; } }
controller:
package com.aiyuesheng.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.aiyuesheng.service.OrderService; @RestController public class Index { @Autowired private OrderService orderService; @RequestMapping("/searchBankInfo") public Object searchBankInfo() { System.out.println("searchBankInfo"); return orderService.searchBankInfo(); } @RequestMapping("/searchCustomerInfo") public Object searchCustomerInfo() { System.out.println("searchCustomerInfo"); return orderService.searchCustomerInfo(); } }
rpc:工具类
package com.aiyuesheng.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参数 */ 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 */ 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; } 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; } }
启动类:
package com.aiyuesheng; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
测试:两个服务同时启动
通过jmeter 模拟请求:
600 个线程同时跑http://127.0.0.1:8080/searchBankInfo 的时候,有些线程放入了阻塞队列里面
当我们访问http://127.0.0.1:8080/searchCustomerInfo 的时候速度会变的很慢
加入hystrix 服务隔离:代码实现如下:
package com.aiyuesheng.hystrix; import org.springframework.beans.factory.annotation.Autowired; import com.aiyuesheng.service.OrderService; 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; public class OrderHystrixCommand extends HystrixCommand<JSONObject> { @Autowired private OrderService orderService; // 通过构造方法传参 public OrderHystrixCommand(OrderService orderService) { super(setter()); this.orderService = orderService; } 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); } @Override protected JSONObject run() throws Exception { JSONObject customerInfo = orderService.searchCustomerInfo(); System.out.println("当前线程名称:" + Thread.currentThread().getName()); return customerInfo; } @Override protected JSONObject getFallback() { // 如果Hystrix发生熔断,当前服务不可用,直接执行Fallback方法 System.out.println("系统错误!"); JSONObject jsonObject = new JSONObject(); jsonObject.put("code", 500); jsonObject.put("msg", "系统错误!"); return jsonObject; } }
原来的接口改为:
@RequestMapping("/searchCustomerInfo") public Object searchCustomerInfo() throws Exception { return orderService.searchCustomerInfo(); } @RequestMapping("/hystrixSearchCustomerInfo") public Object hystrixSearchCustomerInfo() throws Exception { OrderHystrixCommand orderHystrixCommand = new OrderHystrixCommand(orderService); return orderHystrixCommand.execute(); }
3,服务降级
当服务不可用的时候,为了避免让客户端一直等待,可以直接返回个页面提示用户,例如服务器忙,请稍后重试。可以提供用户体验。
避免服务雪崩的情况发生。使用fallback 方法返回
4,服务熔断
服务熔断的目的是为了保护服务器,为了不让其宕机。
当高并发的情况下,大量的请求过来的时候,可以设置一个零界点,超过这个这个临界点之后的请求,直接拒绝掉,返回服务降级的提示,从而保护服务器。
服务熔断一般和服务降级一块使用。
5,应用场景
线程池隔离:
1、 第三方应用或者接口
2、 并发量大
信号量隔离:
1、 内部应用或者中间件(redis)
2、 并发需求不大(突发的高并发,难以预计多少并发量,难设置临界点,所以不太适合高并发量)