基于之前文章 实战分布式治理方案之SpringCloudNetflix学习篇(一) ,开始升级对Spring Cloud Netflix的学习,下面即将搭建的是:
1.Fegin 分布式服务调用 2.Hyxtrix 分布式服务熔断 3.ZipKin 分布式链路追踪 4.Config 分布式配置中心 5.Zuul 分布式API网关 复制代码
Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单。使用 Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用 Feign 注解和 JAX-RS 注解。Feign 支持可插拔的编码器和解码器。Feign 默认集成了 Ribbon,并和 Eureka 结合,默认实现了负载均衡的效果
1)创建服务消费者 在pom中引用:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 复制代码
2)在@SpringBootApplication下增加下面两个注解:
@EnableDiscoveryClient @EnableFeignClients 复制代码
3)yml和之前Ribbon相同只需要修改端口号和服务名
4)创建服务间接口调用
package com.funtl.hello.spring.cloud.web.admin.feign.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "your-Producer")
public interface FeginAdminService {
//你生产者服务提供的Rest接口名称
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String siHi(@RequestParam(value = "message") String message);
}
复制代码
5)创建需要被前端调用的Rest API
package com.funtl.hello.spring.cloud.web.admin.feign.controller;
import com.funtl.hello.spring.cloud.web.admin.feign.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FeginAdminController {
@Autowired
private FeginAdminService feginAdminService;
@RequestMapping(value = "hiWebF", method = RequestMethod.GET)
public String siHi(@RequestParam String message) {//注意注解
return feginAdminService.sayHi(message);
}
}
复制代码
运行结果:
简单叙述: 为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,被调用服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。一般在同步调用机制的治理框架中使用熔断机制,当服务访问出现不可达时,使用熔断机制会对整个项目进行保护。较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystrix 是 5 秒 20 次) 熔断器将会被打开。
Fegin是自带熔断机制的所以不必要导入其他依赖,不过可以增加一个Spring Cloud Netflix提供的仪表视图
1)导入Spring Cloud Netflix提供的仪表视图
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
复制代码
2)在application类中加入注解 @EnableHystrixDashboard
3)正常使用熔断步骤如下:
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component
public class FeginAdminServiceHystrix implements FeginAdminService {
@Override
public String siHi(String message) {
return "Hi,your message is :/"" + message + "/" but request error.";
}
}
复制代码
3.yml设置打开熔断
feign:
hystrix:
enabled: true
复制代码
4)仪表使用:
/**
* 增加仪表熔断功能,多配置了一个servlet来访问
* 浏览器端访问 http://localhost:你的端口/hystrix
*/
@Configuration
public class HystrixDashboardConfiguration {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
复制代码
完成以下操作后:
开始调用:
结果如下:
熔断仪表访问如下:
ZipKin 是一个开放源代码的分布式跟踪系统,简单来说可以帮你理清服务的调用链。 随着服务的越来越多,对调用链的分析会越来越复杂。它们之间的调用关系也许如下:
这样不言而喻ZipKin的重要性了。
1).导入依赖
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
复制代码
2).application中加入两个注解:
3)yml加入:
management:
metrics:
web:
server:
auto-time-requests: false
复制代码
4)其他服务加入被追踪 在 所有需要被追踪的项目(就当前教程而言,除了 dependencies 项目外都需要被追踪,包括 Eureka Server) 中增加 spring-cloud-starter-zipkin 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
复制代码
在这些项目的 application.yml 配置文件中增加 Zipkin Server 的地址即可
spring:
zipkin:
base-url: http://localhost:9411
复制代码
运行如下:
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在 Spring Cloud 中,有分布式配置中心组件 Spring Cloud Config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程 Git 仓库中。在 Spring Cloud Config 组件中,分两个角色,一是 Config Server,二是 Config Client。
1)创建自己Git的配置中心仓库
2.引入配置中心依赖,依旧引入Eureka并向Eureka注册
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> 复制代码
3).application启动类中增加下面注解:
4)向yml文件中加入:
spring:
cloud:
config:
label: master
server:
git:
uri: https://github.com/topsale/your-config
search-paths: respo 目录
username:yourname
password:yourpassword
复制代码
1)引入Config包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
复制代码
2)客户端使用
spring
cloud:
config:
uri: http://localhost:8888 //Config服务端地址
name: your-Feign //要访问的文件名 your-Feign-dev.yml和上述git文件名保持一致
label: master
profile: dev
复制代码
然后调用服务端:
启动客户端:
Zuul 的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如 /api/user 转发到到 User 服务,/api/admin转发到到 admin服务。Zuul 默认和 Ribbon 结合实现了负载均衡的功能。其实完全可以理解为过滤器服务。
#创建路由网关 1)引入依赖
<!--导入zuul网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
复制代码
2)Application启动类中加入:
3)yml中配置增加:
zuul:
routes:
api-a:
path: /api/a/**
serviceId: your-Ribbon
api-b:
path: /api/b/**
serviceId: your-Feign
复制代码
4)增加过滤功能:
package com.gn.community.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Zuul 的服务过滤演示
*/
@Component
public class LoginFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(LoginFilter.class);
/**
* 配置过滤类型,有四种不同生命周期的过滤器类型
* 1. pre:路由之前
* 2. routing:路由之时
* 3. post:路由之后
* 4. error:发送错误调用
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 配置过滤的顺序
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 配置是否需要过滤:true/需要,false/不需要
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器的具体业务代码
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
logger.info("{} >>> {}", request.getMethod(), request.getRequestURL().toString());
//在请求入参中判断是否存在token
String token = request.getParameter("token");
if (token == null) {
logger.warn("Token is empty");
context.setSendZuulResponse(false);
context.setResponseStatusCode(401);
try {
context.getResponse().getWriter().write("Token is empty");
} catch (IOException e) {
}
} else {
logger.info("OK");
}
return null;
}
}
复制代码
5)增加回调内容
package com.gn.community.provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 访问失败时的回调
*/
@Component
public class WebAdminFeignFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
// ServiceId,如果需要所有调用都支持回退,则 return "*" 或 return null
return "your-feign"; //你调用的服务名
}
/**
* 如果请求服务失败,则返回指定的信息给调用者
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
/**
* 网关向 api 服务请求失败了,但是消费者客户端向网关发起的请求是成功的,
* 不应该把 api 的 404,500 等问题抛给客户端
* 网关和 api 服务集群对于客户端来说是黑盒
* @return
* @throws IOException
*/
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("message", "无法连接,请检查您的网络");
return new ByteArrayInputStream(objectMapper.writeValueAsString(map).getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
// 和 getBody 中的内容编码一致
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
};
}
}
复制代码
启动服务: 发起调用,由于过滤中增加了入参token判断,所以返回失败
加入token后访问正常。
如果关闭Fegin服务也不会出现整体崩盘的情况:
SpringCloudNetflix学习篇到此结束了。
在此继续感谢前锋教育的李老师 李老师B站地址