文章目录
微服务概述与Spring Cloud
微服务:
强调的是服务的大小,它关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用,
狭意的看,可以看作Eclipse里面的一个个微服务工程/或者Module
从技术角度而言微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毁,拥有自己独立的数据库。
微服务架构:
微服务架构是⼀种架构模式,它提倡将单⼀应⽤程序划分成⼀组⼩的服务,服务之间互相协调、互相配合,为⽤户提供最终价值。每个服务运⾏在其独⽴的进程中,服务与服务间采⽤轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进⾏构建,并且能够被独⽴的部署到⽣产环境、类⽣产环境等。另外,应当尽量避免统⼀的、集中式的服务管理机制,对具体的⼀个服务⽽⾔,应根据业务上下⽂,选择合适的语⾔、⼯具对其进⾏构建。
- - -Spring Cloud 项目搭建流程- - -
一、利用idea创建一个空项目
二、创建注册中心Eureka
file–>new–>Module–>Spring Initializer
目录结构:
1、启用注册中心
启动类上
启动一个服务注册中心,只需要一个注解 @EnableEurekaServer
/**
* 注册中心配置步骤
* 配置Eureka信息
* 在启动类中使用注解@EnableEurekaServer启用注册中心
*/
@EnableEurekaServer //启用注册中心
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
2、配置Eureka信息
application.yml
在application.yml文件中配置相关参数
server:
port: 8761 #服务的端口号
# eureka配置
eureka:
instance:
hostname: eureka-server # eureka实例的主机名
client: #eureka客户端配置
register-with-eureka: false #不把自己注册到eureka上
fetch-registry: false #不从eureka上来获取服务的注册信息
service-url: #指定eureka注册中心的地址
defaultZone: http://localhost:8761/eureka/
3、启动注册中心
Eureka Server 是有界面的,启动工程,打开浏览器访问:
http://localhost:8761
三、创建服务提供者
file–>new–>Module–>Spring Initializer
目录结构:
1、配置Eureka信息
application.yml
server:
port: 8762 #服务提供者的端口
spring:
application:
name: spring-cloud-provider #服务提供者的名字
#注册中心的配置
eureka:
instance:
prefer-ip-address: true # 注册服务的时候使用服务的ip地址
client:
serviceUrl: #指定eureka注册中心的地址
defaultZone: http://localhost:8761/eureka/
注意:需要指明 spring.application.name
,这个很重要,这在以后的服务与服务之间相互调用一般都是根据这个 name
2、声明为Eureka Client
启动类上
启动类上添加注解@EnableEurekaClient表明自己是一个Eureka Client
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient//表明自己是一个Eureka Client
@SpringBootApplication
public class SpringCloudProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudProviderApplication.class, args);
}
}
3、编写Controller层测试
import org.springframework.beans.factory.annotation.Value;
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 AdminController {
@Value("${server.port}")//将配置文件中配配置的端口号读进来
private String port;//8762
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String sayHi(@RequestParam(value = "msg") String msg) {
//格式化字符串返回
return String.format("地址栏信息为: %s ,端口号为 : %s", msg, port);
}
}
这时打开 http://localhost:8762/hi?message=前端数据
,你会在浏览器上看到 :
打开 http://localhost:8761
,即 Eureka Server 的网址,你会发现一个服务已经注册在服务中了,服务名为 SPRING-CLOUDPROVIDER
,端口为 8762
四、创建服务消费者
在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于 http restful 的。Spring cloud 有两种服务调用方式,一种是 ribbon + restTemplate,另一种是 feign。首先讲解下基于 ribbon + rest。
*创建基于Ribbon 的服务消费者
Ribbon 是一个负载均衡客户端,可以很好的控制 http
和 tcp
的一些行为。
1、创建消费者
file–>new–>Module–>Spring Initializer
目录结构:
在这里为了后面页面操作,引入了thymeleaf模板引擎。在pom文件中主要是增加了Ribbon的依赖
<!--引入模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--引入ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2、注册到服务中心(注册处中心)
启动类上
通过 @EnableDiscoveryClient
注解注册到服务中心
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient//注册到服务中心
@SpringBootApplication
public class SpringCloudRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudRibbonApplication.class, args);
}
}
3、配置Eureka信息
application.yml
spring:
application:
name: spring-cloud-ribbon #服务发现者的名字
#模板引擎的配置
thymeleaf:
cache: false
mode: LEGACYHTML5
encoding: UTF-8
servlet:
content-type: text/html
server:
port: 8764 #端口为8764
#Eureka设置
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
4、编写RestTemplate配置类
新建config.RestTemplateConfiguration类
配置注入 RestTemplate
的 Bean,并通过 @LoadBalanced
注解表明开启负载均衡功能
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration //表明这是一个配置类
public class RestTemplateConfiguration {
@Bean
@LoadBalanced //使用负载均衡机制(轮流模式)
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
5、编写Controller
import com.zzr.ribbon.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.RestController;
@RestController
public class AdminController {
@Autowired //将service层注入
private AdminService adminService;
@RequestMapping(value = "hi",method = RequestMethod.GET)
public String sayHi(String msg){
//调用service层的方法,向服务提供者获取数据
return adminService.sayHi(msg);
}
}
6、编写Service层
service.AdminService类
在这里我们直接用的程序名替代了具体的 URL 地址,在 Ribbon 中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的 URL 替换掉服务名,代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class AdminService {
@Autowired //注入配置类RestTemplate
RestTemplate restTemplate;
public String sayHi(String msg){
//利用RestTemplate向服务提供者获取数据 url:http://服务提供者的名字/请求参数
return restTemplate.getForObject("http://spring-cloud-provider/hi?msg="+msg,String.class);
}
}
测试访问
在浏览器上多次访问 http://localhost:8764/hi?msg=前端数据
浏览器交替显示下面的信息。达到了负载均衡的效果(轮流机制):
地址栏信息为: 前端信息 ,端口号为 : 8762
地址栏信息为: 前端信息 ,端口号为 : 8763
此时的架构:
- 一个服务注册中心,Eureka Server,端口号为:
8761
- 服务提供者
spring-cloud-provide
工程运行了两个实例,端口号分别为:8762
,8763
- 服务消费者
spring-cloud-ribbon
工程端口号为:8764
spring-cloud-ribbon
(消费者) 通过RestTemplate
调用spring-cloud-provide
(服务者) 接口时因为启用了负载均衡功能故会轮流调用它的8762
和8763
端口
*创建基于Feign的服务消费者
Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单。使用 Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用 Feign 注解和 JAX-RS 注解。Feign 支持可插拔的编码器和解码器。Feign 默认集成了 Ribbon,并和 Eureka 结合,默认实现了负载均衡的效果
- Feign 采用的是基于接口的注解
- Feign 整合了 ribbon
1、创建服务消费者
file–>new–>Module–>Spring Initializer
目录结构:
在这里为了后面页面操作,引入了thymeleaf模板引擎。在pom文件中主要是增加了OpenFeign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、开启Feign功能
启动类
通过 @EnableFeignClients
注解开启 Feign 功能
@EnableFeignClients //开启Feign功能
@EnableDiscoveryClient //注册到服务中心
@SpringBootApplication
public class SpringCloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignApplication.class, args);
}
}
3、配置Eureka信息
application.yml
spring:
application:
name: spring-cloud-feign #服务发现者的名字
#模板引擎的配置
thymeleaf:
cache: false
mode: LEGACYHTML5
encoding: UTF-8
servlet:
content-type: text/html
server:
port: 8765 #端口为8765
#Eureka设置
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
4、创建 Feign 接口
新建service.AdminService类
通过 @FeignClient("服务名")
注解来指定调用哪个服务。代码如下:
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 = "spring-cloud-provider")//value=服务提供者的名字
public interface AdminService {
@RequestMapping(value = "hi",method = RequestMethod.GET)
//如果前端页有参数传过来,此处必须加上RequestParam注解用于获取参数
public String sayHi(@RequestParam(value = "msg") String msg);
}
5、创建测试用的 Controller
import com.zzr.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.RestController;
@RestController
public class AdminController {
@Autowired//注入Feign接口
private AdminService adminService;
@RequestMapping(value = "sayhi",method = RequestMethod.GET)
public String sayHi(String msg){
return adminService.sayHi(msg);
}
}
测试访问
在浏览器上多次访问 http://localhost:8765/sayhi?msg=前端数据
浏览器交替显示:
地址栏信息为: 前端信息 ,端口号为 : 8762
地址栏信息为: 前端信息 ,端口号为 : 8763
- - -使用熔断器防止服务雪崩- - -
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + Ribbon 和 Feign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。
为了解决这个问题,业界提出了熔断器模型。
Netflix 开源了 Hystrix 组件,实现了熔断器模式,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图:
较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystrix 是 5 秒 20 次) 熔断器将会被打开。
熔断器打开后,为了避免连锁故障,通过 fallback
方法可以直接返回一个固定值。
Ribbon 中使用熔断器
1、引入依赖
ribbon服务的pom.xml
在搭建好的项目的基础上,在pom文件中引入熔断器的依赖
<!--引入熔断器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、开启熔断器
启动类
在 启动类 中增加 @EnableHystrix
注解开启熔断器
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@EnableHystrix //开启熔断器
@EnableDiscoveryClient //注册到服务中心
@SpringBootApplication
public class SpringCloudRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudRibbonApplication.class, args);
}
}
3、在 Service 中增加 @HystrixCommand 注解
service.AdminService
在 Ribbon 调用方法上增加 @HystrixCommand
注解并指定 fallbackMethod
熔断方法
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class AdminService {
@Autowired //注入配置类RestTemplate
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "error")//熔断器注解 失败后回调error方法
public String sayHi(String msg){
//利用RestTemplate向服务提供者获取数据 url:http://服务提供者的名字/请求参数
return restTemplate.getForObject("http://spring-cloud-provider/hi?msg="+msg,String.class);
}
//熔断方法 连不上服务后执行此方法返回,就不会一直阻塞了
public String error(String msg){
return String.format("前端的消息为:%s, 但是请求失败了!!",msg);
}
}
测试熔断器
关闭服务提供者,再次请求http://localhost:8764/hi?msg=前端数据
,浏览器会显示:
Feign 中使用熔断器
1、开启熔断器
Feign 是自带熔断器的,但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码:
application.yml
feign:
hystrix:
enabled: true #开启熔断器
2、创建熔断器类并实现对应的 Feign 接口
/**
* 熔断器类
* 当阻塞时执行这个类中的熔断方法,直接返回
*/
import com.zzr.feign.service.AdminService;
import org.springframework.stereotype.Component;
@Component //交由容器管理
public class AdminServiceHystrix implements AdminService {
//重写Service接口中的方法,当阻塞时执行此类中的方法
@Override
public String sayHi(String msg) {
//直接返回消息,就不会导致一直阻塞
return String.format("前端的消息为:%s, 但是请求失败了!!",msg);
}
}
3、在 Service 中增加 fallback
指定类
import com.zzr.feign.service.hystrix.AdminServiceHystrix;
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 = "spring-cloud-provider")//value=服务提供者的名字
//增加熔断器后的写法 fallback=熔断器类.class 本service类的实现类
@FeignClient(value = "spring-cloud-provider",fallback = AdminServiceHystrix.class)
public interface AdminService {
@RequestMapping(value = "hi",method = RequestMethod.GET)
//如果前端页有参数传过来,此处必须加上RequestParam注解用于获取参数
public String sayHi(@RequestParam(value = "msg") String msg);
}
测试熔断器
关闭服务提供者,再次请求 http://localhost:8765/hi?msg=前端数据
,浏览器会显示:
使用熔断器仪表盘监控
在 Ribbon 和 Feign 项目增加 Hystrix 仪表盘功能,两个项目的改造方式相同
1、在 pom.xml
中增加依赖
<!--熔断器仪表盘 监视仪熔断器状态-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2、开启熔断器仪表盘
在 Application 中增加 @EnableHystrixDashboard
注解,开启熔断器仪表盘
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableHystrixDashboard//开启熔断器仪表盘
@EnableFeignClients //开启Feign功能
@EnableDiscoveryClient //注册到服务中心
@SpringBootApplication
public class SpringCloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignApplication.class, args);
}
}
3、创建 hystrix.stream
的 Servlet 配置
Spring Boot 2.x 版本开启 Hystrix Dashboard 与 Spring Boot 1.x 的方式略有不同,需要增加一个 HystrixMetricsStreamServlet
的配置,代码如下:
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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;
}
}
测试 Hystrix Dashboard
浏览器端访问http://localhost:8765/hystrix
界面如下:
再访问前面的请求,就可以在这里监控到:
什么情况下会触发 fallback
(失败回调-熔断)方法
名字 | 描述 | 触发fallback |
---|---|---|
EMIT | 值传递 | NO |
SUCCESS | 执行完成,没有错误 | NO |
FAILURE | 执行抛出异常 | YES |
TIMEOUT | 执行开始,但没有在允许的时间内完成 | YES |
BAD_REQUEST | 执行抛出HystrixBadRequestException | NO |
SHORT_CIRCUITED | 断路器打开,不尝试执行 | YES |
THREAD_POOL_REJECTED | 线程池拒绝,不尝试执行 | YES |
SEMAPHORE_REJECTED | 信号量拒绝,不尝试执行 | YES |
fallback
方法在什么情况下会抛出异常
名字 | 描述 | 抛异常 |
---|---|---|
FALLBACK_EMIT | Fallback值传递 | NO |
FALLBACK_SUCCESS | Fallback执行完成,没有错误 | NO |
FALLBACK_FAILURE | Fallback执行抛出出错 | YES |
FALLBACK_REJECTED | Fallback信号量拒绝,不尝试执行 | YES |
FALLBACK_MISSING | 没有Fallback实例 | YES |
Hystrix Dashboard 界面监控参数
- - -使用路由网关统一访问接口- - -
在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、熔断器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简单的微服务系统如下图:
在 Spring Cloud 微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(Zuul、Ngnix),再到达服务网关(Zuul 集群),然后再到具体的服。服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理,配置服务的配置文件放在 GIT 仓库,方便开发人员随时改配置。
Zuul 简介
Zuul 的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如 /api/user
转发到到 User 服务,/api/shop
转发到到 Shop 服务。Zuul 默认和 Ribbon 结合实现了负载均衡的功能。
创建路由网关
1、新建一个服务
file–>new–>Module–>Spring Initializer
pom文件中主要是增加了 Zuul 的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2、开启Zuul功能
启动类
在application.yml文件中增加 @EnableZuulProxy
注解开启 Zuul 功能
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableEurekaClient //注册到服务中心
@EnableZuulProxy //开启Zull功能
@SpringBootApplication
public class SpringCloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudZuulApplication.class, args);
}
}
3、配置Zull信息
application.yml
- 设置端口号为:
8769
- 增加 Zuul 配置
spring:
application:
name: spring-cloud-zuul #服务名
server:
port: 8769 #端口号
#Eureka配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#Zull配置
zuul:
routes: #路由设置
api-a: #路由a:当访问/api/a/**时转发到spring-cloud-ribbon
path: /api/a/**
serviceId: spring-cloud-ribbon
api-b: #路由a:当访问/api/b/**时转发到spring-cloud-feign
path: /api/b/**
serviceId: spring-cloud-feign
路由说明:
- 以
/api/a
开头的请求都转发给spring-cloud-ribbon
服务 - 以
/api/b
开头的请求都转发给spring-cloud-feign
服务
4、测试访问
依次运行 SpringCloudEurekaApplication
(注册中心)、SpringCloudProviderApplication
(服务提供者)、SpringCloudRibbonApplication
(Ribbon服务消费者)、SpringCloudFeignApplication
(Feign服务消费者)、SpringCloudZuulApplication
(路由网关)
在浏览器上访问:http://localhost:8769/api/a/hi?msg=前端信息
,此时浏览器会报错(PS:如果一直按照上述步骤进行)!!
原因:我自己的理解,在前面配置Feign接口时,与controller层里面定义的访问路径不一致,导致访问路由转发失败。
解决方案:在Feign服务中,Feign接口与Controller接口中对应的方法的访问路径修改为一致
Feign接口中的方法:
Controller层中的方法
修改完成后重启Feign服务。
测试ribbon服务:
打开浏览器访问:http://localhost:8769/api/a/hi?msg=前端信息
,浏览器显示
地址栏信息为: 前端信息 ,端口号为 : 8762
地址栏信息为: 前端信息 ,端口号为 : 8763
两者交替运行,因为之前配置过负载均衡
测试feign服务:
打开浏览器访问:http://localhost:8769/api/b/hi?msg=前端信息
,浏览器显示
地址栏信息为: 前端信息 ,端口号为 : 8762
地址栏信息为: 前端信息 ,端口号为 : 8763
两者交替运行
至此说明 Zuul 的路由功能配置成功(撒花)!!!
配置网关路由失败时的回调
1、创建处理失败回调的类
创建一个类实现FallbackProvider,并重写其中的方法
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;
/**
* 路由 spring-cloud-feign 失败时的回调
*/
@Component //交由容器管理
public class FeignFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//返回服务名 如果需要所有调用都支持回退,则 return "*" 或 return null
return "spring-cloud-feign";
}
/**
* 如果请求服务失败,则返回指定的信息给调用者
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
/**
* 返回状态码
* 网关向 api 服务请求失败了,但是消费者客户端向网关发起的请求是成功的,
* 不应该把 api 的 404,500 等问题抛给客户端
* 网关和 api 服务集群对于客户端来说是黑盒
*/
@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;
}
};
}
}
2、测试失败回调
测试之前先将服务提供者关闭(不关闭提供者不会出现失败的回调的情况,除非你的服务都部署在远程服务器上,网络波动可能造成),然后访问http://localhost:8769/api/b/hi?msg=前端信息
,页面信息如下:
强行解释:其实客户端发送到服务端的请求是成功的,所以返回的状态码为200,但是可能因为路由转发的过程中发生异常或者路由转发请求失败,从而导致客户端并不能得到想要的结果,但我们不能将400,500等错误信息返回,所以在此处强行甩锅,一律是客户端网络的原因。
使用路由网关的服务过滤功能
Zuul 不仅仅只是路由,还有很多强大的功能,比如它的服务过滤功能,可以用在安全验证方面。
1、创建服务过滤器
新建过滤器类,继承 ZuulFilter
类并在类上增加 @Component
注解就可以使用服务过滤功能了,非常简单方便
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Zull的过滤功能
* 此处模拟登录拦截
*/
@Component //交由容器管理
public class LoginFilter extends ZuulFilter {
/**
* 过滤类型
* 返回一个字符串代表过滤器的类型,在 Zuul 中定义了四种不同生命周期的过滤器类型
* pre:路由之前
* routing:路由之时
* post: 路由之后
* error:发送错误调用
*/
@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获得HttpServletRequest
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//通过 HttpServletRequest 获取请求参数值
String token = request.getParameter("token");
if(token==null){
//如果请求参数为空,表示没有登录,则不进行路由转发,直接返回(拦截)
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(401);//设置返回的响应码 401:没有权限
try {
//设置响应字符串编码格式
HttpServletResponse response=currentContext.getResponse();
response.setContentType("text/html;charset=utf-8");
//将响应字符串写在页面上
currentContext.getResponse().getWriter().write("非法请求");
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
2、测试访问
访问http://localhost://8769/api/b/hi=前端信息
页面如下,因为没有设置token,所以后台将其拦截。
访问http://localhost://8769/api/b/hi=前端信息&token=123
页面如下:
将token设置值后,可以正常访问。
- - -分布式配置中心- - -
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在 Spring Cloud 中,有分布式配置中心组件 Spring Cloud Config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程 Git 仓库中。在 Spring Cloud Config 组件中,分两个角色,一是 Config Server,二是 Config Client。
分布式配置中心服务端
1、创建服务端配置中心
创建一个工程名为 hello-spring-cloud-config
的项目,pom.xml
配置文件如下:
po文件中主要增加了 spring-cloud-config-server
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
2、开启配置服务器功能
启动类
通过 @EnableConfigServer
注解,开启配置服务器功能
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableConfigServer //开启配置服务器功能
@EnableEurekaClient //注册到服务中心 服务提供者
@SpringBootApplication
public class SpringCloudConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConfigApplication.class, args);
}
}
3、增加config相关配置
application.yml
spring:
application:
name: spring-cloud-config #服务名称
cloud:
config:
label: master #仓库分支
server:
git:
uri: ****** #仓库地址 例如:https://github.com/topsale/spring-cloud-config
search-paths: config #存放配置文件的目录
username: ****** #git用户名
password: ****** #git密码
server:
port: 8888 #端口默认为8888,不能修改
#如果要修改,则需要另外创建配置文件bootstrap.yml或者bootstrap.properties,在这里面指定端口
#启动项目加载配置文件时,都会被加载
#Eureka配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
GitHub托管配置文件地址:https://github.com/zzr946/SpringCloud-Config.git
相关配置说明,如下:
spring.cloud.config.label
:配置仓库的分支spring.cloud.config.server.git.uri
:配置 Git 仓库地址(GitHub、GitLab、码云 …)spring.cloud.config.server.git.search-paths
:配置仓库路径(存放配置文件的目录)spring.cloud.config.server.git.username
:访问 Git 仓库的账号spring.cloud.config.server.git.password
:访问 Git 仓库的密码
注意事项:
- 如果使用 GitLab 作为仓库的话,
git.uri
需要在结尾加上.git
,GitHub 则不用
4、测试
将配置文件上传到GitHub后,通过浏览器访问
前提条件是Git仓库已经创建好,并且已经拉取到项目中,并将配置文件在提交到Git。
浏览器端访问:http://localhost:8888//spring-cloud-feign/master
显示如下:
证明配置服务中心可以从远程获取配置信息
在正式开发工程中,配置文件不可能只有一套,即开发环境,测试环境,生产环境的配置肯定不想同,所以我们需要将配置文件表明为何种环境下的配置文件。
我们将上图中的spring-cloud-feign.yml
文件名修改为spring-cloud-feign-dev.yml
,即开发环境下的配置,然后我们浏览器访问http://localhost:8888//spring-cloud-feign/dev/master
,也能够正确读取到配置文件信息。
附:HTTP 请求地址和资源文件映射
http://ip:port/{application}/{profile}[/{label}]
http://ip:port/{application}-{profile}.yml
http://ip:port/{label}/{application}-{profile}.yml
http://ip:port/{application}-{profile}.properties
http://ip:port/{label}/{application}-{profile}.properties
分布式配置中心客户端
1、导入依赖
以Feign服务消费者为例,在其pom文件中增加了 spring-cloud-starter-config
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
启动类上不需要增加任何注解
2、增加 Config Client 相关配置
application.yml
spring:
cloud:
config:
uri: http://localhost:8888
name: spring-cloud-feign #配置文件名 git仓库中的文件名(spring-cloud-feign-dev.yml),不包含环境-dev,不包含文件后缀名
label: master #分支
profile: dev #激活哪个配置,由配置文件后的-dev等指定
#dev:表示开发环境
#test:表示测试环境
#prod:表示生产环境
#其他配置都可以不需要了因为在Git仓库中的配置文件中都已经配置了(云配置)
# application:
# name: spring-cloud-feign #服务发现者的名字
# #客户端分布式配置中心
# thymeleaf: #模板引擎的配置
# cache: false
# mode: LEGACYHTML5
# encoding: UTF-8
# servlet:
# content-type: text/html
#
#server:
# port: 8765 #端口为8765
##Eureka设置
#eureka:
# client:
# service-url:
# default-zone: http://localhost:8761/eureka/
#
#feign:
# hystrix:
# enabled: true #开启熔断器
注意事项:
- 配置服务器的默认端口为
8888
,如果修改了默认端口,则客户端项目就不能在application.yml
或application.properties
中配置spring.cloud.config.uri
,必须在bootstrap.yml
或是bootstrap.properties
中配置,原因是bootstrap
开头的配置文件会被优先加载和配置,切记。
开启 Spring Boot Profile
我们在做项目开发的时候,生产环境和测试环境的一些配置可能会不一样,有时候一些功能也可能会不一样,所以我们可能会在上线的时候手工修改这些配置信息。但是 Spring 中为我们提供了 Profile 这个功能。我们只需要在启动的时候添加一个虚拟机参数,激活自己环境所要用的 Profile 就可以了。
操作起来很简单,只需要为不同的环境编写专门的配置文件,如:application-dev.yml
、application-prod.yml
, 启动项目时只需要增加一个命令参数 --spring.profiles.active=环境配置
即可,启动命令如下:
java -jar spring-cloud-feign-1.0.0-SNAPSHOT.jar --spring.profiles.active=prod
注:打包命令mvn clean package
,执行完毕后再target目录下面会生成打完的JAR包,然后在target目录下运行jar包。
服务启动顺序
1、启动服务注册于发现
2、启动分布式配置
3、启动所有服务提供者
4、启动所有服务消费者
5、启动服务网关
至此,SpringCloud分布式开发五大常用组件,即:
服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器(熔断器)——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config
的基本使用已经学完啦,源码GitHub地址:https://github.com/zzr946/hello-spring-cloud.git
- - -服务链路追踪- - -
ZipKin 简介
ZipKin 是一个开放源代码的分布式跟踪系统,由 Twitter 公司开源,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。它的理论模型来自于 Google Dapper 论文。
每个服务向 ZipKin 报告计时数据,ZipKin 会根据调用关系通过 ZipKin UI 生成依赖关系图,显示了多少跟踪请求通过每个服务,该系统让开发者可通过一个 Web 前端轻松的收集和分析数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈。
创建 ZipKin 服务端
1、新建一个服务
由于SpringBoot2.x版本后,Zipkin官网已经不再推荐自己搭建定制Zipkin,而是直接提供了编译好的jar包。详情可以查看官网:https://zipkin.io/pages/quickstart.html
我们直接运行命令,将官方提供的JAR包下载下来,直接运行,服务就起来了,不需要像之前那样单独在项目中创建一个zipkin的服务。
原来在项目中创建zipkin服务的步骤:
1、创建服务:
主要增加了 3 个依赖,io.zipkin.java:zipkin
、io.zipkin.java:zipkin-server
、io.zipkin.java:zipkin-autoconfigure-ui
<!--引入zipkin相关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!--
引入zipkin相关依赖
注意版本,三者都为2.10.1,由于SpringBoot没有管理zipkin的版本,所以手动添加上去
这三个依赖也手动添加
-->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.10.1</version>
</dependency>
2、开启 Zipkin Server 功能
启动类
通过 @EnableZipkinServer
注解开启 Zipkin Server 功能
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import zipkin.server.internal.EnableZipkinServer;
@EnableEurekaClient //注册到服务中心
@EnableZipkinServer //开启zipkin功能
@SpringBootApplication
public class SpringCloudZipkinApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudZipkinApplication.class, args);
}
}
**3、配置Zipkin Server **
application.yml
spring:
application:
name: spring-cloud-zipkin
server:
port: 9411
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
management:
metrics:
web:
server:
auto-time-requests: false
2、追踪服务
在 所有需要被追踪的项目(包括 Eureka Server) 中增加 spring-cloud-starter-zipkin
依赖
<!--引入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
5、测试追踪
启动全部服务(注意启动顺序:eureka->config->provider->ribbon->feign->zuul),最后运行zipkin的jar文件,这时发现,zipkin的图标竟然变了:
全部启动完毕后,访问localhost:9411/我们就可以进入到管理界面(疯狂刷新其他服务,这里就可以显示访问到的服务):
- - -Spring Boot Admin- - -
随着开发周期的推移,项目会不断变大,切分出的服务也会越来越多,这时一个个的微服务构成了错综复杂的系统。对于各个微服务系统的健康状态、会话数量、并发数、服务资源、延迟等度量信息的收集就成为了一个挑战。Spring Boot Admin 应运而生,它正式基于这些需求开发出的一套功能强大的监控管理系统。
Spring Boot Admin 有两个角色组成,一个是 Spring Boot Admin Server,一个是 Spring Boot Admin Client。
Spring Boot Admin 服务端
1、创建 Spring Boot Admin Server
2、开启 Admin 功能
启动类
通过 @EnableAdminServer
注解开启 Admin 功能
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableAdminServer //开启Admin功能
@SpringBootApplication
public class SpringCloudAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudAdminApplication.class, args);
}
}
3、修改配置文件
spring:
application:
name: spring-cloud-admin-server
zipkin:
base-url: http://localhost:9411
server:
port: 8084
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health,info
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
4、测试访问监控中心
打开浏览器访问:http://localhost:8084 会出现以下界面(因为我服务是启动的,所以监控到有实例)
Spring Boot Admin 客户端
1、创建 Spring Boot Admin Client
admin客户端不需要在启动类上配置
2、修改配置文件
spring:
application:
name: spring-cloud-admin-client
boot:
admin:
client:
url: http://localhost:8084 #客户端端口
zipkin:
base-url: http://localhost:9411 #服务链路追踪
server:
port: 8085 #admin客户端端口号
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #配置中心
3、测试访问
依次启动admin服务端与admin客户端应用,打开浏览器访问:http://localhost:8084 界面显示如下:
由此说明,Admin客户端搭建成功!