SpringCloudNetflix组件整合
Eureka注册中心
Eureka是什么
Eureka是netflix的一个子模块,也是核心模块之一,Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现和注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务,而不需要修改服务调用的配置文件了,功能类似于dubbo的注册中心,比如zookeeper。
原理
Eureka是一个服务注册与发现组件,简单说就是用来统一管理微服务的通信地址,它包含了EurekaServer服务端(也叫注册中心)和EurekaClient客户端两部分组成,EureakServer是独立的服务,而EurekaClient需要集成到每个微服务中。
服务注册:微服务(EurekaClient)在启动的时候会向EureakServer提交自己的通信地址清单如:服务名,ip,端口,在EurekaServer会形成一个微服务的通信地址列表。
服务发现:微服务(EurekaClient)会定期的从EureakServer拉取一份微服务通信地址列表缓存到本地。一个微服务在向另一个微服务发起调用的时候会根据目标服务的服务名找到其通信地址清单,然后基于HTTP协议向目标服务发起请求微服务(EurekaClient)采用“心跳”机制向EureakServer发请求进行服务续约,其实就是定时向。EureakServer发请求报告自己的健康状况,告诉EureakServer自己还活着,不要把自己从服务地址清单中掉,那么当微服务(EurekaClient)宕机未向EureakServer续约,或者续约请求超时,注册中心机会从地址清单中剔除该续约失败的服务。
Eureka注册中心搭建
创建项目
创建一个普通maven项目
eureka-server-1010
<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-server</artifactId>
</dependency>
yml配置
server:
port: 1010
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false #是否要注册到eureka
fetchRegistry: false #表示是否从Eureka Server获取注册信息
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机配置
主类
@SpringBootApplication
@EnableEurekaServer //标识是eureka服务端
public class EnrekaServerApplication_7001 {
public static void main(String[] args) {
SpringApplication.run(EnrekaServerApplication_7001.class);
}
}
服务提供者注册到Eureka
<!--eureka客户端支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
client:
service-url:
defaultZone: http://localhost:1010/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
启动类启用Eureka
@SpringBootApplication
//@EnableEurekaClient//这个表示开启eureka客户端,万一我的注册中心编程nacas
@EnableDiscoveryClient //通用性更好,以后可以切换支持主流注册中心
public class UserProviderApplication_8001 {
public static void main(String[] args) {
SpringApplication.run(UserProviderApplication_8001.class);
}
}
服务消费者从Eureka调用服务
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
client:
service-url:
defaultZone: http://localhost:1010/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
入口类用Eureka客户端
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableEurekaClient
public class UserConsumerAppliction_9001 {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
public static void main(String[] args) {
SpringApplication.run(UserConsumerAppliction_9001.class);
}
}
整改服务调用
@Autowired
private DiscoveryClient discoveryClient;// Eureka客户端,可以获取到服务实例信息
// String baseUrl = "http://localhost:8081/user/";
// 根据服务名称,获取服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
// 因为只有一个UserService,因此我们直接get(0)获取
ServiceInstance instance = instances.get(0);
// 获取ip和端口信息
String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/";
this.restTemplate.getForObject(baseUrl + id, User.class)
还使用没有负载均衡
服务消费者常见负载均衡实现技术
Ribbon:通过RestTemplate,以url完成服务的调用
Feign/OpenFeign:Feign/OpenFeign底层还是ribbon,只是进行了封装,让我们以接口的方式进行调用
Ribbon负载均衡调用
是什么?
Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中列出load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。
Ribbon是一个客户端负载均衡器,它可以按照一定规则来完成一种服务多个服务实例负载均衡调用,这些规则默认提供了很多而且还支持自定义。
集成原理
<!--客户端负载均衡实现 ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
@Configuration
public class CfgBean {
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
@RestController
@RequestMapping("/consumer")
public class UserController {
//多个方法调用只需改一处就ok
//public static final String URL_PREFIX = "http://localhost:8001";
public static final String URL_PREFIX = "http://USER-PROVIDER"; //通过服务名从注册中心获取服务列表,通过负载均衡调用
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/user/{id}")
public User getUser(@PathVariable("id")Long id){
//调用远程服务 http请求
String url = URL_PREFIX+"/provider/user/"+id;
return restTemplate.getForObject(url,User.class );
}
}
为了看到测试效果,改造下服务提供者
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;
import java.applet.AppletContext;
@RestController
@RequestMapping("/user/provider")
public class UserController {
@Autowired
private ApplicationContext context;
@GetMapping("/{id}")
public User getById(@PathVariable(value = "id")Long id){
System.out.println(context.getEnvironment().getActiveProfiles());
//正常来说需要调用service,进而调用mapper获取数据,现在模拟一些直接构造返回。
String[] activeProfiles = applicationContext.getEnvironment().getActiveProfiles();
if (activeProfiles!=null || activeProfiles.length>0){
String profile = activeProfiles[0];
return new User(id,"zs"+profile);}
return new User(id,"zs");
}
}
不断刷新会出现轮询效果
负载均衡策略
ribbon通过服务名获取到多个服务实例后,要根据一定规则来选择一个服务实例来完成调用.这个规则就叫负载均衡策略. 默认负载均衡策略是轮询。
*内置负载均衡规则类* | *规则描述* |
---|---|
RoundRobinRule(默认) | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。注意:可以通过修改配置loadbalancer..connectionFailureCountThreshold来修改连接失败多少次之后被设置为短路状态。默认是3次。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上线,可以由客户端的..ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。 |
BestAvailableRule | 忽略哪些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
Retry | 重试机制的选择逻辑 |
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public IRule myRule(){
return new RandomRule();
}
}
优化
超时配置
使用Ribbon进行服务通信时为了防止网络波动造成服务调用超时,针对Ribbon配置超时时间以及重试机制
ribbon:
http:
client:
enabled: true # 开启
ConnectTimeout: 1000 #连接超时时间
ReadTimeout: 1000 #读取(响应)超时时间
MaxAutoRetries: 1 #重试机制:同一台实例最大重试次数 1除了首次调用之外,还要重试一次
MaxAutoRetriesNextServer: 1 #切换服务器次数,如果第一台服务器失败,最多只切换1次到其他服务器
OkToRetryOnAllOperations: false #是否所有操作都重试,如果设置了false,除了get请求其他的都不做重试
饥饿加载
启动Ribbon发起服务调用的时候往往会出现找不到目标服务的情况,这是因为Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况,即在服务启动时就把Ribbon相关内容创建好。
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: user-server,yyy #针对于哪些服务需要饥饿加载
OpenFeign负载均衡
是什么?
我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下,并且显得好傻。
OpenFeign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的接口模板(上面标的有访问地址),通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。而OpenFeign则会完全代理(动态代理)HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix(关于Hystrix我们后面再讲),可以让我们不再需要显式地使用这两个组件。
总起来说,Feign具有如下特性:
可插拔的注解支持,包括Feign注解和JAX-RS注解;
支持可插拔的HTTP编码器和解码器;
支持Hystrix和它的Fallback;
支持Ribbon的负载均衡;
支持HTTP请求和响应的压缩。
这看起来有点像我们springmvc模式的Controller层的RequestMapping映射。这种模式是我们非常喜欢的。Feign是用@FeignClient来映射服务的。
Open Feign是以接口方式进行服务调用,而不是通过RestTemplate来调用地址,feign底层是对ribbon进行了封装,让我们调用起来更加happy.
替换ribbon jar为feigin
<!--feign的支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "cn.ronghuanet.client") //如果是主类所在子包,可以不用加basePackages,但是最好加上
public class UserConsumerFeignApp5011 {
public static void main(String[] args) {
SpringApplication.run(UserConsumerFeignApp5011.class,args);
}
}
创建一个接口
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
//通过给出线索,完全可以产生一个动态代理类并且交给Spring管理,完对特定服务,特定地址的调用,封装是ribbon
@FeignClient("USER-PROVIDER")
public interface UserClient {
//正常要调用service,进而调用数据库,但是现在模拟一下,直接返回
@RequestMapping("/user/provider//{id}")
User getById(@PathVariable("id") Long id);
}
调用
import com.netflix.appinfo.InstanceInfo;
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.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("/user/consumer")
public class UserController {
@Autowired
private UserClient userClient;
//正常要调用service,进而调用数据库,但是现在模拟一下,直接返回
@RequestMapping("/{id}")
public User getById(@PathVariable("id") Long id)
{
System.out.println(userClient.getClass());
return userClient.getById(id);
}
}
Feign的超时配置
如果在服务调用时出现了 “feign.RetryableException : Read timed out…”错误日志,说明Ribbon处理超时 ,我们可以配置Ribbon的超时时间:
ribbon:
ConnectTimeout: 3000
ReadTimeout: 6000
如果服务调用出现“com.netflix.hystrix.exception.HystrixRuntimeException:… timed - out and no fallback available” 错误日志,是因为Hystrix超时,默认Feign集成了Hystrix,但是高版本是关闭了Hystrix,我们可以配置Hystrix超时时间:
feign:
hystrix:
enabled: true #开启熔断支持hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 #hystrix超时时间
Hystrix断路器
Hystrix简介
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。
Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。
资源隔离(限流):包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
熔断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复
降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
缓存:提供了请求缓存、请求合并实现。
原理分析-命令模式
Hystrix使用命令模式(继承HystrixCommand(隔离)类)来包裹具体的服务调用逻辑(run方法), 并在命令模式中添加了服务调用失败后的降级逻辑(getFallback).
同时我们在Command的构造方法中可以定义当前服务线程池和熔断器的相关参数
在使用了Command模式构建了服务对象之后, 服务便拥有了熔断器和线程池的功能.
资源隔离&限流
(1)线程池隔离模式(默认):使用一个线程池来存储当前请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求先入线程池队列。这种方式要为每个依赖服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
(2)信号量隔离模式:使用一个原子计数器(或信号量)记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
服务熔断
如果调用持续出错或者超时,电路被打开进入熔断状态(Open),后续一段时间内的所有调用都会被拒绝
一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,
如果调用仍然失败,则回到熔断状态;如果调用成功,则回到电路闭合状态;
Hystrix提供了如下的几个关键参数,来对一个熔断器进行配置:
circuitBreaker.requestVolumeThreshold //滑动窗口的大小,默认为20
circuitBreaker.sleepWindowInMilliseconds //过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
circuitBreaker.errorThresholdPercentage //错误率,默认50%
3个参数放在一起,所表达的意思就是:
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开
服务降级
有了熔断和限流,就得有降级。所谓降级,就是当某个服务出错,超时、熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。
这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。
Hystrix实现-服务提供者
<!--断路器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
server:
port: 4020
spring:
application:
name: user-provider
eureka:
instance:
prefer-ip-address: true #显示eureka客户端真实ip
client:
service-url:
#defaultZone: http://localhost:1010/eureka #告诉服务提供者要把服务注册到哪
#eureka集群环境
defaultZone: http://eureka1:1010/eureka,http://eureka2:1011/eureka,http://eureka3:1012/eureka
主类
@SpringBootApplication
@EnableDiscoveryClient //效果一样 @EnableEurekaClient
@EnableHystrix //效果一样 @EnableCircuitBreaker
public class OrderApp
{
public static void main( String[] args ) {
SpringApplication.run(OrderApp.class, args);
}
}
服务代码:
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/user/{userId}")
@HystrixCommand(fallbackMethod = "getOrderByUserIdFallback")
public List<Order> getOrderByUserId(@PathVariable("userId") Long userId) {
System.out.println(1/0);
List<Order> orders = Arrays.asList(
new Order(1L, "订单1", userId),
new Order(2L, "订单2", userId),
new Order(3L, "订单3", userId)
);
return orders;
}
public List<Order> getOrderByUserIdFallback(Long userId){
return Arrays.asList(new Order(-1L, "服务异常", userId));
}
}
注意:
1 熔断我们是在服务提供方做
2 调用时不能使用openfeign调用(有自己熔断机制),要用ribbon调用,应该用open自己的机制
我们往往会把Ribbon和Hystrix的超时配合起来配置:
ribbon:
MaxAutoRetries: 1 #最大重试次数
MaxAutoRetriesNextServer: 1 #切换实例的重试次数
OkToRetryOnAllOperations: false # 对所有的操作请求都进行重试,
ConnectTimeout: 1000 #请求连接的超时时间
ReadTimeout: 1800 #请求处理的超时时间hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #hystrix超时,置thread和semaphore两种隔离策略的超时时间
Feign实现
为什么使用?
问题: 每个方法都要加回调并且耦合业务代码类中
解决方案:
使用spring面向切面编程,为feign的接口创建一个代理对象,完成对服务调用,当发现熔断后就调用同名托底方法.
实现(feign服务消费者实现)
- 创建项目-拷贝feign
- 导入jar-没有额外的
- 配置
server:
port: 9003
eureka:
client:
registerWithEureka: false #不注册到Eureka,不在注册中心显示
service-url:
defaultZone: http://localhost:7001/eureka
feign:
hystrix:
enabled: true #开启熔断支持
client:
config:
remote-service: #服务名,填写default为所有服务
connectTimeout: 3000
readTimeout: 3000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
- 入口类
如果接口包结构不按照规范来,额外加入扫描包.@ComponentScan
@SpringBootApplication
@EnableDiscoveryClient // 开启服务发现客户端
@EnableFeignClients //当client接口类在启动类平级的包下或子孙包下,就可以简写,否则需要添加包路径
public class UserApp {
public static void main(String[] args) {
SpringApplication.run(UserApp.class,args);
}
}
- 代码-公共里面实现
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
//UserClient 表示对那个接口进行托底处理
@Component
public class UserClientHystrixFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User getUser(Long id) {
return new User(id,"出异常,请联系管理员!");
}
};
}
}
//调用服务名字
@FeignClient(value = "USER-PROVIDER",fallbackFactory = UserClientHystrixFallbackFactory.class)
@RequestMapping("/provider")
public interface UserClient {
@RequestMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}
通过feign.hystrix.enabled=true开启Hystrix
feign:
hystrix:
enabled: true #开启熔断支持ribbon:
ReadTimeout: 3000
SocketTimeout: 3000
ConnectTimeout: 3000hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10300
总结
如果服务消费者实现的技术为ribbon,服务提供者方使用Hystrix的断路器,也可以在服务消费者来做,但是不能两边做。建议在消费端(能监控服务端是否启动),如果我们服务消费者实现的技术为feign,必须在服务消费者通过feign的断路器,feign断路器底层还是Hystrix的断路器. 默认是关闭的,要启用。
Zuul路由网关
是什么?
Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet(filter)应用。
Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门入口,也要注册入Eureka。
基本配置
<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>
<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-zuul</artifactId>
</dependency>
yml
server:
port: 8848
spring:
application:
name: zuul-gateway
eureka:
client:
service-url:
defaultZone: defaultZone: http://eureka1.com:1010/eureka,http://eureka2.com:1020/eureka,http://eureka3.com:1030/eureka
instance:
instance-id: zuul:2010
prefer-ip-address: false
修改入口类
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;
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulGatewayApp2010 {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApp2010.class,args);
}
}
启动测试
Eureka 服务提供者 zuul
直接访问地址:http://localhost:{user服务端口}/user/provider/1
路由访问地址:http://localhost:{网关服务端口}/user-provider(小写)/user/provider/1
路由访问映射规则
- 安全加固:不用服务名,映射路径
- 忽略原来服务名访问:原来模式不可以访问
- 加上统一前缀
zuul:
routes:
myUser.serviceId: user-provider # 服务名
myUser.path: /user/** # 把user打头的所有请求都转发给user-provider
ignored-services: "*" #所有服务都不允许以服务名来访问
prefix: "/services" #加一个统一前缀
请求路径变为: http://localhost:{网关服务端口}/services/user/user/provider/1
过滤器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权(认证授权)。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
ZuulFilter
ZuulFilter是过滤器的顶级父类。
- shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
- run:过滤器的具体业务逻辑。
- filterType:返回字符串,代表过滤器的类型。包含以下4种:
- pre:请求在被路由之前执行
- routing:在路由请求时调用
- post:在routing和errror过滤器之后调用
- error:处理请求时发生错误调用
- filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
过滤器执行周期
- 正常流程:
- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
使用场景
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计:pre和post结合使用。
自定义过滤器
@Component
public class LoginFilter extends ZuulFilter{
@Override
public String filterType() {
// 登录校验,肯定是在前置拦截
return "pre";
}
@Override
public int filterOrder() {
// 顺序设置为1
return 1;
}
@Override
public boolean shouldFilter() {
// 返回true,代表过滤器生效。
return true;
}
@Override
public Object run() throws ZuulException {
// 登录校验逻辑。
// 1)获取Zuul提供的请求上下文对象
RequestContext ctx = RequestContext.getCurrentContext();
// 2) 从上下文中获取request对象
HttpServletRequest req = ctx.getRequest();
// 3) 从请求中获取token
String token = req.getHeader("token");
// 4) 判断
if(token == null || "".equals(token.trim())){
// 没有token,登录校验失败,拦截
ctx.setSendZuulResponse(false);
// 返回401状态码。也可以考虑重定向到登录页。
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
// 校验通过,可以考虑把用户信息放入上下文,继续向后执行
return null;
}
}
负载均衡与熔断
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置。
zuul:
retryable: true
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
OkToRetryOnAllOperations: false# 是否对所有操作重试
MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 3000 # 熔断超时时长:3000ms
SpringCloud Config配置中心
微服务架构中,每个微服务都有一个或多个yml配置,里面内容有可能很多,管理起来麻烦。要使用统一配置中心来统一管理。常见配置中心有springcloud config,nacos等
是什么?
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
能干什么
统一管理配置
配置实时刷新
和gitee集成
可以和svn,git集成,但是推荐使用gitee集成使用
1)方案1:configserver不集群有单点故障。(所有的配置都要放到里面)
2)方案2:Eureka集群,Eureka配置不能放到里面(不建议使用)
3)方案3:外置负载均衡器(Nginx),所有的配置都放到仓库中。(采纳)
步骤分析:zuul配置为例
1)创建一个gitee仓库
准备配置文件
2)创建config server
通过config server获取配置文件
3)在zuul实现config client
最终达到效果,zuul配置是从配置中心获取的
1)gitee创建配置文件,将application-zuul-dev.yml放在gitee仓库
server:
port: 8848
spring:
application:
name: zuul-gateway-dev
eureka:
client:
service-url:
defaultZone: http://eureka1:1010/eureka,http://eureka2:1020/eureka,http://eureka3:1030/eureka
instance:
instance-id: zuul:2010
prefer-ip-address: false
zuul:
retryable: true
routes:
myUser.serviceId: user-provider #服务名
myUser.path: /user/** #哪个路径
xxx.serviceId: order-provider #服务名
xxx.path: /order/** #哪个路径
ignored-services: "*" #哪些服务不能通过服务名访问,*表示所有的服务
prefix: "/services" #所有访问地址都加一个前缀
ribbon: #负载均衡配置
connectTimeout: 250 # 连接超时时间(ms)
readTimeout: 2000 # 通信超时时间(ms)
okToRetryOnAllOperations: true # 是否对所有操作重试
maxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
maxAutoRetries: 1 # 同一实例的重试次数
hystrix: #熔断配置
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 3000 # 熔断超时时长:3000ms
config服务端的导包
<dependencies>
<!--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>
<!--配置中心支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
config服务端的配置文件
server:
port: 3010
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: git仓库地址
username: 账号
password: 密码
@SpringBootApplication
@EnableConfigServer //启用配置服务端
public class ConfigServerApplication_1299 {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication_1299.class);
}
}
客户端配置
zuul模块导包
<dependencies>
<!--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>
<!--configclient端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
准备bootstrap.yml/properties
spring:
cloud:
config:
name: application-user #gitee上面名称,文件名,放在仓库根目录
profile: dev #环境,配置文件名后缀 如:application-zuul-dev.yml
label: master #分支
uri: http://127.0.0.1:3010 #配置服务器,config服务端的地址
@SpringBootApplication
@EnableEurekaClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
SpringCloud bus配置文件自动刷新
原理分析
配置中心实现了配置的统一管理,但是如果有很多个微服务实例,当更改配置时,需要重启多个微服务实例,会非常麻烦。SpringCloud Bus 就能让这个过程变得非常简单,当我们Git仓库中的配置更改后,只需要某个微服务实例发送一个POST请求,通过消息组件通知其他微服务实例重新获取配置文件,以达到配置的自动刷新效果。
Config-server实现
Pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
server:
port: 3010
spring:
rabbitmq: #集成RabbitMQ如果配置是默认,可以省略
host: localhost #mq连接地址
port: 5672 #mq端口
username: guest
password: guest
application:
name: config-server
cloud:
config:
server:
git:
uri: git仓库
username: 账号
password: 密码
eureka:
client:
service-url:
defaultZone: http://eureka1.com:1010/eureka
instance:
instance-id: config-server:3010
prefer-ip-address: true
#actuator配置
management:
endpoint:
health:
show-details: always #打印日志
endpoints:
web:
exposure:
include: "*" #向外暴露的接口,这里*则表示所有的actuator接口都可以被访问
enabled-by-default: true #开启actuator监控
Config-client实现-zuul为例
Pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置
spring:
rabbitmq: #集成RabbitMQ如果配置是默认,可以省略
host: localhost #mq连接地址
port: 5672 #mq端口
username: guest
password: guest
cloud:
config:
name: application-user #gitee上面名称
profile: dev #环境
label: master #分支
uri: http://127.0.0.1:1299 #配置服务器
bus: #这行代码很重要,根据官方定义的标准来的 ,就是为了产生一个bus.id
id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}
PostMan测试
配置文件中修改配置
使用Post触发测试 http://localhost:3010/actuator/bus-refresh
Gitee webhook触发
内网穿透技术:http://localhost:9010/actuator/bus-refresh-----》:http://xxx.yyy.com/actuator/bus-refresh
软件支持:场景的软件,nat123(30元),花生壳(6元),Natapp(域名经常变不要钱)–花钱买固定域名
Natapp配置
gitee, Webhook配置:管理-WebHooks
错误解决:config server格式错误
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@Component
public class WebHooksFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String url = new String(httpServletRequest.getRequestURI());
if(!url.endsWith("/bus-refresh")){
filterChain.doFilter(servletRequest,servletResponse);
return;
}
RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
private class RequestWrapper extends HttpServletRequestWrapper {
public RequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true : false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
}
}
@SpringBootApplication
@EnableConfigServer //启动configserver
@ServletComponentScan("cn.ronghuanet.config")
public class ConfigServerApp1030 {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp1030.class,args);
}
}