SpringCloud microservices Xiaobai can also take (Hoxton.SR8) (4) Hystrix|Circuit Breaker

Simple to get started, copy directly, you can build microservices (Hoxton.SR8) 2020.8.28 released, SpringCloud build articles are being sorted out, don’t miss out on dry goods

Summary

Spring Cloud Netflix Hystrix is ​​one of the core components of the Spring Cloud Netflix sub-project, with a series of service protection functions such as service fault tolerance and thread isolation. In the microservice architecture, services and services communicate through remote calls. Once a called service fails, its dependent services will also fail. At this time, the failure will spread and eventually lead to the system paralysis. Hystrix implements the circuit breaker mode. When a service fails, through the monitoring of the circuit breaker, an error response is returned to the caller instead of waiting for a long time, so that the caller will not receive a response for a long time. And occupy the thread, thereby preventing the spread of the fault. Hystrix has powerful functions such as service degradation, service fusing, thread isolation, request caching, request merging and service monitoring.

1. Create a hystrix-service module

  

1.1 New hystrix dependency in pom.xml

<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 Added @EnableCircuitBreaker and @EnableDiscoveryClient to the startup class

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 New UserService and implementation class

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 Create controller test call

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 configuration

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 tool class

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. Test

2.1 Normal test

2.2 Load service test

  

2.3 Close a service

As a result, there will be a short service degradation and then return to normal

 

2.4 Test cache

You can see that there is only one request

2.5 Delete cache

After calling it once, clear the cache, and then call again

3.gitee address

Source code reference

Guess you like

Origin blog.csdn.net/itjavaee/article/details/109067798