基于之前文章 实战分布式治理方案之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站地址