总结Spring Cloud
这里简单的总结一下学过的东西,并查缺补漏看下哪些没有学到
Spring Cloud模块的相关介绍
- Eureka:服务注册中心,用于服务管理。
- Ribbon:基于客户端的负载均衡组件。
- Hystrix:容错框架,能够防止服务的雪崩效应。
- Feign:Web 服务客户端,能够简化 HTTP 接口的调用。
- Zuul:API 网关,提供路由转发、请求过滤等功能。
- Config:分布式配置管理。
- Sleuth:服务跟踪。
- Stream:构建消息驱动的微服务应用程序的框架。
- Bus:消息代理的集群消息总线。
Eureka模块
Eureka主要功能有注册中心服务,服务提供者,服务消费者,主要配置有密码认证,集群搭建,自我保护和InstanceID,快速移除失效服务,简单的过一遍
注册中心比较简单,只要导入依赖,在启动类上添加@EnableEurekaServer
配置文件
spring.application.name=eureka-server
server.port=8761
# 由于该应用为注册中心, 所以设置为false, 代表不向注册中心注册自己
eureka.client.register-with-eureka=false
# 由于注册中心的职责就是维护服务实例, 它并不需要去检索服务, 所以也设置为 false
eureka.client.fetch-registry=false
服务提供者提供一个接口给其他服务调用
启动类添加注解 @EnableDiscoveryClient
配置文件
spring.application.name= eureka-client-user-service
server.port=8081
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
# 采用IP注册
eureka.instance.preferIpAddress=true
# 定义实例ID格式
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
提供接口
@RestController
public class UserController {
@GetMapping("/user/hello")
public String hello() {
return “hello”;
}
}
消费者和提供者配置文件相似,可以通过配置RestTemplate来调用接口
@Configuration
public class BeanConfiguration {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@RestController
public class ArticleController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/article /callHello")
public String callHello() {
return restTemplate.getForObject("http://localhost:8081/user/hello", String.class);
}
}
这一段还需要确定你要访问的地址不可行
改造 RestTemplate 的配置,添加一个 @LoadBalanced 注解,这个注解会自动构造 LoadBalancerClient 接口的实现类并注册到 Spring 容器中
@Configuration
public class BeanConfiguration {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@GetMapping("/article/callHello2")
public String callHello2() {
return restTemplate.getForObject("http://eureka-client-user-service/user/hello", String.class);
}
集成spring-security进行安全认证
配置文件上添加认证
spring.security.user.name=xfgg #用户名
spring.security.user.password=123456 #密码
增加security配置类
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭csrf
http.csrf().disable();
// 支持httpBasic
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
}
搭建集群
配置文件
server.port=8761
# 指向你的从节点的Eureka
eureka.client.serviceUrl.defaultZone=http://用户名:密码@localhost:8762/eureka/
增加 application-slaveone.properties:
server.port=8762
# 指向你的主节点的Eureka
eureka.client.serviceUrl.defaultZone=http://用户名:密码 @localhost:8761/eureka/
在 application.properties 中添加下面的内容:
spring.application.name=eureka-server-cluster
# 由于该应用为注册中心, 所以设置为false, 代表不向注册中心注册自己
eureka.client.register-with-eureka=false
# 由于注册中心的职责就是维护服务实例, 并不需要检索服务, 所以也设置为 false
eureka.client.fetch-registry=false
spring.security.user.name=xfgg
spring.security.user.password=123456
# 指定不同的环境
spring.profiles.active=master
在 A 机器上默认用 master 启动,然后在 B 机器上加上 --spring.profiles.active=slaveone 启动即可
关闭自我保护
配置文件
eureka.server.enableSelfPreservation=false
自定义instanceID
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
eureka.instance.preferIpAddress=true
自定义实例跳转链接
eureka.instance.status-page-url=c.biancheng.net
开发时快速移除失效服务
注册中心
eureka.server.enable-self-preservation=false
# 默认 60000 毫秒
eureka.server.eviction-interval-timer-in-ms=5000
客户端服务
eureka.client.healthcheck.enabled=true
# 默认 30 秒
eureka.instance.lease-renewal-interval-in-seconds=5
# 默认 90 秒
eureka.instance.lease-expiration-duration-in-seconds=5
Ribbon模块
Ribbon主要学习了Ribbon的使用,结合RestTemplate实现负载均衡,负载均衡策略,自定义负载均衡策略,配置详解
Rinbbon的使用
Rinbbon模块是写在服务提供者中的。需要启动两个服务,一个是8081的端口,一个是8083的端口
// 服务列表
List<Server> serverList = Lists.newArrayList(new Server("localhost", 8081), new Server("localhost", 8083));
// 构建负载实例
ILoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
// 调用 5 次来测试效果
for (int i = 0; i < 5; i++) {
String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build()
.submit(new ServerOperation<String>() {
public Observable<String> call(Server server) {
try {
String addr = "http://" + server.getHost() + ":" + server.getPort() + "/user/hello";
System.out.println(" 调用地址:" + addr);
URL url = new URL(addr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
InputStream in = conn.getInputStream();
byte[] data = new byte[in.available()];
in.read(data);
return Observable.just(new String(data));
} catch (Exception e) {
return Observable.error(e);
}
}
}).toBlocking().first();
System.out.println(" 调用结果:" + result);
}
使用RestTemplate整合Ribbon
服务提供者
@GetMapping("/house/data")
public HouseInfo getData(@RequestParam("name") String name) {
return new HouseInfo(1L, "上海" "虹口" "东体小区");
}
@GetMapping("/house/data/{name}")
public String getData2(@PathVariable("name") String name) {
return name;
}
服务消费者
@GetMapping("/call/data")
public HouseInfo getData(@RequestParam("name") String name) {
return restTemplate.getForObject( "http://localhost:8081/house/data?name="+ name, HouseInfo.class);
}
@GetMapping("/call/data/{name}")
public String getData2(@PathVariable("name") String name) {
return restTemplate.getForObject( "http://localhost:8081/house/data/{name}", String.class, name);
}
RestTemplate负载均衡实例
配置
@Configuration
public class BeanConfiguration {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
接口调用代码
@GetMapping("/call/data")
public HouseInfo getData(@RequestParam("name") String name) {
return restTemplate.getForObject("http://ribbon-eureka-demo/house/data?name=" + name, HouseInfo.class);
}
RibbonAPI使用
当你有一些特殊的需求,想通过 Ribbon 获取对应的服务信息时,可以使用 Load-Balancer Client 来获取,比如你想获取一个 ribbon-eureka-demo 服务的服务地址,可以通过 LoadBalancerClient 的 choose 方法来选择一个
@Autowired
private LoadBalancerClient loadBalancer;
@GetMapping("/choose")
public Object chooseUrl() {
ServiceInstance instance = loadBalancer.choose("ribbon-eureka-demo");
return instance;
}
负载均衡策略介绍
每个策略的意义
- BestAvailabl:选择一个最小并发请求的server,逐个考察Server,如果Server被标记为错误,则跳过,然后再选择ActiveRequestCount中最小的Server
- AvailabilityFilteringRule:过滤掉那些一直连接失败的且被标记为 circuit tripped 的后端 Server,并过滤掉那些高并发的后端 Server 或者使用一个 AvailabilityPredicate 来包含过滤 Server 的逻辑。其实就是检查 Status 里记录的各个 Server 的运行状态。
- ZoneAvoidanceRule:使用 ZoneAvoidancePredicate 和 AvailabilityPredicate 来判断是否选择某个 Server,前一个判断判定一个 Zone 的运行性能是否可用,剔除不可用的 Zone(的所有 Server),AvailabilityPredicate 用于过滤掉连接数过多的 Server。
- RandomRule:随机选择一个 Server。
- RoundRobinRule:轮询选择,轮询 index,选择 index 对应位置的 Server
- RetryRule:对选定的负载均衡策略机上重试机制,也就是说当选定了某个策略进行请求负载时在一个配置时间段内若选择 Server 不成功,则一直尝试使用 subRule 的方式选择一个可用的 Server。
- ResponseTimeWeightedRule:作用同 WeightedResponseTimeRule,ResponseTime-Weighted Rule 后来改名为 WeightedResponseTimeRule。
- WeightedResponseTimeRule:根据响应时间分配一个 Weight(权重),响应时间越长,Weight 越小,被选中的可能性越低
Ribbon配置详解
- 禁用Eureka
# 禁用 Eureka
ribbon.eureka.enabled=false
当我们禁用了 Eureka 之后,就不能使用服务名称去调用接口了,必须指定服务地址
- 配置接口地址列表
# 禁用 Eureka 后手动配置服务地址
ribbon-config-demo.ribbon.listOfServers=localhost:8081,localhost:8083
- 配置负载均衡策略
- 使用代码配置Ribbon
@Configuration
public class BeanConfiguration {
@Bean
public MyRule rule() {
return new MyRule();
}
}
@RibbonClient(name = "ribbon-config-demo", configuration = BeanConfiguration.class)
public class RibbonClientConfig {
}
- 超时时间
# 请求连接的超时时间
ribbon.ConnectTimeout=2000
# 请求处理的超时时间
ribbon.ReadTimeout=5000
也可以为每个Ribbon客户端设置不同的超时时间, 通过服务名称进行指定:
ribbon-config-demo.ribbon.ConnectTimeout=2000
ribbon-config-demo.ribbon.ReadTimeout=5000
- 并发参数
# 最大连接数
ribbon.MaxTotalConnections=500
# 每个host最大连接数
ribbon.MaxConnectionsPerHost=500
- 重试参数
要添加retry依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
# 对当前实例的重试次数
ribbon.maxAutoRetries=1
# 切换实例的重试次数
ribbon.maxAutoRetriesNextServer=3
# 对所有操作请求都进行重试
ribbon.okToRetryOnAllOperations=true
# 对Http响应码进行重试
ribbon.retryableStatusCodes=500,404,502
Feign模块
使用Feign的调用接口
启动类添加
@EnableFeignClients注解
定义一个Feign客户端
@FeignClient(value = "eureka-client-user-service")
public interface UserRemoteClient {
@GetMapping("/user/hello")
String hello();
}
这个注解标识当前是一个 Feign 的客户端,value 属性是对应的服务名称,也就是你需要调用哪个服务中的接口
调用接口
@Autowired
private UserRemoteClient userRemoteClient;
@GetMapping("/callHello")
public String callHello() {
//return restTemplate.getForObject("http://localhost:8083/house/hello",String.class);
//String result = restTemplate.getForObject("http://eureka-client-user-service/user/hello",String.class);
String result = userRemoteClient.hello();
System.out.println("调用结果:" + result);
return result;
}
Feign的自定义配置
具体详情见http://c.biancheng.net/view/5362.html
日志配置
首先定义一个配置类
@Configuration
public class FeignConfiguration {
/**
* 日志级别
*
* @return
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
日志等级有四种
NONE:不输出日志。
BASIC:只输出请求方法的 URL 和响应的状态码以及接口执行的时间。
HEADERS:将 BASIC 信息和请求头信息输出。
FULL:输出完整的请求信息。
在@FeignClient注解中指定使用的配置类
@FeignClient(value = "eureka-client-user-service", configuration = FeignConfiguration. class)
public interface UserRemoteClient {
// ...
}
配置文件执行Client的日志级别
logging.level.net.biancheng.feign_demo.remote.UserRemoteClient=DEBUG
Hystrix模块
信号量策略配置
主要适用于并发需求不大的依赖调用,因为如果并发需求较大,相应的信号量的数量就要设置得够大,因为 Tomcat 线程与处理线程为同一个线程,那么这个依赖调用就会占用过多的 Tomcat 线程资源,有可能会影响到其他服务的接收。
super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MyGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE
)));
this.name = name;
}
线程隔离策略配置
通过为每个包裹了 HystrixCommand 的 API 接口设置独立的、固定大小的线程池(hystrix.threadpool.default.coreSize)来控制并发访问量,当线程饱和的时候可以拒绝服务,防止依赖问题扩散
public MyHystrixCommand(String name) {
super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MyGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10)
.withMaxQueueSize(100).withMaximumSize(100)));
this.name = name;
}
具体如何实现的在之前写的文章中:https://blog.youkuaiyun.com/qq_42337039/article/details/113104010
结果缓存
通过重写getCacheKey来判断是否返回缓存的数据,getCacheKey可以根据参数来生成,同样的参数就可以都用到缓存
可以自己写一个HystrixCommand然后继承HystrixCommand,重写getCachekey
@Override
protected String getCacheKey() {
return String.valueOf(this.name);
}
缓存的处理取决于请求的上下文,我们必须初始化Hystrix-RequestContext
public static void main(String[] args) throws InterruptedException, ExecutionException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
String result = new MyHystrixCommand("zhangsan").execute();
System.out.println(result);
Future<String> future = new MyHystrixCommand("zhangsan").queue();
System.out.println(future.get());
context.shutdown();
}
缓存清除
增加一个支持缓存清除的类
flushCache 方法就是清除缓存的方法,通过 HystrixRequestCache 来执行清除操作,根据 getCacheKey 返回的 key 来清除。
public class ClearCacheHystrixCommand extends HystrixCommand<String> {
private final String name;
private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("MyKey");
public ClearCacheHystrixCommand(String name) {
super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MyGroup"))
.andCommandKey(GETTER_KEY));
this.name = name;
}
public static void flushCache(String name) {
HystrixRequestCache.getInstance(GETTER_KEY,HystrixConcurrencyStrategyDefault.getInstance()).clear(name);
}
@Override
protected String getCacheKey() {
return String.valueOf(this.name);
}
@Override
protected String run() {
System.err.println("get data");
return this.name + ":" + Thread.currentThread().getName();
}
@Override
protected String getFallback() {
return "失败了 ";
}
}
合并请求
继承Collaapser来实现将多个请求合并为一个请求
public class MyHystrixCollapser extends HystrixCollapser<List<String>, String, String> {
private final String name;
public MyHystrixCollapser(String name) {
this.name = name;
}
@Override
public String getRequestArgument() {
return name;
}
@Override
protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, String>> requests) {
return new BatchCommand(requests);
}
@Override
protected void mapResponseToRequests(List<String> batchResponse,
Collection<CollapsedRequest<String, String>> requests) {
int count = 0;
for (CollapsedRequest<String, String> request : requests) {
request.setResponse(batchResponse.get(count++));
}
}
private static final class BatchCommand extends HystrixCommand<List<String>> {
private final Collection<CollapsedRequest<String, String>> requests;
private BatchCommand(Collection<CollapsedRequest<String, String>> requests) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
this.requests = requests;
}
@Override
protected List<String> run() {
System.out.println(" 真正执行请求......");
ArrayList<String> response = new ArrayList<String>();
for (CollapsedRequest<String, String> request : requests) {
response.add(" 返回结果 : " + request.getArgument());
}
return response;
}
}
}
容错处理
启动类上添加@EnableHystrix
调用接口的方法,上面添加一个@HystrixCommand注解
@GetMapping("/callHello")
@HystrixCommand(fallbackMethod = "defaultCallHello")
public String callHello() {
String result = restTemplate.getForObject("http://localhost:8088/house/hello", String.class);
return result;
}
public String defaultCallHello() {
return "fail";
}
可以在回退的同时添加配置
@HystrixCommand(fallbackMethod = "defaultCallHello",commandProperties = {
@HystrixProperty(name="execution.isolation.strategy", value = "THREAD")
}
)
@GetMapping("/callHello")
public String callHello() {
String result = restTemplate.getForObject("http://localhost:8088/house/hello", String.class);
return result;
}
Zuul模块
构建服务网关
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
配置文件
eureka.client.serviceUrl.defaultZone=http://xfgg:123456@localhost:8761/eureka/
spring.application.name=zuul-demo
server.port=2103
zuul.routes.xfgg.path=//**
zuul.routes.xfgg.url=http://c.xfgg.net
通过 zuul.routes 来配置路由转发,xfgg是自定义的名称,当访问xfgg/** 开始的地址时,就会跳转到 http://c.biancheng.net 上。
路由配置
- 指定具体服务路由
我们可以为每一个服务都配置一个路由转发规则:
配置文件
zuul.routes.fsh-house.path=/api-house/**
- 路由前缀
有的时候我们会想在 API 前面配置一个统一的前缀,比如像 http://c.biancheng.net/user/login 这样登录接口,如果想将其变成 http://c.biancheng.net/rest/user/login,即在每个接口前面加一个 rest,此时我们就可以通过 Zuul 中的配置来实现:
zuul.prefix=/rest
- 本地跳转
Zuul 的 API 路由还提供了本地跳转功能,通过 forward 就可以实现
zuul.routes.fsh-substitution.path=/api/**
zuul.routes.fsh-substitution.url=forward:/local
当我们想在访问 api/1 的时候会路由到本地的 local/1 上去,就可以参照上述代码实现。local 是本地接口需要我们自行添加,因此我们要建一个 Controller,代码如下所示。
@RestController
public class LocalController {
@GetMapping("/local/{id}")
public String local(@PathVariable String id) {
return id;
}
}
Zuul过滤器详解
过滤器类型
(1)pre
可以在请求路由之前被调用,使用于身份认证的场景,认证通过后在继续执行下面的流程
(2)route
在路由请求时被调用,使用于灰度发布场景,在将要路由的时候可以做一些自定义的逻辑
(3)post
在route和error过滤器之后被调用,这种过滤器将请求路由到达具体的服务之后执行,适用于需要添加响应头,记录响应日志等应用场景
(4)error
处理请求时发生错误时被调用,在执行过程中发送错误时,会进入error过滤器,可以用来统一记录错误信息
过滤器的生命周期
编写ZuulServlet类似Spring-MVC的dispatcherServlet,所有的Request都要经过ZuulServlet的处理
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse)
throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
使用过滤器
创建一个pre过滤器,来实现IP黑名单的过滤操作
public class IpFilter extends ZuulFilter {
// IP黑名单列表
private List<String> blackIpList = Arrays.asList("127.0.0.1");
public IpFilter() {
super();
}
@Override
public boolean shouldFilter() {
return true
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String ip = IpUtils.getIpAddr(ctx.getRequest());
// 在黑名单中禁用
if (StringUtils.isNotBlank(ip) && blackIpList.contains(ip)) {
ctx.setSendZuulResponse(false);
ResponseData data = ResponseData.fail("非法请求 ", ResponseCode.NO_AUTH_CODE.getCode());
ctx.setResponseBody(JsonUtils.toJson(data));
ctx.getResponse().setContentType("application/json; charset=utf-8");
return null;
}
return null;
}
}
自定义过滤器需要继承ZuulFilter,并且需要实现下面几个方法
(1)shouldFilter
是否执行该过滤器,true为执行,falsee为不执行,这个也可以利用配置中心来实现,达到动态的开启和关闭过滤器
(2)filterType
过滤器类型,可选值有pre,route,post,error
(3)filterOrder
过滤器的执行顺序,数值越小,优先级越高
(4)run
执行自己的业务逻辑,本段代码中是通过判断请求的 IP 是否在黑名单中,决定是否进行拦截。blackIpList 字段是 IP 的黑名单,判断条件成立之后,通过设置 ctx.setSendZuulResponse(false),告诉 Zuul 不需要将当前请求转发到后端的服务了。通过 setResponseBody 返回数据给客户端。
过滤器定义完成之后我们需要配置过滤器才能生效,IP 过滤器配置代码
@Configuration
public class FilterConfig {
@Bean
public IpFilter ipFilter() {
return new IpFilter();
}
}
过滤器禁用
配置文件
zuul.IpFilter.pre.disable=true
过滤器中传递数据
可以通过 RequestContext 的 set 方法进行传递,RequestContext 的原理就是 ThreadLocal
RequestContext的源码
protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
@Override
protected RequestContext initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
public static RequestContext getCurrentContext() {
if (testContext != null)
return testContext;
RequestContext context = threadLocal.get();
return context;
}
使用方法
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("msg", "你好吗");
RequestContext ctx = RequestContext.getCurrentContext();
ctx.get("msg");
过滤器拦截请求
拦截和返回结果只需要5行代码即可实现
RequestContext ctx = RequestContext.getCurrentContext();
ctx.setSendZuulResponse(false);
ctx.set("sendForwardFilter.ran", true);
ctx.setResponseBody("返回信息");
return null;
ctx.setSendZuulResponse(false) 告诉 Zuul 不需要将当前请求转发到后端的服务
ctx.set(“sendForwardFilter.ran”,true);”是用来拦截本地转发请求的
过滤器中异常处理
定义一个error过滤器来记录异常信息
public class ErrorFilter extends ZuulFilter {
private Logger log = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 100;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
log.error("Filter Erroe : {}", throwable.getCause().getMessage());
return null;
}
}
简单的套用之前的框架就好了
run里面进行主要逻辑的修改
可以编写一个controller来对返回的错误进行整理和显示
@RestController
public class ErrorHandlerController implements ErrorController {
@Autowired
private ErrorAttributes errorAttributes;
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public ResponseData error(HttpServletRequest request) {
Map<String, Object> errorAttributes = getErrorAttributes(request);
String message = (String) errorAttributes.get("message");
String trace = (String) errorAttributes.get("trace");
if (StringUtils.isNotBlank(trace)) {
message += String.format("and trace %s", trace);
}
return ResponseData.fail(message, ResponseCode.SERVER_ERROR_CODE.getCode());
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request) {
return errorAttributes.getErrorAttributes(new ServletWebRequest(request), true);
}
}
Zull容错机制
容错,简单来说就是当某个服务不可用时,能够切换到其他可用的服务上去,也就是需要有重试机制
添加retry依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置文件中开启重试机制以及配置重试次数
zuul.retryable=true
ribbon.connectTimeout=500
ribbon.readTimeout=5000
ribbon.maxAutoRetries=1
ribbon.maxAutoRetriesNextServer=3
ribbon.okToRetryOnAllOperations=true
ribbon.retryableStatusCodes=500,404,502
参数意义
zuul.retryable:开启重试。
ribbon.connectTimeout:请求连接的超时时间(ms)。
ribbon.readTimeout:请求处理的超时时间(ms)。
ribbon.maxAutoRetries:对当前实例的重试次数。
ribbon.maxAutoRetriesNextServer:切换实例的最大重试次数。
ribbon.okToRetryOnAllOperations:对所有操作请求都进行重试。
ribbon.retryableStatusCodes:对指定的 Http 响应码进行重试。
回退机制
Zuul 默认整合了 Hystrix,当后端服务异常时可以为 Zuul 添加回退功能,返回默认的数据给客户端。
实现回退机制需要实现 ZuulFallbackProvider 接口
@Component
public class ServiceConsumerFallbackProvider implements ZuulFallbackProvider {
private Logger log = LoggerFactory.getLogger(ServiceConsumerFallbackProvider.class);
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
if (cause != null) {
log.error("", cause.getCause());
}
RequestContext ctx = RequestContext.getCurrentContext();
ResponseData data = ResponseData.fail("服务器内部错误 ", ResponseCode.SERVER_ERROR_CODE.getCode());
return new ByteArrayInputStream(JsonUtils.toJson(data).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(mt);
return headers;
}
};
}
}
getRoute 方法中返回*表示对所有服务进行回退操作,如果只想对某个服务进行回退,那么就返回需要回退的服务名称,这个名称一定要是注册到 Eureka 中的名称。
通过 ClientHttpResponse 构造回退的内容。通过 getStatusCode 返回响应的状态码。通过 getStatusText 返回响应状态码对应的文本。通过 getBody 返回回退的内容。通过 getHeaders 返回响应的请求头信息。
查看路由端点和过滤器信息
/routes和/filter端点来查看路由和过滤器信息
如localhost:8080/actuator/filters