在之前的文章中 微服务架构搭建-SpringCloud(一).搭建了Eureka注册中心,使用Feign实现微服务之间的调用,本篇文章整合Hystrix做熔断和回退,使用zuul做路由的转发和过滤,使用apollo做配置中心
Hystrix 服务熔断与降级
Hystrix 是 Netflix 针对微服务分布式系统采用的熔断保护中间件,相当于电路中的保险丝。
什么是服务熔断和服务降级呢?
服务熔断是当下游的服务因为某种原因突然变得不可用或响应过慢时,上游服务为了保证自己服务的可用性和稳定性,不再继续调用目标服务,直接返回,快速释放资源。
服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。保证了整个系统的稳定性和可用性。
接下来使用之前搭建好的Feign项目来整合Hystrix实现服务熔断后的降级处理,1.首先增加依赖:
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.18</version>
</dependency>
2.属性文件application.properties中开启 Feign 对 Hystrix 的支持
feign.hystrix.enabled=true
3.在 Feign 的客户端类上的 @FeignClient 注解中指定 fallback (回退类)进行回退,回退类要实现Feign 的客户端类 UserRemoteClient中所有的方法,在方法中返回降级处理。
4.接下来启动Eureka服务,启动我们刚刚加入Hystrix的上游服务,不启动下游服务,调用结果如下,可以看到当下游不通时,接口没有报错,而是执行了服务降级的回退方法。
5.以上通过 fallback 已经可以实现服务不可用时回退的功能,如果你想知道触发回退的原因,可以使用 FallbackFactory 来实现回退功能。在 @FeignClient 中用 fallbackFactory 指定回退处理类,代码和执行结果如下,可以再控制台中看到服务熔断的具体信息。
@Component
public class UserRemoteClientFallbackFactory implements FallbackFactory<UserRemoteClient> {
private Logger logger = LoggerFactory.getLogger(UserRemoteClientFallbackFactory.class);
@Override
public UserRemoteClient create(Throwable throwable) {
logger.error("UserRemoteClient回退:", throwable);
return new UserRemoteClient() {
@Override
public Object getById(Integer id) {
return "---fail---";
}
};
}
}
Hystrix 除了实现容错外,还提供了实时监控功能。在服务调用时,Hystrix 会实时累积执行信息,比如每秒的请求数、成功数等。可以通过 hystrix.stream 端点来获取监控数据,也可以借助图形化展示页面 Hystrix Dashboard 。当然在实际工作中,单个实例的监控数据没有多大的价值,我们更需要的是集群系统的监控信息,Turbine 是用来监控集群的,通过它来汇集监控信息,并将聚合后的信息提供给 Hystrix Dashboard 来集中展示和监控。
Dashboard的监控页面如下:
zuul网关
Zuul 是 Netflix OSS 中的一员,是一个基于 JVM 路由和服务端的负载均衡器。提供路由、监控、弹性、安全等方面的服务框架。Zuul 能够与 Eureka、Ribbon、Hystrix 等组件配合使用。
在实际应用场景中,会使用zuul来做路由转发、统一权限认证、调用链入口埋点、ip黑名单配置、频率控制、压力测试、灰度发布等功能,在spring cloud生态中,zuul作为一个单独的项目部署。接下来使用zuul做一个动态路由转发和过滤黑名单的功能。
- 首先是创建一个新的maven项目,zuul-demo,引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- 在application.properties属性文件中增加配置信息
spring.application.name=zuul-demo
server.port=2103
- 整合Eureka实现路由转发
在实际使用中我们通常是用 Zuul 来代理请求转发到内部的服务上去,统一为外部提供服务。内部服务的数量会很多,而且可以随时扩展,我们不可能每增加一个服务就改一次路由的配置,所以也得通过结合 Eureka 来实现动态的路由转发功能。
在zuul-demo项目中添加Eureka的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改启动代码
@SpringBootApplication
@EnableZuulProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
在配置文件中添加eureka的地址,eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/
重启服务,我们可以通过默认的转发规则来访问 Eureka 中的服务。访问规则是“API 网关地址+访问的服务名称+接口 URI”。
项目启动后,在eureka中可以看到zuul项目已经注册好了。
在这里插入图片描述
我们之前访问的http://localhost:8082/user/getById就可以通过http://localhost:2103/hello-july-two/user/getById来访问成功了。其中hello-july-two为注册在eureka上的另一个项目名称。
在实际应用场景中,还可以通过配置,设置网关url的前缀、更改默认规则
“API 网关地址+访问的服务名称+接口 URI”为“API 网关地址+访问的服务端口号+接口 URI”等功能来配置路由格式。
zuul.prefix=/api
zuul.routes.hello-july-two=/8082/**
5. 过滤黑名单ip
Zuul 中的过滤器总共有 4 种类型,每种类型也有不同的应用场景。
- pre过滤器:可以在请求路由之前被调用,可以用来做身份认证(获取当前登录人信息放入请求头中)、频控(使用redis来查看接口调用次数,达到次数,不允许调用)、调用链cat埋点等功能,以及之后我们过滤ip黑名单也是使用pre过滤器
- route过滤器:可以在路由请求时被调用
- post过滤器:在请求路由到达到具体的服务后,返回响应时被调用,可以用来记录响应日志
- error过滤器:在请求发生错误的时候被调用,可以做统一错误处理
我们创建一个 pre 过滤器,来实现 IP 黑名单的过滤操作,代码如下:
public class IpFilter extends ZuulFilter {
@Resource
private FilterConfig filterConfig;
// IP黑名单列表
private List<String> blackIpList = Arrays.asList("127.0.0.1");
@Override
public String filterType() {
// 过滤器类型,可选值有 pre、route、post、error。
return "pre";
}
@Override
public int filterOrder() {
// 过滤器的执行顺序,数值越小,优先级越高。
return 1;
}
@Override
public boolean shouldFilter() {
// 是否执行该过滤器,true 为执行,false 为不执行,这个也可以利用配置中心来实现,达到动态的开启和关闭过滤器。
return true;
}
@Override
public Object run() throws ZuulException {
// 业务逻辑
// 通过设置 ctx.setSendZuulResponse(false),告诉 Zuul 不需要将当前请求转发到后端的服务了。通过 setResponseBody 返回数据给客户端。
RequestContext ctx = RequestContext.getCurrentContext();
String ip = this.filterConfig.getIp();
if(!CollectionUtils.isEmpty(blackIpList) && blackIpList.contains(ip)){
ctx.setSendZuulResponse(false);
ResponseData data = ResponseData.builder()
.isSuccess(false)
.body("请求被拒绝")
.build();
ctx.setResponseBody(JSON.toJSONString(data));
ctx.getResponse().setContentType("application/json; charset=utf-8");
return null;
}
return null;
}
}
配置过滤器生效
@Configuration
@Getter
public class FilterConfig {
@Bean
public IpFilter ipFilter() {
return new IpFilter();
}
@Value("${ip}")
private String ip;
}
再次调用http://localhost:2103/api/8082/user/getById请求就会被拦截
apollo配置中心
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够热发布,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
在整合配置中心apollo前先在本地部署apollo,快速启动安装包下载地址:https://github.com/nobodyiam/apollo-build-scripts。下载之后进行解压,根据RRADME文件,先初始化数据库,然后修改数据库连接信息,最后启动apollo就ok了。
接下来在我们的项目中整合apollo,
- 首先还是添加依赖
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.1.0</version>
</dependency>
- 然后配置 Apollo 的信息,配置放在 application.properties 中
app.id=2103
apollo.meta=http://localhost:8080
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces=application
- apollo添加一个模块,并配置ip黑名单
4.在项目中通过注解引用使用
@ApolloJsonValue("${blackIpList}")
private List<String> blackIpList;