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、 并发需求不大(突发的高并发,难以预计多少并发量,难设置临界点,所以不太适合高并发量)

 

 

posted @ 2019-07-26 14:59  Chris,Cai  阅读(376)  评论(0编辑  收藏  举报