创建一个初始化的父工程
- 首先使用IDEA创建一个Maven父工程,下面是父工程的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.visiontalk</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
创建一个注册中心
- 在父工程下创建Module,pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.visiontalk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>child01</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
</project>
- 创建配置文件application.yml
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://127.0.0.1:${server.port}/eureka
spring:
application:
name: server-center
server:
port: 8761
配置属性说明
server.port:当前Eureka Server服务端口。
eureka.client.register-with-eureka:是否将当前的Eureka Server服务作为客户端进行注册。
eureka.client.fetch-fegistry:是否获取其他 Eureka Server服务的数据。
eureka.client.service-url.defaultZone:注册中心的访问地址。
- 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
@SpringBootApplication:声明该类是Spring Boot服务的入口。
@EnableEurekaServer:声明该类是一个Eureka Server微服务,提供服务注册和服务发现功能,即注册中心。
创建一个服务提供者
在父工程下创建一个Module,pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>cn.visiontalk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>childclient01</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
</project>
- 创建服务提供者的配置文件application.yml
server:
port: 8081
spring:
application:
name: provide
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
配置属性说明
spring.application.name:当前服务注册在Eureka Server上的名称。
eureka.client.service-url.defaultZone:注册中心的访问地址。
eureka.instance.prefer-ip-address:是否将当前服务的IP注册到Eureka Server
- 创建服务提供者的启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
将注册中心和服务提供者分别运行后用浏览器打开http://localhost:8761即可看到像下图所示
为服务提供者创建Controller,用来提供服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class HelloController {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
Registration registration;
@GetMapping("/hello")
public String hello() {
List<String> services = discoveryClient.getServices();
for (String service : services) {
System.out.println("service = " + service);
}
String host = registration.getHost();
int port = registration.getPort();
System.out.printf("Host:%s,Port:%d", host, port);
return "hello world";
}
}
重新启动服务提供者,访问地址http://192.168.1.105:8081/hello,浏览器显示如下图
控制台显示如下图
接下来创建服务消费者
为能有多个服务提供者,这里我们创建多个服务提供者的启动入口,点击Edit Configurations
添加命令行参数主要是为了防止端口被占用的问题
接下来在父工程下创建一个Module,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>cn.visiontalk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer01</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
</project>
创建配置文件application.yml
server:
port: 8030
spring:
application:
name: consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
创建控制器作为服务消费者
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/consumer")
public String consumer() {
return restTemplate.getForObject("http://provide/hello", String.class);
}
}
将所有的程序启动入口都运行起来,有浏览器打开http://localhost:8761如下所示
为了知道消费者消费的是哪一个服务提供者提供的服务,这里我将服务提供者的Controller进行更改,
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class HelloController {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
Registration registration;
@GetMapping("/hello")
public String hello() {
List<String> services = discoveryClient.getServices();
for (String service : services) {
System.out.println("service = " + service);
}
String host = registration.getHost();
int port = registration.getPort();
// 只是在这个地方做了小量更改,为了知道是那个提供了服务
String format = String.format("Host:%s,Port:%d\t------>hello world", host, port);
return format;
}
}
用浏览器访问网址http://192.168.1.105:8030/consumer,即可看到如下图所示
上面就是一个用
Ribbon + RestTemplate实现的一个负载均衡的客户端消费者,接下来我们用Feign实现一个负载均衡的消费者。
同样在父工程下创建一个Module,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>cn.visiontalk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feignConsumer01</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
</project>
创建配置文件
server:
port: 8034
spring:
application:
name: feign-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
这里注意要使用@EnableFeignClients注解,启动FeignClient组件的扫描
下面就是编写接口,用@FeignClient注解来标识,value=(服务名ID)
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "provide")
public interface Service {
@GetMapping("hello")
String getInfoByProvide();
}
创建控制器,处理请求
import com.example.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FeignController {
@Autowired
Service service;
@GetMapping("feignconsumer")
public String getInfo() {
return service.getInfoByProvide();
}
}
运行后效果如下:
这里和Ribbon一样实现了负载均衡,但是我们如果现在将服务提供者的一个实例关闭,再次刷新几次浏览器就会发生如下的情况:
因为有一个服务提供者实例被关闭,所以出现消费者访问被拒绝的情况
当是这样的一个信息对用户来说是非常不友好的。为了防止这种情况的发生我们对消费者Feign-consumer进行如下改造:
首先更改配置文件application.yml,如下:
server:
port: 8034
spring:
application:
name: feign-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
# 主要是添加了下面的这一段信息
feign:
hystrix:
enabled: true
feign.hystrix.enabled:是否开启feign的熔断机制
然后创建如下类,实现上面编写的Service接口
import org.springframework.stereotype.Component;
@Component
public class MyService implements Service {
@Override
public String getInfoByProvide() {
return "服务器正在维护,请稍后重试......";
}
}
最后在Service接口上面标的注解添加一个fallback字段
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
// 注意下面的注解,进行了更改了。降级处理
@FeignClient(value = "provide", fallback = MyService.class)
public interface Service {
@GetMapping("hello")
String getInfoByProvide();
}
如果再次出现访问不到的情况,就是下面的样子了。对于用户来说,这样就比较友好的。
Hystrix容错机制
在不改变各个微服务调用关系的前提下,针对错误情况进行预先处理。
- 设计原则
1、服务隔离机制
2、服务降级机制
3、熔断机制
4、提供实时的监控和报警功能
5、提供实时的配置修改功能
Hystrix数据监控需要结合Spring Cloud Actuator 来使用,Actuator提供了对服务的健康监控、数据统计,可以通过hystrix-stream节点获取监控的请求数据,提供了可视化的监控界面。
在父工程下创建Module,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>cn.visiontalk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hystrixconsumer01</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
</project>
创建配置文件
server:
port: 8034
spring:
application:
name: feign-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: 'hystrix.stream'
创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
@EnableHystrixDashboard
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
创建控制器,为了方便这里用到上面的Service接口,创建的
import com.example.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
Service service;
@GetMapping("/index")
public String index() {
return service.getInfoByProvide();
}
}
创建好后启动所需要的程序
- 用浏览器访问下面这个网址,可以看到不懂访问的ping消息,但是并没有内容
http://127.0.0.1:8034/actuator/hystrix.stream - 用打开一个标签页,访问下面这个网址
http://127.0.0.1:8034/index
-
在切换到刚刚的标签页,可以看到类似下面的访问信息,这里需要访问的是一个消费者
2019-07-21_115417.png
- 访问这个网址即可查看可视化信息http://192.168.1.105:8034/hystrix/
2019-07-21_115314.png
总结:启动成功之后,访问http://localhost:{服务端口号}/actuator/hystrix.stream可以监控到请求数据,访问http://localhost:{服务端口号}/hystrix,可以看到可视化的监控界面,输入要监控的地址节点即可看到该节点的可视化数据监控。
Spring Cloud 配置中心
Spring Cloud Config,通过服务端可以为多个客户端提供配置服务。Spring Cloud Config可以将配置文件存储在本地,也可以将配置文件存储在远程Git仓库,
在父工程下创建Module,pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>cn.visiontalk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Serviceconfig01</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
</project>
- 创建配置文件application.yml
server:
port: 8035
spring:
application:
name: config-server
profiles:
active: native
cloud:
config:
server:
native:
search-locations: classpath:/shared
resources 路径下创建 shared文件夹,并在此路径下创建 configclient-dev.yml
server:
port: 8070
foo: foo version 1
- 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class SCApplication {
public static void main(String[] args) {
SpringApplication.run(SCApplication.class, args);
}
}
@EnableConfigServer:声明配置中心
创建客户端读取本地配置中心的配置文件
- 在父工程中创建Module,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hystrixconsumer01</artifactId>
<groupId>cn.visiontalk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nativeconfigclient</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
</project>
- 创建 bootstrap.yml,配置读取本地配置中心的相关信息
spring:
application:
name: configclient
profiles:
active: dev
cloud:
config:
uri: http://localhost:8035
字段说明:
spring.cloud.config.uri:本地Config Server的访问路径
spring.cloud.config.fail-fase:设置客户端优先判断 Config Server获取是否正常。
通过spring.application.name 结合spring.profiles.active拼接目标配置文件名,configclient-dev.yml,Config Server
- 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class NCCApplication {
public static void main(String[] args) {
SpringApplication.run(NCCApplication.class,args);
}
}
- 创建访问控制器
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("native")
public class GetConfig {
@Value("${foo}")
private String foo;
@Value("${server.port}")
private int port;
@GetMapping("getconfig")
public String index() {
return String.format("foo:%s , port:%d", foo, port);
}
}
在这个项目中我们只需要启动配置提供者,和配置消费者即可,因为在配置中我们直接写了配置服务器的ip和端口port
使用hystrix + Ribbon实现负载均衡和断路器
在父工程下创建一个Module,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-myTest</artifactId>
<groupId>top.itreatment</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hystrixTest</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
</project>
- 编写配置文件:
server:
port: 8001
spring:
application:
name: hystrix-test
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
- 创建一个Service类
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 HelloService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "helloFallback")
public String index() {
String body = restTemplate.getForEntity("http://SERVICE-PROVIDER/hello", String.class).getBody();
return body;
}
public String helloFallback() {
return "服务器正在升级,请稍后重试......";
}
}
- 创建访问控制器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.itreatment.service.HelloService;
@RestController
public class HelloController {
@Autowired
HelloService service;
@GetMapping("/hello")
String index() {
return service.index();
}
}
- 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableCircuitBreaker
public class HystrixApplication {
@Bean
@LoadBalanced
RestTemplate template() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
@EnableCircuitBreaker:开启断路器设置
Zuul
- 为减轻运维人员对于路由规则与服务实例的维护麻烦问题
- 对千类似签名校验、登录校验在微服务架构中的冗余问题
通过Zuul即可解决,下面创建一个带有Zuul的项目
首先在父工程下创建一个Module,pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-myTest</artifactId>
<groupId>top.itreatment</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zuulTest</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.3.4.RELEASE</version>
</dependency>
</dependencies>
</project>
- 编写配置文件如下:
server:
port: 1025
spring:
application:
name: api-getway
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-provider
api-b:
path: /api-b/**
serviceId: feign-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
- 创建启动类:
package top.itreatment;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class ZApplication {
public static void main(String[] args) {
SpringApplication.run(ZApplication.class, args);
}
}
-
启动必要的服务,进行测试:
-
上面已经做到了路由转发功能,但是并不具备过滤功能
接下啦,我们进行改进使其具备该功能
创建一个过滤类:
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 javax.servlet.http.HttpServletRequest;
public class AccessFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(AccessFilter.class);
// 过滤类型,代表了过滤的时期,pre表示在路由转发之前进行过滤。
@Override
public String filterType() {
return "pre";
}
// 过滤的顺序,如果在一个时期有多个过滤器,
// 这他们的顺序就是根据该方法返回的值进行排序
@Override
public int filterOrder() {
return 0;
}
// 是否启动过滤功能,可以通过该方法进行确定范围
@Override
public boolean shouldFilter() {
return true;
}
// 过滤的主要的过滤逻辑
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
// 获取请求实例request
HttpServletRequest request = ctx.getRequest();
log.info("send {} request to{}", request.getMethod(),
request.getRequestURL().toString());
// 通过request获取发送的参数
Object accessToken = request.getParameter("accessToken");
if (accessToken == null) {
log.warn("access token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseBody("this is a test !!!");
ctx.setResponseStatusCode(401);
} else {
log.info("access token ok");
}
return null;
}
}
改动启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import top.itreatment.filter.AccessFilter;
@SpringBootApplication
@EnableZuulProxy
public class ZApplication {
public static void main(String[] args) {
SpringApplication.run(ZApplication.class, args);
}
@Bean
AccessFilter accessFilter(){
return new AccessFilter();
}
}
如果有非常多的服务,那我们在配置路由转发时就是一个非常大的工程量了,同时命名也会令人头疼,所以Zuul在这方面非常贴切的为我们提供了默认的转发,当带有Zuul功能的客户端连接到注册中心时就会获取服务清单设置转发的规则就是直接通过服务名进行转发。
举个例子:
这里我们将配置的路由转发规则注释掉
server:
port: 1025
spring:
application:
name: api-getway
#zuul:
# routes:
# api-a:
# path: /api-a/**
# serviceId: service-provider
# api-b:
# path: /api-b/**
# serviceId: feign-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
然后重启Zuul客户端
访问地址http://127.0.0.1:1025/service-provider/hello?accessToken=依旧可以访问到
如果觉得这样一个-横杠,你不太容易接受我们也可以通过向Ioc容器中注入一个Bean即可解决,如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.discovery.PatternServiceRouteMapper;
import org.springframework.context.annotation.Bean;
import top.itreatment.filter.AccessFilter;
@SpringBootApplication
@EnableZuulProxy
public class ZApplication {
public static void main(String[] args) {
SpringApplication.run(ZApplication.class, args);
}
@Bean
AccessFilter accessFilter(){
return new AccessFilter();
}
// 下面就是要注入的Bean,通过这一步,就可以将默认的路由转发规则从`service-provider`变成`service/provider`
@Bean
PatternServiceRouteMapper serviceRouteMapper(){
return new PatternServiceRouteMapper("(?<name>^.+?)-(?<action>.+?)","${name}/${action}");
}
}
注意:在使用zuul.prefix属性时会引发一个zuul内部路由的一个Bug
比如:
如果设置zuul.prefix=/api ,当我们设置转发规则是:
zuul:
routes:
api-b:
path: /api/a/**
serviceId: service-provider
就会出现无法访问的情况
spring zuul也含有转发功能,可以直接转发给自己处理
- 编写配置文件:
server:
port: 1025
spring:
application:
name: api-getway
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-provider
api-b:
path: /api-b/**
url: forward:/local
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
通过
url: forward:进行匹配,当用户请求地址 http://127.0.0.1:1025/api-b/hello ,会转发成http://127.0.0.1:1025/local/hello 。
- 创建一个访问控制器:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("local")
public class HelloController {
@GetMapping("hello")
public String hello(){
return "Hello World Local!";
}
}
Zuul的主要组件是过滤器,路由转发功能也是通过过滤器来实现的。
在Spring Cloud Zuul中实现的过滤器必须包含4个基本特征: 过滤类型、 执行顺序、
执行条件、 具体操作。 这些元素看起来非常熟悉,实际上它就是ZuulFilter接口中定义:
-
filterType: 该函数需要返回一个字符串来代表过滤器的类型, 而这个类型就是
在HTTP请求过程中定义的各个阶段。在Zuul中默认定义了 4 种不同生命周期的过
滤器类型, 具体如下所示。-
pre: 可以在请求被路由之前调用。 -
routing: 在路由请求时被调用。 -
post: 在 routing 和 error 过滤器之后被调用。 -
error: 处理请求时发生错误时被调用。
-
-
filterOrder: 通过int值来定义过滤器的执行顺序, 数值越小优先级越高。 -
shouldFilter: 返回一个boolean值来判断该过滤器是否要执行。 我们可以通
过此方法来指定过滤器的有效范围。 -
run: 过滤器的具体逻辑。 在该函数中, 我们可以实现自定义的过滤逻辑, 来确定
是否要拦截当前的请求, 不对其进行后续的路由, 或是在请求路由返回结果之后,
对处理结果做一些加工等。












网友评论