此篇笔记在上一篇笔记SpringCloud-day1的基础上接着写的
1. Eureka集群
1.1 实现方法说明
在生产环境中,直接部署到多个服务器
但是我现在是开发阶段,就通过不同的端口号来代替服务器!
服务器之间配置一下其他服务器,实现互相复制内容相同!服务提供者和服务消费者都注册到多个集群环境的eureka环境中!
1.2 实现集群
1.2.1 拷贝一份eureka服务端的代码
拷贝一份eureka_server_7001的代码并更改名字为eureka_server_7002
1.2.2 搭建集群
-
主类改名字
为了区分主类,规范命名而已!
-
设置映射hosts
hosts文件路径:C:\Windows\System32\drivers\etc\hosts
127.0.0.1 eureka-7001.com
127.0.0.1 eureka-7002.com -
修改配置文件application.yml
两个eureka的服务端配置基本相同:
- 开发环境中就是端口不同,在配置文件中配置另一方的地址(我这里就是开发环境)
- 生产环境中只需要在一方配置另一方的地址
服务器7001的配置
server:
port: 7001
eureka:
instance:
hostname: eureka-7001.com
client:
registerWithEureka: false #是否要注册到eureka 自己本身就是eureka,无需注册
fetchRegistry: false #表示是否从Eureka Server获取注册信息,自己是eureka服务端,无需获取注册信息
serviceUrl:
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka #单机配置 效果同 http://localhost:7001/eureka
defaultZone: http://eureka-7002.com:7002/eureka/ # 配置集群的另一个服务器的地址,目的是互相复制,保持内容相同
服务器7002的配置
server:
port: 7002 # 使用的端口号
eureka:
instance:
hostname: eureka-7001.com
client:
registerWithEureka: false #是否要注册到eureka 自己本身就是eureka,无需注册
fetchRegistry: false #表示是否从Eureka Server获取注册信息,自己是eureka服务端,无需获取注册信息
serviceUrl:
defaultZone: http://eureka-7001.com:7001/eureka/ # 配置集群的另一个服务器的地址,目的是互相复制,保持内容相同
-
启动并测试
测试方法:启动两个eureka的服务端,浏览器地址栏访问地址:http://eureka-7001.com:7001/
测试结果:我访问的是7001,但是在网页上的DS Replicas这个位置下面看到了eureka-7002.com就代表成功!
1.2.3 服务提供者和服务消费者配置文件修改
由于eureka的服务端做了集群,有两个eureka服务端了。服务提供者和服务消费者也都属于eureka的客户端,所以都要把自己的注册到两个eureka服务端那里!
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka #告诉服务提供者要把服务注册到哪儿
2. 服务负载均衡
2.1 为什么需要负载均衡?
为了提供并发量,同一个服务提供者可以部署多个。这个消费者客户端在调用时要根据一定的负责均衡策略完成负载调用。
2.2 实现负载均衡思路
-
服务提供者集群
由于服务消费者要根据负载均衡策略选择合适的服务提供者,所以服务提供者要进行进群!
-
服务消费者负载均衡调用
2.3 服务提供者集群
2.3.1 步骤分析
- 拷贝一份服务提供者
- 修改自身pom.xml和项目的pom.xml,使其成为子模块!
- 修改入口类名
- 修改配置文件application.yml中的port、defaultZone。注意spring.application.name: 这个名字相同服务的服务提供者的要相同!
- 修改两个服务提供者的controller同一个请求返回的对象不同。用于区分是否进行了负载均衡!
- 测试!重启注册中心,和两个服务提供者!查看是否服务提供者名字后面是否有两个地址,如果是,就代表成功!
2.3.2 实现
- 拷贝一份服务提供者
- 修改自身pom.xml和项目的pom.xml,使其成为子模块!
自身pom.xml
<artifactId>user_provider_8002</artifactId>
项目的pom.xml
<module>user_provider_8002</module>
- 修改入口类名。规范命名!
- 修改配置文件application.yml
user_provider_8001的application.yml
server:
port: 8001
spring:
application:
name: USER-PROVIDER # 注意:不要使用下划线
eureka:
client:
service-url:
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
user_provider_8002的application.yml
server:
port: 8002
spring:
application:
name: USER-PROVIDER # 注意:不要使用下划线
eureka:
client:
service-url:
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
- 修改两个服务提供者的controller同一个请求返回的对象不同。用于区分是否进行了负载均衡!
user_provider_8001
package cn.wangningbo.controller;
import cn.wangningbo.domain.User;
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("/provider/user")
public class UserController {
@GetMapping("/{id}")
public User getById(@PathVariable("id") Long id) {
//以后要通过service操作数据库获取数据,现在只是模拟
return new User(id, "二狗8001");
}
}
user_provider_8002
package cn.wangningbo.controller;
import cn.wangningbo.domain.User;
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("/provider/user")
public class UserController {
@GetMapping("/{id}")
public User getById(@PathVariable("id") Long id) {
//以后要通过service操作数据库获取数据,现在只是模拟
return new User(id, "二狗8002");
}
}
- 测试!
重启注册中心和两个服务提供者,浏览器访问地址:
可以看到Instances currently registered with Eureka下面这个服务名字USER-PROVIDER的服务有两个,UP (2) - localhost:USER-PROVIDER:8002 , localhost:USER-PROVIDER:8001
2.4 消费者负载均衡技术实现
2.4.1 常见的负载均衡技术
-
Ribbon
-
Feign
feign底层还是ribbon,只是进行了封装,让我们以接口的方式进行调用
2.4.2 Ribbon
2.4.2.1简介
Ribbon是一个客户端负载均衡器,它可以按照一定规则来完成多态服务器负载均衡调用,这些规则还支持自定义
Ribbon实现客户端消费者实现负载均衡!
2.4.2.2 负载均衡之Ribbon实现分析
- 创建项目
- 导入jar
- 配置application.yml
- 配置配置类(开启负载均衡)
- 消费者使用负载均衡调用
- 测试
2.4.2.3 Ribbon实现
-
创建项目
我这里偷个懒,就不创建新的了,拷贝一份9001那消费者,改一下就好了!
-
导入jar
<!--依赖于公共代码模块-->
<dependency>
<groupId>cn.wangningbo</groupId>
<artifactId>user_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--客户端负载均衡实现 ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
- 配置application.yml
server:
port: 9002
spring:
application:
name: USER-CONSUMER-RIBBON # 注意不要使用下划线
eureka:
client:
service-url:
#defaultZone: http://localhost:7001/eureka #告诉服务提供者要把服务注册到哪儿
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka # 集群
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
- 配置配置类(开启负载均衡)
@LoadBalanced //开启负载均衡
package cn.wangningbo.config;
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 ConfigBean {
@Bean //配置RestTemplate
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 消费者使用负载均衡调用
通过服务名从注册中心获取服务列表,通过负载均衡调用
controller层调用时的代码
package cn.wangningbo.controller;
import cn.wangningbo.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;// org.springframework.cloud.client.discovery.DiscoveryClient;
private static final String URL_PREFIX = "http://USER-PROVIDER";//通过服务名,真实在做时,通过服务名获取服务列表,再通过负载均衡策略完成调用
@GetMapping("/{id}")
public User getById(@PathVariable("id") Long id) {
// 拼接url
String url = URL_PREFIX + "/provider/user/" + id;
//访问
return restTemplate.getForObject(url, User.class);
}
}
-
测试
-
启动7001、8001、8002、9002
-
打开浏览器,访问http://eureka-7001.com:7001/,查看状态是否都是正常的
-
测试负载均衡。
访问地址:http://localhost:9002/consumer/user/1,不断刷新,可以发现返回的对象时不一样的,是交替着换着来的,说明默认的负载均衡策略是轮询!
2.4.3 负载均衡策略
2.4.4 Feign
2.4.4.1 简介
Feign具有如下特性:
- 可插拔的注解支持,包括Feign注解和JAX-RS注解;
- 支持可插拔的HTTP编码器和解码器;
- 支持Hystrix和它的Fallback;
- 支持Ribbon的负载均衡;
- 支持HTTP请求和响应的压缩。
Feign是用@FeignClient来映射服务的。
Feign是以接口方式进行调用,而不是通过RestTemplate来调用,不需要那么麻烦的拼接字符串。feign底层还是ribbon,但feign使用起来更加舒服!
2.4.4.2 负载均衡之Feign实现分析
-
创建项目
-
导入jar
-
配置application.yml
-
配置配置类(开启负载均衡)
-
公共代码区user_interface修改
注意调用服务的名字和路径配置!要和服务提供者里面访问地址和参数等保持一致。
-
服务消费者使用负载均衡
-
测试
-
疑点解析
2.4.4.3 Feign实现分析实现
-
创建项目
我这里偷个懒,就不创建新的了,拷贝一份9001那消费者,改一下就好了!
-
导入jar
<!--依赖于公共代码模块-->
<dependency>
<groupId>cn.wangningbo</groupId>
<artifactId>user_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--feign的支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 配置application.yml
server:
port: 9003
spring:
application:
name: USER-CONSUMER # 注意:不要使用下划线
eureka:
client:
service-url:
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
- 配置入口类(开启负载均衡)
@EnableFeignClients(basePackages = "cn.wangningbo.client") //不在当前包下面的时候才加这个注解,否则可以省略!
// 它会扫描指定包里面加了@FeignClient的所有的接口,并且为他们产生代理对象并且纳入spring管理,我们就可以通过代理对象远程访问接口
// 如果client在springboot扫描范围(加了@SpringbootAPplication类的当前包,及其子包),可以不用加包名
package cn.wangningbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient //表示是eureka的客户端
@EnableFeignClients(basePackages = "cn.wangningbo.client") //不在当前包下面的时候才加这个注解,否则可以省略!
// 它会扫描指定包里面加了@FeignClient的所有的接口,并且为他们产生代理对象并且纳入spring管理,我们就可以通过代理对象远程访问接口
// 如果client在springboot扫描范围(加了@SpringbootAPplication类的当前包,及其子包),可以不用加包名
public class ConsumerApp9003 {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp9003.class, args);
}
}
- 公共代码区user_interface修改
pom.xml导包
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--客户端feign支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
公共代码去区新建client包,准备接口和类,进行配置
package cn.wangningbo.client;
import cn.wangningbo.domain.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
//最终会根据接口产生代理对象,远程访问服务提供者,就需要制定服务器提供者名称
@FeignClient(value = "USER-PROVIDER", fallbackFactory
= UserClientHystrixFallbackFactory.class)
@RequestMapping("/provider/user")
public interface UserClient {
@RequestMapping("/{id}")
///provider/user/{id} 通过它来唯一确定我们要调用远程方法
User getUserById(@PathVariable("id") Long id);
}
package cn.wangningbo.client;
import cn.wangningbo.domain.User;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class UserClientHystrixFallbackFactory implements FallbackFactory<UserClient> {
public UserClient create(Throwable throwable) {
return new UserClient() {
public User getUserById(Long id) {
return new User(id, "用户不存在");
}
};
}
}
- 服务消费者controller调用
package cn.wangningbo.controller;
import cn.wangningbo.client.UserClient;
import cn.wangningbo.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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/user")
public class UserController {
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public User getById(@PathVariable("id") Long id) {
return userClient.getUserById(id);
}
}
- 测试
- 启动一个注册中心服务端(7001)
- 启动两个服务提供者(8001和8002)
- 启动feign的负载均衡,进行测试。(9003)
打开浏览器,访问http://eureka-7001.com:7001/,查看状态是否都是正常的
访问地址:http://localhost:9003/consumer/user/1,不断刷新,可以发现返回的对象时不一样的,是交替着换着来的,说明默认的负载均衡策略也是轮询
- 疑点解析
- client为什么不放到自己实现里面?
要放到公共代码里面
这个接口给很多服务器消费者进行调用,如果写在其中一个消费者里面,其他的人就使用不了.
如果client在springboot扫描范围(加了@SpringbootAPplication类的当前包,及其子包),可以不用加包名
3. Hystrix断路器
3.1 为什么需要Hystrix断路器
在复杂的分布式架构的应用程序有很多的依赖,都会不可避免地在某些时候失败。高并发的依赖失败时如果没有隔离措施,当前应用服务就有被拖垮的风险。俗称的雪崩现象!
当然,上面这种结果不是我们想要的,不允许出现应用服务就有被拖垮现象,对依赖进行隔离,则出现断路器的解决方案m!
Hystrix是保证微服务群健壮框架,做了隔离,熔断,降级等操作.最终达到不会由于某一个服务出问题而导致雪崩现象,让整体群死掉。
3.2 Hystrix简介
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。
Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。
-
资源隔离(限流)
包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
-
熔断
当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
-
降级机制
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
-
缓存
提供了请求缓存、请求合并实现
3.2.1 原理分析
Hystrix使用命令模式(继承HystrixCommand(隔离)类)来包裹具体的服务调用逻辑(run方法), 并在命令模式中添加了服务调用失败后的降级逻辑(getFallback).
同时我们在Command的构造方法中可以定义当前服务线程池和熔断器的相关参数.
在使用了Command模式构建了服务对象之后, 服务便拥有了熔断器和线程池的功能.
3.2.2 资源隔离&限流
- 线程池隔离模式
- 信号量隔离模式
3.2.3 服务熔断
-
正常情况下,断路器处于关闭状态(Closed)
-
如果调用持续出错或者超时,电路被打开进入熔断状态(Open),后续一段时间内的所有调用都会被拒绝(Fail Fast)
-
一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试:
- 如果调用仍然失败,则回到熔断状态。
- 如果调用成功,则回到电路闭合状态
-
熔断的参数配置
- circuitBreaker.requestVolumeThreshold //滑动窗口的大小,默认为20
- circuitBreaker.sleepWindowInMilliseconds //过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
- circuitBreaker.errorThresholdPercentage //错误率,默认50%
-
对这三个熔断参数的解释
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
3.2.4 服务降级
有了熔断,就得有降级。所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。
3.3 Hystrix实现(服务提供者实现)
3.3.1 Hystrix实现步骤分析
Hystrix是在服务提供者实现的!
- 拷贝一份服务端代码并进行配置!
- pom.xml导包(断路器)
- 主类使用注解@EnableHystrix表示开启断路器支持
- 服务提供者接口方法那里加注解@HystrixCommand(fallbackMethod="方法名"),断路器触发以后就会执行这个方法
- 测试
- 注意点
- 熔断是在服务提供方做
- 调用时不能使用feign调用(有自己熔断机制),要用ribbon调用
3.3.2 Hystrix实现
-
拷贝一份服务端代码
我这里拷贝了8001,并进行修改使其成为子模块!并重命名为user_provider_8003_Hystrix!修改配置文件application.yml的端口为8003!
-
pom.xml导包
<!--断路器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 主类使用注解@EnableHystrix表示开启断路器支持
package cn.wangningbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableEurekaClient //表示是eureka的客户端
@EnableHystrix //表示开启断路器支持
public class ProviderApp8003 {
public static void main(String[] args) {
SpringApplication.run(ProviderApp8003.class, args);
}
}
- 服务提供者接口方法那里加注解@HystrixCommand(fallbackMethod="方法名"),断路器触发以后就会执行这个方法
package cn.wangningbo.controller;
import cn.wangningbo.domain.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
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("/provider/user")
public class UserController {
@GetMapping("/{id}")
@HystrixCommand(fallbackMethod = "fallbackGet")//方法里面触发断路器就会执行这个fallbackGet方法
public User getById(@PathVariable("id") Long id) {
//手动设置报错使其触发断路器
if (id == 2) {
throw new RuntimeException("报错!");
}
//以后要通过service操作数据库获取数据,现在只是模拟
return new User(id, "二狗8003");
}
public User fallbackGet(Long id) {
return new User(id, "用户不存在!");
}
}
-
测试
启动7001,8003,9002!
浏览器地址栏访问:http://localhost:9002/consumer/user/1,可以正常返回数据!
浏览器地址栏访问:http://localhost:9002/consumer/user/2,则出发了断路器,返回的是断路器的数据!
3.4 Feign实现(feign服务消费者实现)
3.4.1 使用原因
-
原因
每个方法都要加回调并且耦合
-
解决方案
可以使用spring面向切面编程,为feign的接口创建一个代理对象,完成对服务调用,当发现熔断后就调用同名托底方法.
3.4.2 Feign实现步骤分析
feign是在服务消费者实现
- 拷贝一份消费端代码(拷贝的是9003,那里的controller使用的feign)
- pom.xml导包(没有额外的包,使用9003里面的包就够了)
- 配置application.yml
- 主类使用注解@ComponentScan扫描包
- 公共代码区实现
- 消费者的controller那里配置@FeignClient调用服务端
- 测试
3.4.3 Feign实现
- 拷贝一份消费端代码(拷贝9003)
- pom.xml导包(都是原来的包)
<!--依赖于公共代码模块-->
<dependency>
<groupId>cn.wangningbo</groupId>
<artifactId>user_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--feign的支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 配置application.yml
server:
port: 9004
spring:
application:
name: USER-CONSUMER # 注意:不要使用下划线
eureka:
client:
# registerWithEureka: false # 不注册到Eureka,不在注册中心显示
service-url:
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
feign:
hystrix:
enabled: true # 开启熔断支持
client:
config:
default: #服务名,填写default为所有服务
connectTimeout: 3000 # 连接超时时间(单位:毫秒)
readTimeout: 3000 # 读取超时时间(单位:毫秒)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 # 被该调用方的所有方法的默认超时时间
- 主类使用注解@ComponentScan扫描包(如果接口包比较规范,则不需要加)
- 公共代码区实现
package cn.itsource.springcloud.client;
import cn.itsource.springcloud.domain.User;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class UserClientHystrixFallbackFactory implements FallbackFactory<UserClient> {
public UserClient create(Throwable throwable) {
return new UserClient() {
public User getUserById(Long id) {
return new User(id,"用户不存在");
}
};
}
}
package cn.wangningbo.client;
import cn.wangningbo.domain.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
//最终会根据接口产生代理对象,远程访问服务提供者,就需要制定服务器提供者名称
@FeignClient(value = "USER-PROVIDER", fallbackFactory
= UserClientHystrixFallbackFactory.class)
@RequestMapping("/provider/user")
public interface UserClient {
@RequestMapping("/{id}")
///provider/user/{id} 通过它来唯一确定我们要调用远程方法
User getUserById(@PathVariable("id") Long id);
}
-
测试
启动7001,8001,9004(刚才拷贝的那个)!
浏览器地址栏访问:http://localhost:9004/consumer/user/1,可以正常拿到值
关闭8001服务提供者,这时候再浏览器访问http://localhost:9004/consumer/user/1,就会连接超时,会触发断路器,返回的就职托底数据!
4. Zuul路由网关
4.1 简介
Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门,也要注册入Eureka.
其存在意义在于,将"1对N"问题 转换成了"1对1”问题(路由),同时在请求到达真正的微服务之前,可以做一些预处理(过滤),比如:来源合法性检测,权限校验,反爬虫之类...
官网地址: https://github.com/Netflix/zuul
4.2 基本配置
4.2.1 创建模块zuul_gatewar_1299
4.2.2 导入pom.xml
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--网关也要注册到Eureka中-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--zuul网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
4.2.3 配置application.yml
server:
port: 1299
spring:
application:
name: ZUUL-GATEWAY
eureka:
client:
service-url:
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka # 集群
instance:
prefer-ip-address: true # 在注册中心显示Eureka客户端真实ip地址
4.2.4 入口类注解@EnableZuulProxy //开启网关支持
package cn.wangningbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy //开启网关支持
public class ZuulGatewar1299 {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewar1299.class, args);
}
}
4.2.5 启动测试
启动eureka服务端、eureka的客户端服务提供者客、zuul
浏览器访问:http://localhost:8001/provider/user/1
浏览器访问:http://localhost:1299/user-provider/provider/user/1
发现这两个地址都可以拿到数据,成功!
注意:user-provider是服务中心服务提供者应用名字的小写形式
4.3 路由访问映射规则
-
进行安全加固
不用服务名,映射路径
-
忽略原来服务名访问
规定不可以使用原来模式访问
-
加上统一前缀
访问数据时候使用统一的前缀
实现以上几点,只需修改zuul的application.yml即可
zuul:
routes:
myUser.serviceId: user-provider # 服务名
myUser.path: /myUser/** # 把myUser打头的所有请求都转发给user-provider
ignored-services: "*" # 所有服务都不允许以服务名来访问
prefix: "/services" # 加一个统一前缀
可以进行再次测试!
浏览器地址栏访问:http://localhost:1299/services/myUser/provider/user/1可以拿到数据
浏览器地址栏访问:http://localhost:1299/services/user-provider/provider/user/1却拿不到数据
成功!
4.4 过滤器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
4.4.1 ZuulFilter
ZuulFilter是过滤器的顶级父类。
其最重要的方法有4个,如下:
- shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
- run:过滤器的具体业务逻辑。
- filterType:返回字符串,代表过滤器的类型。包含以下4种:
- pre:请求在被路由之前执行
- routing:在路由请求时调用
- post:在routing和errror过滤器之后调用
- error:处理请求时发生错误调用
- filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
4.4.2 过滤器执行周期
-
正常流程:
请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
-
异常流程:
- (1)整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- (2)如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- (3)如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
4.4.3 使用场景
使用场景比较多,我举例一下几个:
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计:pre和post结合使用。
4.4.4 自定义过滤器(继承ZuulFilter)
-
实现分析
自己新建一个过滤器类,继承于ZuulFilter,实现里面的四个方法,在run方法里面写过滤的逻辑!然后给这个过滤器类打上注解,使其能被扫描到,这个自定义过滤器就生效了!
-
实现举例
这里我使用自定义过滤器模拟一个登录拦截器!使用postman进行测试!
基本逻辑:如果request请求头携带token参数,则认为请求有效,放行。
package cn.wangningbo.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class LoginFilter extends ZuulFilter {
// 过滤器类型
public String filterType() {
return "pre";
}
//过滤器顺序
public int filterOrder() {
return 1;
}
// 是否启用此过滤器
public boolean shouldFilter() {
return true;
}
// 过滤逻辑
public Object run() throws ZuulException {
// 如果request请求头携带token参数,则认为请求有效,放行。
RequestContext currentContext = RequestContext.getCurrentContext();
String token = currentContext.getRequest().getHeader("token");
if (null == token || StringUtils.isEmpty(token)) {
// 拦截
currentContext.setSendZuulResponse(false);
// 设置一个状态吗
currentContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
}
// 放行
return null;
}
}
postman进行测试:没有token参数时,访问失败;反之则成功!
4.5 负载均衡与熔断
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置。
在配置文件application.yml里面配置一下即可!
zuul:
retryable: true # 是否重试
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 3000 # 设置所有有熔断默认超时时长:3000ms
测试负载均衡和熔断是否成功!
网友评论