SpringCloud微服务小白也能搭(Hoxton.SR8)(四)Hystrix|断路器

简单上手,直接照搬,就可搭建微服务(Hoxton.SR8) 2020.8.28发布,SpringCloud搭建的文章正在整理,干货不要错过哦

摘要

Spring Cloud Netflix Hystrix 是Spring Cloud Netflix 子项目的核心组件之一,具有服务容错及线程隔离等一系列服务保护功能。在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。

1.创建一个hystrix-service模块

  

1.1 pom.xml新增 hystrix 依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

1.2启动类新增 @EnableCircuitBreaker 和 @EnableDiscoveryClient

package com.zqh.www;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.core.env.Environment;

/**
 * 开启断路器功能
 */
@EnableCircuitBreaker
/**
 * 开启服务发现客户端
 * @EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient 可以是其他注册中心。
 */
@EnableDiscoveryClient
@SpringBootApplication
/**
 * 可以取代上面三个
 */
@SpringCloudApplication
public class HystrixServiceApplication {

    private final static Logger logger = LoggerFactory.getLogger(HystrixServiceApplication.class);

    public static void main(String[] args) {
        Environment env = SpringApplication.run(HystrixServiceApplication.class, args).getEnvironment();
        logger.info(
                "\n----------------------------------------------------------\n\t"
                        + "Application '{}' is running! Access URLs:\n\t"
                        + "Local: \t\thttp://localhost:{}{}"
                        + "\n----------------------------------------------------------",
                env.getProperty("spring.application.name"), env.getProperty("server.port"),
                env.getProperty("server.servlet.context-path") != null ? env.getProperty("server.servlet.context-path") : "");
    }
}

1.3 新增 UserService 以及实现类

package com.zqh.www.service;

import java.util.Map;

public interface IUserService {

    Map<String, Object> getUserList();

    Map<String, Object> getCacheUserList();

    void removeCache();
}
package com.zqh.www.service.impl;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import com.zqh.www.service.IUserService;
import com.zqh.www.utils.RestTemplateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class UserServiceImpl implements IUserService {

    private String userServiceUrl = "http://user-service";
    //    private String userServiceUrl = "http://localhost:8084";

    @Autowired
    private RestTemplateUtils restTemplateUtils;

    /**
     * 注解@HystrixCommand中的常用参数
     * fallbackMethod:指定服务降级处理方法,此方法必须和hystrix的执行方法在相同类中;
     * ignoreExceptions:忽略某些异常,不发生服务降级;
     * commandKey:命令名称,用于区分不同的命令,默认值为注解方法的名称;
     * groupKey:分组名称,Hystrix会根据不同的分组来统计命令的告警及仪表盘信息,默认注解方法类的名称;
     * threadPoolKey:线程池名称,用于划分线程池,默认定义为groupKey。
     */
    @HystrixCommand(fallbackMethod = "getUserListFallbackMethod1", ignoreExceptions = {NullPointerException.class})
    public Map<String, Object> getUserList() {
        return restTemplateUtils.exchange(userServiceUrl + "/api/user/getUserList", HttpMethod.GET, null, null, null, Map.class);
    }

    /**
     * 缓存测试
     *
     * @return
     */
    @CacheResult(cacheKeyMethod = "getCacheKey")
    @HystrixCommand(fallbackMethod = "getUserListFallbackMethod1", ignoreExceptions = {NullPointerException.class})
    public Map<String, Object> getCacheUserList() {
        return restTemplateUtils.exchange(userServiceUrl + "/api/user/getUserList", HttpMethod.GET, null, null, null, Map.class);
    }

    /**
     * commandKey 用的是 HystrixCommand的commandKey:默认值为注解方法的名称;
     *
     * @HystrixCommand 需要带上
     */
    @HystrixCommand
    @CacheRemove(commandKey = "getCacheUserList", cacheKeyMethod = "getCacheKey")
    public void removeCache() {
    }

    /**
     * 为缓存生成key的方法
     * 如果方法是由传参的话,getCacheKey这个方法也要带参数,主要用来处理是否是唯一的标识,如果你这里直接使用UUID,缓存就会失效,因为每次cacheKey都是不一样的
     * 如果是没有参数的话,返回一个常量就好了
     *
     * @return
     */
    public String getCacheKey() {
        return "1";
//        return UUID.randomUUID().toString();
    }

    /**
     * 降级处理方法
     *
     * @return
     */
    public Map<String, Object> getUserListFallbackMethod1() {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", "500");
        resultMap.put("msg", "调用失败,服务被降级");
        resultMap.put("data", null);
        return resultMap;
    }
}

1.4 创建controller测试调用

package com.zqh.www.controller;

import com.zqh.www.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/api/hystrix")
public class HystrixController {

    @Autowired
    private IUserService userService;

    /**
     * 正常测试
     *
     * @return
     */
    @GetMapping("/getUserList")
    public Map<String, Object> getUserList() {
        return userService.getUserList();
    }

    /**
     * 缓存测试
     *
     * @return
     */
    @GetMapping("/getCacheUserList")
    public Map<String, Object> getCacheUserList() {
        userService.getCacheUserList();
        userService.getCacheUserList();
        userService.getCacheUserList();
        userService.getCacheUserList();
        return userService.getCacheUserList();
    }

    /**
     * 缓存删除测试
     *
     * @return
     */
    @GetMapping("/getRemoveCacheUserList")
    public Map<String, Object> getRemoveCacheUserList() {
        userService.getCacheUserList();
        userService.removeCache();
        return userService.getCacheUserList();
    }
}

1.5 RibbonConfig 配置

package com.zqh.www.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class RibbonConfig {

    /**
     * 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,以及根据微服务名称调用接口的能力
     *
     * @return
     */
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.registerModule(simpleModule);

        RestTemplate restTemplate = new RestTemplate();
        List<HttpMessageConverter<?>> converters = new ArrayList<>();
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        jsonConverter.setObjectMapper(objectMapper);
        converters.add(jsonConverter);
        restTemplate.setMessageConverters(converters);
        return restTemplate;
    }
}

1.6 HystrixRequestContextFilter

package com.zqh.www.filters;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * Request caching is not available. Maybe you need to initialize the HystrixRequestContext?
 * 在缓存使用过程中,我们需要在每次使用缓存的请求前后对HystrixRequestContext进行初始化和关闭,否则会出现如下异常
 * 这里我们通过使用过滤器,在每个请求前后初始化和关闭HystrixRequestContext来解决该问题:
 */
@Component
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class HystrixRequestContextFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            context.close();
        }
    }

}

1.7 RestTemplateUtils 工具类

package com.zqh.www.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@Component
public class RestTemplateUtils {

    private final static Logger logger = LoggerFactory.getLogger(RestTemplateUtils.class);

    @Autowired
    private RestTemplate restTemplate;

    /**
     * @param url           请求路径
     * @param method        请求方式? HttpMethod.POST/HttpMethod.GET
     * @param uriVariables  表单传参的参数
     * @param jsonObject    JSON格式传参的参数
     * @param requestHeader 请求头
     * @param responseType  返回结果Class
     * @return
     * @Description: JSON格式参数和表单参数一起传
     */
    public <T> T exchange(String url, HttpMethod method, Map<String, Object> uriVariables, Object jsonObject, HttpHeaders requestHeader, Class<T> responseType) {
//		一般的表单传参
        String uriVariablesParam = "";
        if (null != uriVariables && 0 != uriVariables.size()) {
            StringBuffer uriSb = new StringBuffer("?");
            uriVariables.forEach((k, v) -> {
                uriSb.append(k).append("=").append("{").append(k).append("}").append("&");
            });
            uriVariablesParam = uriSb.substring(0, uriSb.length() - 1).toString();
        }
        HttpHeaders requestHeaders = requestHeader;
        if (null == requestHeaders) {
            requestHeaders = new HttpHeaders();
        }

        HttpEntity<Object> requestEntity = new HttpEntity<>(null, requestHeaders);
        if (null != jsonObject) {
            requestEntity = new HttpEntity<>(jsonObject, requestHeaders);
        }
        url += uriVariablesParam;
        long startTime = System.currentTimeMillis();
        if (null == uriVariables || 0 == uriVariables.size()) {
            T body = restTemplate.exchange(url, method, requestEntity, responseType).getBody();
            logger.info("【接口请求】【{}】【处理时间:{}】【普通参数:{}】【JSON参数:{}】【返回结果:{}】", url, System.currentTimeMillis() - startTime, uriVariables, jsonObject, body);
            return body;
        }
        T body = restTemplate.exchange(url, method, requestEntity, responseType, uriVariables).getBody();
        logger.info("【接口请求】【{}】【处理时间:{}】【普通参数:{}】【JSON参数:{}】【返回结果:{}】", url, System.currentTimeMillis() - startTime, uriVariables, jsonObject, body);
        return body;
    }
}

2.测试

2.1 正常测试

2.2 负载服务测试

  

2.3 关闭一个服务

结果会出现短暂的服务降级,然后回归正常

 

2.4 测试缓存

可以看到只有一次请求

2.5 删除缓存

调用一次后,清除缓存,就会又重新调用

3.gitee地址

源码参考

猜你喜欢

转载自blog.csdn.net/itjavaee/article/details/109067798