简单上手,直接照搬,就可搭建微服务(Hoxton.SR8) 2020.8.28发布,SpringCloud搭建的文章正在整理,干货不要错过哦
-
SpringCloud微服务小白也能搭(Hoxton.SR8)(七)Gateway|服务网关
-
SpringCloud微服务小白也能搭(Hoxton.SR8)(八)Sleuth|服务链路跟踪
摘要
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 删除缓存
调用一次后,清除缓存,就会又重新调用