1.回顾代码
1.1整体架构
1.2父工程
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>cn.itcast</groupId>
<artifactId>cloud-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>user-service</module>
<module>consumer-demo</module>
<module>eureka-server</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
<mapper.starter.version>2.0.3</mapper.starter.version>
<mysql.version>5.1.32</mysql.version>
<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
</properties>
<!-- dependency管理器中所有子工程都有该依赖-->
<dependencyManagement>
<dependencies>
<!--springcloud版本依赖项目-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--通用mapper启动器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.3user-service
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>cloud-demo</artifactId>
<groupId>cn.itcast</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-service</artifactId>
<dependencies>
<!-- Spring工程的web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
cn.itcast.UserApplication
package cn.itcast;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("cn.itcast.user.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
cn.itcast.pojo.User
package cn.itcast.user.pojo;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Data
@Table(name="tb_user")
public class User {
//id
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
//用户名
// @Column(name = "username")
private String username;
//密码
private String password;
private String phone;
private Date created;
}
cn.itcast.mapper.UserMapper
package cn.itcast.user.mapper;
import cn.itcast.user.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
}
cn.itcast.service.UserService
package cn.itcast.user.service;
import cn.itcast.user.mapper.UserMapper;
import cn.itcast.user.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id){
/*try {
Thread.sleep(2000l);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return userMapper.selectByPrimaryKey(id);
}
}
cn.itcast.controller.UserController
package cn.itcast.user.controller;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class Controller {
@Autowired
private UserService userService;
//@PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id){
return userService.queryById(id);
}
}
application.yml
server:
port: 8081
spring:
application:
name: user-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost/yum6
username: root
password: 123456
mybatis:
type-aliases-package: cn.itcast.user.pojo
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
#每隔30s发送一次心跳
lease-renewal-interval-in-seconds: 30
#最小过期时长,每隔30秒发一次心跳,如果隔了90s还没有发送心跳,就挂了
lease-expiration-duration-in-seconds: 90
1.4consumer-server
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>cloud-demo</artifactId>
<groupId>cn.itcast</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-demo</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
</project>
cn.itcast.ConsumerApplication
package cn.itcast;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
//启动类
//@EnableCircuitBreaker
//@EnableDiscoveryClient
//@SpringBootApplication
@SpringCloudApplication
public class ConsumerApplication {
//需要调用,所以需要这个对象
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
cn.itcast.consumer.pojo.user
package cn.itcast.consumer.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class User {
//id
private Long id;
//用户名
// @Column(name = "username")
private String username;
//密码
private String password;
private String phone;
private Date created;
}
cn.itcast.consumer.web.ConsumerController
package cn.itcast.consumer.web;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("consumer")
//该类中所有方法默认失败时调用方法
@DefaultProperties(defaultFallback = "queryByIdFallback")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
public String queryById(@PathVariable("id") Long id){
//user-service是服务id
String url = "http://user-service/user/"+id;
//返回的对象转成json字符串
String user = restTemplate.getForObject(url, String.class);
return user;
}
/* //集群+失败处理=最终方案
//启用降级和线程隔离,一旦失败,会去找@DefaultProperties中配置的方法
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
//熔断器关闭后,失败次数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
//熔断器关闭后,休眠时间
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
})
@GetMapping("{id}")
public String queryById(@PathVariable("id") Long id){
//模拟熔断环境测试,id为偶数即请求失败,为奇数就成功
if(id%2 == 0){
throw new RuntimeException("");
}
//user-service是服务id
String url = "http://user-service/user/"+id;
//返回的对象转成json字符串
String user = restTemplate.getForObject(url, String.class);
return user;
}*/
public String queryByIdFallback(){
return "不好意思,服务器太拥挤了!";
}
/* //方法一:
//集群+失败处理
//失败容错的指令,即成功时使用该方法,失败时调用注解中参数指的方法。要求失败和成功时的两个方法返回值和参数完全一样
@HystrixCommand(fallbackMethod = "queryByIdFallback" )
@GetMapping("{id}")
public String queryById(@PathVariable("id") Long id){
//user-service是服务id
String url = "http://user-service/user/"+id;
//返回的对象转成json字符串
String user = restTemplate.getForObject(url, String.class);
return user;
}*/
/*//最终方案
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id){
//user-service是服务id
String url = "http://user-service/user/"+id;
User user = restTemplate.getForObject(url, User.class);
return user;
}*/
/* 方法一:
@Autowired
private RibbonLoadBalancerClient client;
//修改后
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id){
//根据服务id获取实例,该方法帮我们实现了负载均衡,默认是轮询。
ServiceInstance instance = client.choose("user-service");
...
User user = restTemplate.getForObject(baseUrl, User.class);
return user;
}
*/
/* 修改前
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id){
//根据服务id获取实例
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
// 因为只有一个UserService,因此我们直接get(0)获取
ServiceInstance instance = instances.get(0);
//从实例中取出ip和端口
String url = instance.getHost();
int port = instance.getPort();
String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"+id;
System.out.println(baseUrl);
User user = restTemplate.getForObject(baseUrl, User.class);
return user;
}*/
}
application.yml
server:
port: 8088
spring:
application:
name: consumer-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
#要不要拉取服务
fetch-registry: true
#拉取周期
registry-fetch-interval-seconds: 3
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
1.5eureka-server
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>cloud-demo</artifactId>
<groupId>cn.itcast</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
cn.itcast.EurekaServer
package cn.itcast;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class);
}
}
application.yml
server:
port: 10086
#服务名
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
register-with-eureka: false
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
server:
eviction-interval-timer-in-ms: 300000
enable-self-preservation: false
2.Feign
由于调用者中的url是:String url = “http://user-service/user/”+id;其中写死了user。
feign可以把正常的Rest请求隐藏起来,让别人看不到,让用户以为是本地请求。伪装成类似SpringMVC的Controller一样。
分析:
想要写这些代码,需要知道四个信息
- 请求url
- 请求参数
- 请求类型
- 返回结果
这上述这段代码中都有这四个信息。
feign利用SpringMVC的注解来取识别请求的信息,从而帮助你自动进行远程调用,而不需要自己去写。
2.1快速入门
2.1.1引入依赖
consumer-service的pom.xml中加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
早期版本不叫openfeign,而是feign。
2.1.2注入注解启用feign
在ConsumerApplication中加入注解@EnableFeignClients
package cn.itcast;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
//启动类
//@EnableCircuitBreaker
//@EnableDiscoveryClient
//@SpringBootApplication
@EnableFeignClients
@SpringCloudApplication
public class ConsumerApplication {
//需要调用,所以需要这个对象
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
2.1.3配置
不需要配置,他是通过springMVC的注解得知那些信息。
2.1.4编写代码
包:cn.itcast.consumer.client
接口:UserClient
package cn.itcast.consumer.client;
import cn.itcast.consumer.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("user-service")
public interface UserClient {
@GetMapping("user/{id}")
User queryById(@PathVariable("id")Long id);
}
2.1.5修改ConsumerController
package cn.itcast.consumer.web;
import cn.itcast.consumer.client.UserClient;
import cn.itcast.consumer.pojo.User;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("consumer")
//该类中所有方法默认失败时调用方法
@DefaultProperties(defaultFallback = "queryByIdFallback")
public class ConsumerController {
@Autowired
private UserClient userClient;
//集群+熔断+feign
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id){
return userClient.queryById(id);
}
public String queryByIdFallback(){
return "不好意思,服务器太拥挤了!";
}
2.1负载均衡
feign底层已经依赖的ribbon,使用方法和之间讲解的一样。
超时时长
l连接超时时长默认是1s。
读取超时时长也默认是1s。
ribbon:
ConnectionTimeOut: 500
ReadTimeOut: 100
2.2hystix支持
feign底层已经依赖的ribbon、hystrix。但是他没有走spring的熔断,所以使用方法跟之前不太一样。
hystri在feign中默认是关闭的,开启需要配置。
2.1.1开启
feign:
hystrix:
enabled: true
2.1.2注解
在启动类上加上@SpringCloudApplication
package cn.itcast;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
//启动类
//@EnableCircuitBreaker
//@EnableDiscoveryClient
//@SpringBootApplication
@EnableFeignClients
@SpringCloudApplication
public class ConsumerApplication {
//需要调用,所以需要这个对象
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
2.1.3在接口上加上熔断,并写一个实现类去实现它
熔断需要自己去配,需要写一个类来做对于feign的熔断配置,这个类必须实现写的UserClient。
package cn.itcast.consumer.client;
import cn.itcast.consumer.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "user-service",fallback = UserClientFallBack.class)
public interface UserClient {
@GetMapping("user/{id}")
User queryById(@PathVariable("id")Long id);
}
注意:
这段代码中最重要的就是
@FeignClient(value = “user-service”,fallback = UserClientFallBack.class)
表示熔断器,其中请求失败,熔断器关闭进入休眠时间时,请求访问进入fallback指代的参数方法,执行该方法。
package cn.itcast.consumer.client;
import cn.itcast.consumer.pojo.User;
import org.springframework.stereotype.Component;
//熔断接口的实现类,在这里面写熔断逻辑,并将其注解到Spring里
@Component
class UserClientFallBack implements UserClient {
@Override
public User queryById(Long id) {
User users = new User();
users.setUsername("未知用户!");
return users;
}
}
注意:
该方法就是请求失败,熔断器关闭进入休眠时间时,请求访问时执行该方法。
测试:
2.4请求压缩
适应于请求比较大,比如文件的上传下载请求。
通过以下参数开启请求与相应的压缩功能:
feign:
compression:
request:
enable:true
response:
enable:true
同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:
feign:
compression:
request:
enable:true
mime-type: text/html,application/xml,application/json #设置压缩的数据类型
min-request-size:2048 #设置触发压缩的大小下限
3.Zuul网关
3.1简介
在微服务架构中,Zuul就是守门的大Boss。
3.2Zuul加入后的架构
不管是来自客户端的请求还是服务内部调用,一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现鉴权、动态路由等操作。Zuul就是我们服务的统一入口。
要想保证服务不挂,还要限流:限制用户的流量。
3.3快速入门
3.3.1新建工程
3.3.2加入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
3.3.3编写启动类并加上注解
包:cn.itcast
类:GatewayApplication
注解:@EnableZuulProxy
package cn.itcast;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class);
}
}
3.3.4编写配置
zuul要有路由转发功能,就必须确切的知道每个请求所对应的是那个微服务。即路由规则,需要人为配置。
server:
port: 10010
zuul:
#路由规则,是map结构,key就是路由id,只要不重复就可以,value
routes:
hehe:
#匹配路径
path: /user-service/**
#匹配路径映射的实际路径
url: http://127.0.0.1:8081
path是访问路径中的匹配路径,只要路径中有匹配路径就映射到url这个实际路径。但是要注意,匹配路径只是用来匹配的,url才是实际路径,所以访问路径中完整路径为匹配路径+所要访问的controller路径。
3.3.5测试
上面出现一个问题就是映射路径写死了。
所以将来如果一个服务是集群,就出现问题。
所以在配置的时候,不应该是面向url的配置,应该是面向服务的配置。
3.4面向服务的
优化
3.4.1加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
完整的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>cloud-demo</artifactId>
<groupId>cn.itcast</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
3.4.2修改配置
server:
port: 10010
eureka:
client:
service-url:
defaultZone: http://127.0.0.1/10086/eureka
zuul:
#路由规则,是map结构,key就是路由id,只要不重复就可以,value
routes:
#路由id
user-service:
#匹配路径
path: /user-service/**
#映射到服务id
serviceId: user-service
当用户请求到匹配路径,会映射到serviceid对应的服务,但是这个服务没有具体的ip地址,其底层就会调用eureka去拉取服务列表,利用负载均衡算法,动态的获取服务的ip地址。
在zuul的底层已经引入了Ribbon。所以已经实现了负载均衡。
3.4.3测试
3.4.4默认路由规则
简化配置
key=服务id,value=映射路径
server:
port: 10010
eureka:
client:
service-url:
defaultZone: http://127.0.0.1/10086/eureka
zuul:
#路由规则,是map结构,key就是路由id,只要不重复就可以,value
routes:
user-service: /user-service/**
3.4.5路由前缀
在配置文件中并没有配置consumer-service,但是还是可以访问。
因为该配置方法其实zuul已经默认配置了,它通过eureka,拉取服务,将eureka上的所有服务都默认配置成该规则。所以不需要配置也可以实现路由转发。
但是默认服务存在问题:
- 他将所有服务都暴露了。
- 服务匹配url过于复杂。
- 匹配路径和真实路径重复,eg:/user/user/9,user重复。
解决前两个问题的办法:
zuul:
routes:
#默认匹配url是/user-service/**,简化匹配路径url。
user-service: /user/**
#配置不想暴露的服务
ignored-services:
- consumer-service
此时再访问:
解决这三个问题的办法:
zuul:
routes:
user-service:
#匹配路径
path: /user/**
#映射到服务id
serviceId: user-service
strip-prefix: false
#配置不想暴露的服务
ignored-services:
- consumer-service
区分全局配置去除前缀和在配置单个服务时的去除前缀。
server:
port: 10010
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
spring:
application:
name: gateway
zuul:
routes:
#默认匹配url是/user-service/**,简化匹配路径url。
user-service:
path: /user/**
servicedId: user-service
#去除前缀,此时只针对于user-service
strip-prefix: false
#全局配置不想暴露的服务
ignored-services:
- consumer-service
prefix: /api
#全局配置去除前缀,此时针对的是prefix,不加该配置的话,访问就应该是、spi/匹配路径/真实url
strip-prefix: false
3.5过滤器
zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过zuul提供的过滤器来实现的。
3.5.1ZuulFilter
ZuulFilter是过滤器的顶级父类,其中最重要的方法:
//过滤器类型:pre-请求在被路由之前执行;routing-在路由请求时调用;post-在routing和error过滤器之后调用;error-处理请求时发生错误调用。
public String filterType() ;
//过滤器优先级。数字越小优先级越高。
public int filterOrder();
//要不要过滤
public boolean shouldFilter();
//过滤逻辑,过滤器的具体业务逻辑。
public Object run() throws ZuulException;
3.5.2过滤器执行生命周期
正常流程:
请求到达首先会经过pre类型过滤器,而后到达routin类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器,而后返回相应。
异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给post过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终会进入post过滤器,而后返回。
- 如果是post过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同时,请求不会再到达post过滤器了。
3.5.3使用场景
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,就直接拦截了。
- 异常处理:一般会放在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计:pre和post结合使用。
3.5.4自定义过滤器
模拟一个登陆的校验。
基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
package cn.itcast.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import javax.servlet.http.HttpServletRequest;
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER -1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//获取请求上下文
RequestContext rxt = RequestContext.getCurrentContext();
//获取request
HttpServletRequest request = rxt.getRequest();
//获取请求参数access-token
String token = request.getParameter("access-token");
//判断是否存在
if(StringUtils.isBlank(token)){
//不存在,未登陆,就拦截
rxt.setSendZuulResponse((false));
//状态码403
rxt.setResponseStatusCode(HttpStatus.SC_FORBIDDEN);
}
return null;
}
}
3.4负载均衡和熔断
Zuul中默认已经继承了Ribbon负载均衡和Hystrix熔断机制,但是所有的超时策略都是走的默认值,比如熔断超时时间只有1.5s,就很容触发了,因为我们建议手动配置。
hystrix:
command:
default:
execution:
islocation:
thread:
timeoutInMilliseconds: 6000
ribbon:
ConnectionTimeout: 500
ReadTimeout: 2000
Ribbon的超时时长真实值是(read+connection)*2,必须小于hystrix的超时时长。
3.5Zuul的高可用
启动多个Zuul服务就可以了,他会自动高可用。
当启动多个时,它会自动注册到Eureka,形成集群。如果是服务内部访问,你访问Zuul,自动负载均衡,没问题。但是Zuul更多是外部访问,用户他们无法通过Eureka进行负载均衡。此时,我们会使用其他网关,来对Zuul进行代理,比如Nginx。
spring-cloud-config:统一配置中心,自动去git拉取最新的配置,缓存。使用Git的Webhook钩子,去通知配置中心,说配置发生了变化没配置中心会通过消息总线去通知所有的微服务,更新配置。
spring-cloud-bus:消息配置总线。
spring-cloud-stream:消息通信
spring-cloud-hystrix-dashboard:容错统计,形成图形化界面
spring-cloud-sleuth:链路追踪,结合Zipkin。
他是一个独立的微服务。config-server。可以把配置都提交到git中,其中公用的部分