作者: gh-xiaohe
gh-xiaohe的博客
觉得博主文章写的不错的话,希望大家三连(✌关注,✌点赞,✌评论),多多支持一下!!!
1、单挑架构
2、垂直结构
3、分布式架构
把拆封出来小的模块,可以随意的增减
4、SOA架构
两两服务都可以直接相互调用,会发生服务调用网络非常的繁杂
5、微服务架构
任意两个服务直接调用,并不需要总线帮助我们调用,而是在注册中心中记录一些,服务所在的位置(记录信息少)
①"微服务”一词源 于 Martin Fowler的名为 Microservices的博文,可以在他的官方博客上找到博客链接
②微服务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间一般通过 HTTP 的 RESTfuL API 进行通信协作。
restfull 风格:数据的增删改查,使用http的不同方式。数据传输用json。
③由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。大厂,各种语言混用。
用户请求过来之后,访问哪一个后端的微服务
Spring Cloud Netflix
Netflix OSS 开源组件集成,包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。
Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略;
Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;
Feign:基于Ribbon和Hystrix的声明式服务调用组件;
Zuul:API网关组件,对请求提供路由及过滤功能。
①Spring Cloud 是 一系列框架的有序集合。
②Spring Cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来。
③通过Spring Boot 风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
④它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、 断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
相同点:Spring Cloud 与 Dubbo 都是实现微服务有效的工具。
不相同点:
小结:
红色不维护。
绿色是alibaba一套,推荐使用
约定大约配置 见本人博客:
创建工程
使用utf-8编码
maven设置 --> 不建议使用系统自带的
pom.xml
<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.0modelVersion>
<groupId>com.ydlclassgroupId>
<artifactId>spring-cloud-parentartifactId>
<version>1.0.0version>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.11.RELEASEversion>
parent>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
properties>
project>
搭建springboot工程:
pom.xml
<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-parentartifactId>
<groupId>com.ydlclassgroupId>
<version>1.0.0version>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>eureka-providerartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
application.yml
server:
port: 8000
主启动类
package com.ydlclass.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args);
}
}
Goods
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Goods {
private int id;//商品id
private String title;//商品名
private double price;//价格
private int count;//库存
}
GoodsController
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("findById/{id}")
public Goods findById(@PathVariable("id") int id) {
Goods goods = goodsService.findById(id);
return goods;
}
}
GoodsService
@Service
public class GoodsService {
@Autowired
private GoodsDao goodsDao;
public Goods findById(int id){
Goods goods = goodsDao.findById(id);
return goods;
}
}
}
GoodsDao
@Repository
public class GoodsDao {
public Goods findById(int id){
//查数据库
return new Goods(id,"手机",2000,100);
}
}
pom.xml
<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-parentartifactId>
<groupId>com.ydlclassgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>eureka-consumerartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
Goods
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Goods {
private int id;//商品id
private String title;//商品名
private double price;//价格
private int count;//库存
}
OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/add/{id}")
public Goods add(@PathVariable("id") Integer id){
//业务逻辑
//1查询商品
//2减库存
//3支付
//4物流
return new Goods();
}
}
让order服务发送 http 请求访问到goods服务
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
// 下单:应该是post 请求此时是浏览器测试,用get请求
@GetMapping("/add/{id}")
public Goods add(@PathVariable("id") Integer id){
String url = "http://localhost:8000/goods/findById/" + id; // 访问此请求
/* 返回是数据
Goods对象的 json串 转换成 实体类
{
id: 1,
title: "手机",
price: 2000,
count: 100
}
*/
Goods goods = restTemplate.getForObject(url, Goods.class);// responseType: 响应体中
return goods;
}
}
父工程 pom
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.11.RELEASEversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.RELEASEspring-cloud.version>
<lombok.version>RELEASElombok.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
<scope>truescope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
@EnableEurekaServer eureka的服务端
// 启用EurekaServer
@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class,args);
}
}
eureka 一共有4部分 配置
server:
port: 8761 # 默认8761 端口
# eureka 配置
# eureka 一共有4部分 配置
# 1. dashboard:eureka的web控制台配置
# 2. server:eureka的服务端配置
# 3. client:eureka的客户端配置
# 4. instance:eureka的实例配置
eureka:
instance: # eureka的 实例配置
hostname: localhost # 主机名
client: # 客户端的属性
service-url:
# localhost:8761/eureka
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
# 是否注册自己
register-with-eureka: false # 是否将自己的路径 注册到eureka上。eureka server 不需要的,eureka provider client 需要
fetch-registry: false # 是否需要从eureka中抓取路径。eureka server 不需要的,eureka consumer client 需要
dashboard: # 图形化界面 (默认即可)
path: / # 路径
enabled: true # 默认开启
# server: # 服务端的属性
① 引 eureka-client 相关依赖
② 完成 eureka client 相关配置
③ 启动测试
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
@SpringBootApplication
@EnableEurekaClient // 开启Eureka客户端
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args);
}
}
server:
port: 8001
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
spring:
application:
name: eureka-provider # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
server:
port: 9000
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
spring:
application:
name: eureka-consumer # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; // 发现客户端
// 下单:应该是post 请求此时是浏览器测试,用get请求
@GetMapping("/add/{id}")
public Goods add(@PathVariable("id") Integer id){
// 服务发现
List<ServiceInstance> instances = discoveryClient.getInstances("EUREKA-PROVIDER");// 获取实例:实例的名称
if(instances!=null || instances.size() < 0) {
}
// 如果是多个实例,选取一个实例 (比如: 随机或者权重) 策略
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost();// ip
int port = serviceInstance.getPort();// 端口
System.out.println("host = " + host + " port = " + port);
String url = "http://" + host + ":" + port + "/goods/findById/" + id;
Goods goods = restTemplate.getForObject(url, Goods.class);// responseType: 响应体中
return goods;
}
}
主启动类中激活激活 DiscoveryClient
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
Eureka包含四个部分的配置
Eureka Instance、Eureka Client、Eureka Server在org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean配置类里,实际上它是com.netflix.appinfo.EurekaInstanceConfig的实现类,替代了netflix的com.netflix.appinfo.CloudInstanceConfig的默认实现。
Eureka Instance、Eureka Client、Eureka ServerEureka Instance、Eureka Client、Eureka Server的配置信息全部以eureka.instance.xxx的格式配置。
eureka:
instance:
hostname: localhost # 主机名
prefer-ip-address: # 是否将自己的ip注册到eureka中,默认false 注册 主机名
ip-address: # 设置当前实例ip
instance-id: # 修改instance-id显示
lease-renewal-interval-in-seconds: 30 # 每一次eureka client 向 eureka server发送心跳的时间间隔 默认
lease-expiration-duration-in-seconds: 90 # 如果90秒内eureka server没有收到eureka client的心跳包,则剔除该服务 默认
配置列表 | |
---|---|
hostname = null | 主机名,不配置的时候讲根据操作系统的主机名来获取 |
preferIpAddress = false | 是否优先使用IP地址作为主机名的标识 |
leaseRenewalIntervalInSeconds = 30 | 实例续约间隔时间 |
leaseExpirationDurationInSeconds = 90 | 实例超时时间,表示最大leaseExpirationDurationInSeconds秒后没有续约,Server就认为他不可用了,随之就会将其剔除。 |
ipAddress=null | 实例的IP地址 |
instanceId | 注册到eureka上的唯一实例ID,不能与相同appname的其他实例重复。 |
appname = unknown | 应用名,首先获取spring.application.name的值,如果取值为空,则取默认unknown。 |
appGroupName = null | 应用组名 |
instanceEnabledOnit = false | 实例注册到Eureka上是,是否立刻开启通讯。有时候应用在准备好服务之前需要一些预处理。 |
nonSecurePort = 80 | 非安全的端口 |
securePort = 443 | 安全端口 |
nonSecurePortEnabled = true | 是否开启非安全端口通讯 |
securePortEnabled = false | 是否开启安全端口通讯 |
virtualHostName = unknown | 虚拟主机名,首先获取spring.application.name的值,如果取值为空,则取默认unknown。 |
secureVirtualHostName = unknown | 安全虚拟主机名,首先获取spring.application.name的值,如果取值为空,则取默认unknown。 |
metadataMap = new HashMap(); | 实例元数据,可以供其他实例使用。比如spring-boot-admin在监控时,获取实例的上下文和端口。 |
dataCenterInfo = new MyDataCenterInfo(DataCenterInfo.Name.MyOwn); | 实例部署的数据中心。如AWS、MyOwn。 |
statusPageUrlPath = “/actuator/info” | 实例状态页相对url |
statusPageUrl = null | 实例状态页绝对URL |
homePageUrlPath = “/” | 实例主页相对URL |
homePageUrl = null | 实例主页绝对URL |
healthCheckUrlUrlPath = “/actuator/health” | 实例健康检查相对URL |
healthCheckUrl = null | 实例健康检查绝对URL |
secureHealthCheckUrl = null | 实例安全的健康检查绝对URL |
namespace = “eureka” | 配置属性的命名空间(Spring Cloud中被忽略) |
eureka:
client:
service-url:
# eureka服务端地址,将来客户端使用该地址和eureka进行通信
defaultZone:
register-with-eureka: # 是否将自己的路径 注册到eureka上。
fetch-registry: # 是否需要从eureka中抓取数据。
配置列表 | |
---|---|
enabled=true | 是否启用Eureka client。 |
registryFetchIntervalSeconds=30 | 定时从Eureka Server拉取服务注册信息的间隔时间 |
instanceInfoReplicationIntervalSeconds=30 | 定时将实例信息(如果变化了)复制到Eureka Server的间隔时间。(InstanceInfoReplicator线程) |
initialInstanceInfoReplicationIntervalSeconds=40 | 首次将实例信息复制到Eureka Server的延迟时间。(InstanceInfoReplicator线程) |
eurekaServiceUrlPollIntervalSeconds=300 | 拉取Eureka Server地址的间隔时间(Eureka Server有可能增减) |
proxyPort=null | Eureka Server的代理端口 |
proxyHost=null | Eureka Server的代理主机名 |
proxyUserName=null | Eureka Server的代理用户名 |
proxyPassword=null | Eureka Server的代理密码 |
eurekaServerReadTimeoutSeconds=8 | 从Eureka Server读取信息的超时时间 |
eurekaServerConnectTimeoutSeconds=5 | 连接Eureka Server的超时时间 |
backupRegistryImpl=null | Eureka Client第一次启动时获取服务注册信息的调用的回溯实现。Eureka Client启动时首次会检查有没有BackupRegistry的实现类,如果有实现类,则优先从这个实现类里获取服务注册信息。 |
eurekaServerTotalConnections=200 | Eureka client连接Eureka Server的链接总数 |
eurekaServerTotalConnectionsPerHost=50 | Eureka client连接单台Eureka Server的链接总数 |
eurekaServerURLContext=null | 当Eureka server的列表在DNS中时,Eureka Server的上下文路径。如http://xxxx/eureka。 |
eurekaServerPort=null | 当Eureka server的列表在DNS中时,Eureka Server的端口。 |
eurekaServerDNSName=null | 当Eureka server的列表在DNS中时,且要通过DNSName获取Eureka Server列表时,DNS名字。 |
region=“us-east-1” | 实例所属区域。 |
eurekaConnectionIdleTimeoutSeconds = 30 | Eureka Client和Eureka Server之间的Http连接的空闲超时时间。 |
heartbeatExecutorThreadPoolSize=2 | 心跳(续约)执行器线程池大小。 |
heartbeatExecutorExponentialBackOffBound=10 | 心跳执行器在续约过程中超时后的再次执行续约的最大延迟倍数。默认最大延迟时间=10 * eureka.instance.leaseRenewalIntervalInSeconds |
cacheRefreshExecutorThreadPoolSize=2 | cacheRefreshExecutord的线程池大小(获取注册信息) |
cacheRefreshExecutorExponentialBackOffBound=10 | cacheRefreshExecutord的再次执行的最大延迟倍数。默认最大延迟时间=10 *eureka.client.registryFetchIntervalSeconds |
serviceUrl= new HashMap();serviceUrl.put(DEFAULT_ZONE, DEFAULT_URL); | Eureka Server的分区地址。默认添加了一个defualtZone。也就是最常用的配置eureka.client.service-url.defaultZone=xxx |
registerWithEureka=true | 是否注册到Eureka Server。 |
preferSameZoneEureka=true | 是否使用相同Zone下的Eureka server。 |
logDeltaDiff=false | 是否记录Eureka Server和Eureka Client之间注册信息的差异 |
disableDelta=false | 是否开启增量同步注册信息。 |
fetchRemoteRegionsRegistry=null | 获取注册服务的远程地区,以逗号隔开。 |
availabilityZones=new HashMap() | 可用分区列表。用逗号隔开。 |
filterOnlyUpInstances = true | 是否只拉取UP状态的实例。 |
fetchRegistry=true | 是否拉取注册信息。 |
shouldUnregisterOnShutdown = true | 是否在停止服务的时候向Eureka Server发起Cancel指令。 |
shouldEnforceRegistrationAtInit = false | 是否在初始化过程中注册服务。 |
eureka:
server:
enable-self-preservation: true # 是否开启自我保护机制,默认true
eviction-interval-timer-in-ms: 120000 # 120s 清理间隔(单位毫秒,默认是60*1000)
instance:
lease-renewal-interval-in-seconds: 30 # 每一次eureka client 向 eureka server发送心跳的时间间隔 默认
lease-expiration-duration-in-seconds: 90 # 如果90秒内eureka server没有收到eureka client的心跳包,则剔除该服务 默认
配置列表 | |
---|---|
enableSelfPreservation=true | 是否开启自我保护 |
renewalPercentThreshold = 0.85 | 自我保护续约百分比阀值因子。如果实际续约数小于续约数阀值,则开启自我保护 |
renewalThresholdUpdateIntervalMs = 15 * 60 * 1000 | 续约数阀值更新频率。 |
peerEurekaNodesUpdateIntervalMs = 10 * 60 * 1000 | Eureka Server节点更新频率。 |
enableReplicatedRequestCompression = false | 是否启用复制请求压缩。 |
waitTimeInMsWhenSyncEmpty=5 * 60 * 1000 | 当从其他节点同步实例信息为空时等待的时间。 |
peerNodeConnectTimeoutMs=200 | 节点间连接的超时时间。 |
peerNodeReadTimeoutMs=200 | 节点间读取信息的超时时间。 |
peerNodeTotalConnections=1000 | 节点间连接总数。 |
peerNodeTotalConnectionsPerHost = 500; | 单个节点间连接总数。 |
peerNodeConnectionIdleTimeoutSeconds = 30; | 节点间连接空闲超时时间。 |
retentionTimeInMSInDeltaQueue = 3 * MINUTES; | 增量队列的缓存时间。 |
deltaRetentionTimerIntervalInMs = 30 * 1000; | 清理增量队列中过期的频率。 |
evictionIntervalTimerInMs = 60 * 1000; | 剔除任务频率。 |
responseCacheAutoExpirationInSeconds = 180; | 注册列表缓存超时时间(当注册列表没有变化时) |
responseCacheUpdateIntervalMs = 30 * 1000; | 注册列表缓存更新频率。 |
useReadOnlyResponseCache = true; | 是否开启注册列表的二级缓存。 |
disableDelta=false。 | 是否为client提供增量信息。 |
maxThreadsForStatusReplication = 1; | 状态同步的最大线程数。 |
maxElementsInStatusReplicationPool = 10000; | 状态同步队列的最大容量。 |
syncWhenTimestampDiffers = true; | 当时间差异时是否同步。 |
registrySyncRetries = 0; | 注册信息同步重试次数。 |
registrySyncRetryWaitMs = 30 * 1000; | 注册信息同步重试期间的时间间隔。 |
maxElementsInPeerReplicationPool = 10000; | 节点间同步事件的最大容量。 |
minThreadsForPeerReplication = 5; | 节点间同步的最小线程数。 |
maxThreadsForPeerReplication = 20; | 节点间同步的最大线程数。 |
maxTimeForReplication = 30000; | 节点间同步的最大时间,单位为毫秒。 |
disableDeltaForRemoteRegions = false; | 是否启用远程区域增量。 |
remoteRegionConnectTimeoutMs = 1000; | 远程区域连接超时时间。 |
remoteRegionReadTimeoutMs = 1000; | 远程区域读取超时时间。 |
remoteRegionTotalConnections = 1000; | 远程区域最大连接数 |
remoteRegionTotalConnectionsPerHost = 500; | 远程区域单机连接数 |
remoteRegionConnectionIdleTimeoutSeconds = 30; | 远程区域连接空闲超时时间。 |
remoteRegionRegistryFetchInterval = 30; | 远程区域注册信息拉取频率。 |
remoteRegionFetchThreadPoolSize = 20; | 远程区域注册信息线程数。 |
eureka:
dashboard: #
enabled: true # 是否启用eureka web控制台
path: / # 设置eureka web控制台默认访问路径
改造 provider
server:
port: 8001
eureka:
instance:
hostname: localhost # 主机名
prefer-ip-address: true # 将当前实例的ip注册到eureka server 中。默认是false 注册主机名
ip-address: 127.0.0.1 # 设置当前实例的ip
instance-id: ${eureka.instance.ip-address}:${spring.application.name}:${server.port} # 设置web控制台显示的 实例id
lease-renewal-interval-in-seconds: 3 # 每隔3 秒发一次心跳包
lease-expiration-duration-in-seconds: 9 # 如果9秒没有发心跳包,服务器呀,你把我干掉吧~
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
spring:
application:
name: eureka-provider # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
consumer
server:
port: 9000
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
spring:
application:
name: eureka-consumer # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
server
server:
port: 8761 # 默认8761 端口
# eureka 配置
# eureka 一共有4部分 配置
# 1. dashboard:eureka的web控制台配置
# 2. server:eureka的服务端配置
# 3. client:eureka的客户端配置
# 4. instance:eureka的实例配置
eureka:
instance:
hostname: localhost # 主机名
instance-id: # 修改instance-id显示
prefer-ip-address: true # 将当前实例的ip注册到eureka server 中。默认是false 注册主机名
ip-address: 127.0.0.1 # 设置当前实例的ip
lease-renewal-interval-in-seconds: 30 # 每一次eureka client 向 eureka server发送心跳的时间间隔
lease-expiration-duration-in-seconds: 90 # 如果90秒内eureka server没有收到eureka client的心跳包,则剔除该服务
client: # 客户端的属性
service-url:
# localhost:8761/eureka
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
# 是否注册自己
register-with-eureka: false # 是否将自己的路径 注册到eureka上。eureka server 不需要的,eureka provider client 需要
fetch-registry: false # 是否需要从eureka中抓取路径。eureka server 不需要的,eureka consumer client 需要
dashboard: # 图形化界面 (默认即可)
path: / # 路径
enabled: true # 默认开启
# server: # 服务端的属性
spring:
application:
name: eureka-server
server:
port: 8761
eureka:
instance:
hostname: eureka-server1 # 主机名
client:
service-url:
defaultZone: http://eureka-server2:8762/eureka
register-with-eureka: true # 是否将自己的路径 注册到eureka上。eureka server 不需要的,eureka provider client 需要
fetch-registry: true # 是否需要从eureka中抓取路径。eureka server 不需要的,eureka consumer client 需要
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 120000
spring:
application:
name: eureka-server-ha
server:
port: 8762
eureka:
instance:
hostname: eureka-server2 # 主机名
client:
service-url:
defaultZone: http://eureka-server1:8761/eureka
register-with-eureka: true # 是否将自己的路径 注册到eureka上。eureka server 不需要的,eureka provider client 需要
fetch-registry: true # 是否需要从eureka中抓取路径。eureka server 不需要的,eureka consumer client 需要
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 120000
spring:
application:
name: eureka-server-ha
修改 C:\Windows\System32\drivers\etc\hosts
provider
eureka:
client:
service-url:
defaultZone: http://eureka-server1:8761/eureka,http://eureka-server2:8762/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
consumer
eureka:
client:
service-url:
defaultZone: http://eureka-server1:8761/eureka,http://eureka-server2:8762/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
测试:
1、是否完成相互注册(高可用集群)
2、集群是否完成高可用:能都调用通,一个挂掉,是否能访问
3、高可用测试:停掉一个eureka,依然可以访问consumer。
eureka不更新了,所以淘汰了。
有的老项目以前是dubbo,升级到微服务,使用zookeeper做注册中心。
zookeeper是一个分布式协调工具,可以实现注册中心功能。dubbo,大数据组件hadoop,hive,kafka。
实质:注册中心换成zk
下载:https://zookeeper.apache.org/
zookeeper\conf zoo_sample.cfg 重新命名为 zoo.cfg
tickTime=2000 # 心跳时间
initLimit=10 # 启示数
syncLimit=5 # 发送请求并确认,可以通过的数量
dataDir=F:/apache-zookeeper-3.5.6-bin/data # 数据存放的位置
clientPort=2181 # 默认端口号
启动 : 在bin目录下 cmd zkServer.cmd 启动
zkServer.cmd # 服务端
zkCli.cmd # 客户端
<artifactId>zookeeper-providerartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zookeeper-discoveryartifactId>
<exclusions>
<exclusion>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.7.0version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
server:
port: 8004
spring:
application:
name: zookeeper-provider
cloud:
zookeeper:
connect-string: 127.0.0.1:2181 # zk地址
Zookeeper、Eureka、Consul 无所谓,都是注册到注册中心的客户端
@SpringBootApplication
@EnableDiscoveryClient // 开启发现客户端 是Zookeeper、Eureka、Consul 无所谓,都是注册到注册中心的客户端
public class ZookeeperProvider {
public static void main(String[] args) {
SpringApplication.run(ZookeeperProvider.class, args);
}
}
业务逻辑代码直接复制粘贴过来
Zookeeper客户端信息查看
# zookeeper 客户端
[zk: localhost:2181(CONNECTED) 0] ls / # zookeeper-provider 未启动
[zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls / # zookeeper-provider 启动后
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 2] ls /services # 可以发现服务已经注册上
[zookeeper-provider]
[zk: localhost:2181(CONNECTED) 3] ls /services/zookeeper-provider # 节点的id,记录一些信息
[4774a514-8395-4d2d-87fa-dd2a4344e349]
[zk: localhost:2181(CONNECTED) 4] ls /services/zookeeper-provider/4774a514-8395-4d2d-87fa-dd2a4344e349
[]
# 服务的提供者已经注册到 zookeeper 中
<artifactId>zookeeper-consumerartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zookeeper-discoveryartifactId>
<exclusions>
<exclusion>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.7.0version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
server:
port: 8005
spring:
application:
name: zookeeper-consumer
cloud:
zookeeper:
connect-string: 127.0.0.1:2181 # zk地址
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
业务逻辑代码直接复制粘贴过来
修改控制层中的 服务发现名称
List<ServiceInstance> instances = discoveryClient.getInstances("zookeeper-provider");
Zookeeper客户端信息查看
[zk: localhost:2181(CONNECTED) 5] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 6] ls /services
[zookeeper-consumer, zookeeper-provider]
zookeeper-consumer zookeeper-provider
[zk: localhost:2181(CONNECTED) 7] ls /services/zookeeper-consumer
[97a2cb66-b0bd-4798-b5ea-4e85bd5bd07e]
运行 consul.exe agent -dev
consul.exe agent -dev
# or
consul agent -dev
访问控制台:地址http://localhost:8500/ui/dc1/services
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
server:
port: 8006
spring:
application:
name: consul-provider
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
service-name: ${spring.application.name} # 把服务于叫成什么名字 (此时:把项目名注册上去)
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulProvider {
public static void main(String[] args) {
SpringApplication.run(ConsulProvider.class,args);
}
}
业务逻辑复制
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
server:
port: 8007
spring:
application:
name: consul-consumer
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
service-name: ${spring.application.name}
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
业务逻辑复制
controller只改一句话
List<ServiceInstance> instances = discoveryClient.getInstances("consul-provider");
最终测试调用成功即可:http://localhost:8007/order/add/9
cap 面试
组件 | 语言 | cap | 健康检查 | 暴露接口 | cloud集成 |
---|---|---|---|---|---|
eureka | java | ap | 支持 | http | 已经集成 |
zookeeper | java | cp | 支持 | tcp | 已经集成 |
consul | go | cp | 支持 | http | 已经集成 |
Netflix公司推出的http和TCP的客户端负载均衡工具。
Ribbon功能:
负载均衡算法在服务端,服务端维护服务列表。 负载均衡的操作在服务端
使用Ribbon 实现负载均衡策略:
1、新版的eureka依赖以及集成了Ribbon依赖,所以可以不引用
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
2、声明restTemplate时@LoadBalanced
// 01eureka-consumer-9000
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 1、进化成Ribbon 的客户端
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3、restTemplate请求远程服务时,ip端口替换为服务名
// 01eureka-consumer-9000
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; // 发现客户端
// 下单:应该是post 请求此时是浏览器测试,用get请求
@GetMapping("/add/{id}")
public Goods add(@PathVariable("id") Integer id){
// ip 和 端口 就不用书写了 直接写 provider
String url = "http://EUREKA-PROVIDER/goods/findById/" + id;
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
1、启动2个eureka-provider-8000
idea设置 能启动两份 provider
是访问8000,还是8001,负载均衡 访问那个 还需一步操作
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@Value("${server.port}")
int port;
@GetMapping("findById/{id}")
public Goods findById(@PathVariable("id") int id) {
Goods goods = goodsService.findById(id);
goods.setTitle(goods.getTitle()+"|端口号:"+port);
return goods;
}
}
2、多次访问consumer
多次刷新,发现:ribbon客户端,默认使用轮询算法,经行负载均衡调用。
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。注意:可以通过修改配置loadbalancer. .connectionFailureCountThreshold来修改连接失败多少次之后被设置为短路状态。默认是3次。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上线,可以由客户端的 . .ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。 |
BestAvailableRule | 忽略哪些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
Retry | 重试机制的选择逻辑 |
consumer工程
1、MyRule 返回想要的规则即可
/**
* creste by ydlclass.itcast
*/
@Configuration
public class MyRule {
@Bean
public IRule rule(){
return new RandomRule();
}
}
2、启动类
@RibbonClient(name ="EUREKA-PROVIDER",configuration = MyRule.class)
总结:
consumer工程
EUREKA-PROVIDER: #远程服务名
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #策略
作用:方便运维修改,重启。随时切换策略。
前言:
使用Ribbon调用非常方便 consumer 调用 provider 步骤:
只需把RestTemplate 升级为Ribbon 的客户端 @LoaadBalanced
使用 @LoaadBalanced 的 人RestTemplate 发送一个请求
请求写一个服务名称 就 ok了
已经很简单了, 但是:想 调用 provider 还需要写 restTemplate (比较麻烦),就想像调用本地方法一样 来进行一个远程调用实现 OpenFeign
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
//@RibbonClient(name ="EUREKA-PROVIDER",configuration = MyRule.class) 方式一: 配置类,实现负载均衡策略
@EnableFeignClients // 开启feign 的调用
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
GoodsFeignClient:
@FeignClient("EUREKA-PROVIDER") // Feign 的客户端 调用 EUREKA-PROVIDER 的服务
public interface GoodsFeign {
/**
* 调用的是哪个方法那?
* 不用自己写: 需要调用 01eureka-provider-8000 中的 findById() 方法
* 把其方法复制过来即可
*/
@GetMapping("/goods/findById/{id}")
public Goods findById(@PathVariable("id") Integer id);
}
OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private GoodsFeign goodsFeign;
/**
* OpenFeign 实现调用
*/
@GetMapping("/add/{id}")
public Goods add(@PathVariable("id") Integer id){
//feign调用
Goods goods = goodsFeign.findById(id);
return goods;
}
}
Ribbon默认1秒超时。
超时配置: yml中
# 设置Ribbon的超时时间
ribbon:
ConnectTimeout: 1000 # 连接超时时间 默认1s
ReadTimeout: 3000 # 逻辑处理的超时时间 默认1s
测试:
1、测试连接超时时间,provider都停掉
2、测试逻辑处理的超时时间
provider
@GetMapping("/findById/{id}")
public Goods findById(@PathVariable("id") Integer id){
Goods goods = goodsService.findById(id);
goods.setTitle(goods.getTitle()+"|端口号:"+port);
//模拟业务逻辑比较繁忙
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return goods;
}
1、Feign 只能记录 debug 级别的日志信息。
包名为程序所在的包 : 只要是com.gh 下用Feign 来远程调用,都把debug级别的日志打印出来
# 设置当前的日志级别 debug,feign只支持记录debug级别的日志
logging:
level:
com.gh: debug # 包名为程序所在的包 : 只要是com.gh 下用Feign 来远程调用,都把debug级别的日志打印出来
2、定义Feign日志级别Bean(定义日志的级别:多详细或者多简略 )
FeignLogConfig:
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignLogConfig {
/*
NONE,不记录
BASIC,记录基本的请求行,响应状态码数据
HEADERS,记录基本的请求行,响应状态码数据,记录响应头信息
FULL;记录完成的请求 响应数据
*/
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
3、启用该Bean:
GoodsFeignClient:
@FeignClient(value = "FEIGN-PROVIDER",configuration = FeignLogConfig.class)
设置为Full
设置为headers
重点:能让服务的调用方,够快的知道被调方挂了!不至于说让用户在等待。
Hystix 是 Netflix 开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败(雪崩)。
雪崩:一个服务失败,导致整条链路的服务都失败的情形。
某一个时间点 tb_D 表数据过多,导致D服务挂了,服务之间是依次调用,所以服务一次挂掉,(一直调不通,每秒可能损失10W笔订单)此现象称之为雪崩
官网介绍:https://github.com/Netflix/Hystrix/wiki/How-it-Works
在调用 挂掉的数据是,1s后拿不到数据,返回给调用的服务,相关的信息,能够较快的感知到信息,返回给用户,让用户知道,用户的操作是成功还是失败
调用B、调用C、调用D,C挂了,没调通后进行重试,重试100次,才知道c挂了!(没有hystrix)
使用了hystrix,更细分线程池,B分配 30线程池,C分配 40线程池,D分配 30线程池(设置共100个线程池),让a更快的知道c挂了
此时只能有一个线程请求,因为带着认证信息一个线程进行重复的调用,重试100次,才知道c挂了,没有hystrix
使用了hystrix,更细分线程池,一个带着认证信息的线程,只需要重试40次,让a更快的知道c挂了
服务提供方降级(异常,超时)
根据id 查goods对象 报错 —> 返回一个特殊对象
1、方法的返回值要和原方法一致
2、方法参数和原方法一样
消费方降级
是有限流,但是,项目一般不用。nginx或者网关限流。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
/**
* 定义一个降级方法 返回特殊对象
*
* 1、方法的返回值要和原方法一致
* 2、方法参数和原方法一样
*/
public Goods findById_fallback(Integer id) {
Goods goods = new Goods();
goods.setId(-1);
goods.setTitle("provider提供方降级!");
goods.setPrice(-9.9);
goods.setCount(-10);
return goods;
}
@GetMapping("/findById/{id}")
@HystrixCommand(fallbackMethod = "findById_fallback",
commandProperties = {
//设置Hystrix的超时时间,默认1s
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public Goods findById(@PathVariable("id") Integer id) {
Goods goods = goodsService.findById(id);
// 模拟异常 或者 模拟业务逻辑繁忙
// 1、模拟异常
// int a = 1/0;
// 2、模拟业务逻辑繁忙
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
goods.setTitle(goods.getTitle()+"|端口号:"+port);
return goods;
}
原因: Hystrix 的默认时间是1s需要自行设置
在理解
GoodsFeignClientFallback
/**
* GoodsFeign 的实现类
*/
@Component
public class GoodsFeignCallback implements GoodsFeign {
@Override
public Goods findById(Integer id) {
Goods goods = new Goods();
goods.setId(-2);
goods.setTitle("调用方降级了!");
goods.setPrice(-5.5);
goods.setCount(-5);
return goods;
}
}
GoodsFeignClient
/**
* @author gh Email:@2495140780qq.com
* @Description
* @date 2023-01-13-下午 8:34
*/
// Feign 的客户端 调用 EUREKA-PROVIDER 的服务 时 , 打印的日志 复杂程度是 FeignLogConfig类中的 配置 失败了(fallback) 执行GoodsFeignCallback类中的方法
@FeignClient(value = "EUREKA-PROVIDER",configuration = FeignLogConfig.class,fallback = GoodsFeignCallback.class)
public interface GoodsFeign {
/**
* 调用的是哪个方法那?
* 不用自己写: 需要调用 01eureka-provider-8000 中的 findById() 方法
* 把其方法复制过来即可
*/
@GetMapping("/goods/findById/{id}")
public Goods findById(@PathVariable("id") Integer id);
}
# 开启feign对hystrix的支持
feign:
hystrix:
enabled: true
测试:停掉provider
测试:
provider --> GoodsController
- id = 1 报错、 非1 正常执行
访问两个接口
id = 1 报错、 非1 正常执行,访问id=10 多少次都是正常执行,访问id =1 报错 提供方降级,此时导致id = 10 同样访问不了,在过一段时间之后,会进入到半开状态,在访问id=10 可以进行正常访问。
Hystrix 熔断机制,用于监控微服务调用情况,当失败的情况达到预定的阈值(5秒失败20次),会打开断路器,拒绝所有请求,直到服务恢复正常为止。circuitBreaker.sleepWindowInMilliseconds:监控时间 circuitBreaker.requestVolumeThreshold:失败次数 circuitBreaker.errorThresholdPercentage:失败率
提供者controller中
@HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = { //设置Hystrix的超时时间,默认1s @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"), //监控时间 默认5000 毫秒 @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"), //失败次数。默认20次 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "20"), //失败率 默认50% @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50") })
1色 1圈 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。1线 曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
1、Hystrix 提供了 Hystrix-dashboard 功能,用于实时监控微服务运行状态。
2、但是Hystrix-dashboard只能监控一个微服务。
3、Netflix 还提供了 Turbine ,进行聚合监控。
创建hystrix-monitor模块,使用Turbine聚合监控多个Hystrix dashboard功能
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-turbineartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
spring:
application:
name: hystrix-monitor
server:
port: 8769
turbine:
combine-host-port: true
# 配置需要监控的服务名称列表
app-config: xxx,xxxx, # 如 :EUREKA-PROVIDER,EUREKA-CONSUMER
cluster-name-expression: "'default'"
aggregator:
cluster-config: default
#instanceUrlSuffix: /actuator/hystrix.stream
# eureka 所在的位置 本机的 8761 端口
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
# 根据版本不同进行选择添加
#hystrix:
# dashboard:
# proxy-stream-allow-list: "*"
@SpringBootApplication
@EnableEurekaClient
@EnableTurbine //开启Turbine 很聚合监控功能
@EnableHystrixDashboard //开启Hystrix仪表盘监控功能
public class HystrixMonitorApp {
public static void main(String[] args) {
SpringApplication.run(HystrixMonitorApp.class,args);
}
}
需要分别修改 hystrix-provider 和 hystrix-consumer 模块:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
此处为了方便,将其配置在启动类中。
@Configuration
public class HystrixConfig {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
@EnableHystrixDashboard // 开启Hystrix仪表盘监控功能
在浏览器访问http://localhost:8769/hystrix/ 进入Hystrix Dashboard界面
界面中输入监控的Url地址 http://localhost:8769/turbine.stream,监控时间间隔2000毫秒和title,如下图
# 根据版本不同进行选择添加
#hystrix:
# dashboard:
# proxy-stream-allow-list: "*"
Hystrix需要在主启动类MainAppHystrix8001中指定监控路径
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //Circuit 回路 Breaker 断路器
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
zuul核心人员走了两个,zuul2的研发过久,spring公司等不及,自己研发的Gateway网关。
https://github.com/Netflix/zuul/wiki
功能:路由+过滤
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
项目的架构 不建议使用
存在的问题:
使用了网关:
1、网关旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
2、在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。
3、网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控、路由转发等
4、在目前的网关解决方案里,有Nginx+ Lua、Netflix Zuul/zuul2 、Spring Cloud Gateway等等
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
package com.ydlclass.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class,args);
}
}
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
# filters:配置局部过滤器的
- id: eureka-provider
# 静态路由
# uri: http://localhost:8001/
# 动态路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
filters:
- AddRequestParameter=username,zhangsan
- id: eureka-consumer
# uri: http://localhost:9000
uri: lb://GATEWAY-CONSUMER
predicates:
- Path=/order/**
# 微服务名称配置
discovery:
locator:
enabled: true # 设置为true 请求路径前可以添加微服务名称
lower-case-service-id: true # 允许为小写
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
http://localhost/goods/findById/2
uri: http://localhost:8000/
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
uri: lb://eureka-provider
http://localhost/goods/findById/2
spring:
cloud:
# 网关配置
gateway:
# 微服务名称配置
discovery:
locator:
enabled: true # 设置为true 请求路径前可以添加微服务名称
lower-case-service-id: true # 允许为小写
Gateway 支持过滤器功能,对请求或响应进行拦截,完成一些通用操作。
Gateway 提供两种过滤器方式:“pre”和“post”
# 刚进来走到网关 先做一些事情 在发送给微服务
pre 过滤器,在转发之前执行,可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
# 返回给用户之前
post 过滤器,在响应之前执行,可以做响应内容、响应头的修改,日志的输出,流量监控等。
Gateway 还提供了两种类型过滤器
GatewayFilter:局部过滤器,针对单个路由
GlobalFilter :全局过滤器,针对所有路由
- id: gateway-provider
#uri: http://localhost:8001/
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
filters:
- AddResponseHeader=foo, bar
default-filters:
- AddResponseHeader=yld,itlils
内置的过滤器工厂
这里简单将Spring Cloud Gateway内置的所有过滤器工厂整理成了一张表格。如下:
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 |
无 |
secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large |
请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
Default | 为所有路由添加过滤器 | 过滤器工厂名称及值 |
Tips每个过滤器工厂都对应一个实现类,并且这些类的名称必须以GatewayFilterFactory
结尾,这是Spring Cloud Gateway的一个约定,例如AddRequestHeader
对应的实现类为AddRequestHeaderGatewayFilterFactory
。
官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories
1、AddRequestHeader GatewayFilter Factory
为原始请求添加Header,配置示例:
spring: cloud: gateway: routes: - id: add_request_header_route uri: https://example.org filters: - AddRequestHeader=X-Request-Foo, Bar
为原始请求添加名为
X-Request-Foo
,值为Bar
的请求头2、AddRequestParameter GatewayFilter Factory
为原始请求添加请求参数及值,配置示例:
spring: cloud: gateway: routes: - id: add_request_parameter_route uri: https://example.org filters: - AddRequestParameter=foo, bar
为原始请求添加名为foo,值为bar的参数,即:
foo=bar
3、AddResponseHeader GatewayFilter Factory
为原始响应添加Header,配置示例:
spring: cloud: gateway: routes: - id: add_response_header_route uri: https://example.org filters: - AddResponseHeader=X-Response-Foo, Bar
为原始响应添加名为
X-Request-Foo
,值为Bar
的响应头4、DedupeResponseHeader GatewayFilter Factory
DedupeResponseHeader可以根据配置的Header名称及去重策略剔除响应头中重复的值,这是Spring Cloud Greenwich SR2提供的新特性,低于这个版本无法使用。
我们在Gateway以及微服务上都设置了CORS(解决跨域)Header的话,如果不做任何配置,那么请求 -> 网关 -> 微服务,获得的CORS Header的值,就将会是这样的:
Access-Control-Allow-Credentials: true, true Access-Control-Allow-Origin: https://musk.mars, https://musk.mars
可以看到这两个Header的值都重复了,若想把这两个Header的值去重的话,就需要使用到DedupeResponseHeader,配置示例:
spring: cloud: gateway: routes: - id: dedupe_response_header_route uri: https://example.org filters: # 若需要去重的Header有多个,使用空格分隔 - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
去重策略:
- RETAIN_FIRST:默认值,保留第一个值
- RETAIN_LAST:保留最后一个值
- RETAIN_UNIQUE:保留所有唯一值,以它们第一次出现的顺序保留
若想对该过滤器工厂有个比较全面的了解的话,建议阅读该过滤器工厂的源码,因为源码里有详细的注释及示例,比官方文档写得还好:
org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory
5、Hystrix GatewayFilter Factory
为路由引入Hystrix的断路器保护,配置示例:
spring: cloud: gateway: routes: - id: hystrix_route uri: https://example.org filters: - Hystrix=myCommandName
Hystrix是Spring Cloud第一代容错组件,不过已经进入维护模式,未来Hystrix会被Spring Cloud移除掉,取而代之的是Alibaba Sentinel/Resilience4J。所以本文不做详细介绍了,感兴趣的话可以参考官方文档:
- Hystrix GatewayFilter Factoryopen in new window
6、FallbackHeaders GatewayFilter Factory
同样是对Hystrix的支持,上一小节所介绍的过滤器工厂支持一个配置参数:
fallbackUri
,该配置用于当发生异常时将请求转发到一个特定的uri上。而FallbackHeaders
这个过滤工厂可以在转发请求到该uri时添加一个Header,这个Header的值为具体的异常信息。配置示例:spring: cloud: gateway: routes: - id: ingredients uri: lb://ingredients predicates: - Path=//ingredients/** filters: - name: Hystrix args: name: fetchIngredients fallbackUri: forward:/fallback - id: ingredients-fallback uri: http://localhost:9994 predicates: - Path=/fallback filters: - name: FallbackHeaders args: executionExceptionTypeHeaderName: Test-Header
这里也不做详细介绍了,感兴趣可以参考官方文档:
- FallbackHeaders GatewayFilter Factoryopen in new window
7、PrefixPath GatewayFilter Factory
为原始的请求路径添加一个前缀路径,配置示例:
spring: cloud: gateway: routes: - id: prefixpath_route uri: https://example.org filters: - PrefixPath=/mypath
该配置使访问
${GATEWAY_URL}/hello
会转发到https://example.org/mypath/hello
8、PreserveHostHeader GatewayFilter Factory
为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host Header。配置示例:
spring: cloud: gateway: routes: - id: preserve_host_route uri: https://example.org filters: - PreserveHostHeader
如果不设置,那么名为
Host
的Header将由Http Client控制9、RequestRateLimiter GatewayFilter Factory
用于对请求进行限流,限流算法为令牌桶。配置示例:
spring: cloud: gateway: routes: - id: requestratelimiter_route uri: https://example.org filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20
由于另一篇文章中已经介绍过如何使用该过滤器工厂实现网关限流,所以这里就不再赘述了:
- Spring Cloud Gateway - 扩展open in new window
或者参考官方文档:
- RequestRateLimiter GatewayFilter Factoryopen in new window
10、RedirectTo GatewayFilter Factory
将原始请求重定向到指定的Url,配置示例:
spring: cloud: gateway: routes: - id: redirect_route uri: https://example.org filters: - RedirectTo=302, https://acme.org
该配置使访问
${GATEWAY_URL}/hello
会被重定向到https://acme.org/hello
,并且携带一个Location:http://acme.org
的Header,而返回客户端的HTTP状态码为302注意事项:
- HTTP状态码应为3xx,例如301
- URL必须是合法的URL,该URL会作为
Location
Header的值11、RemoveHopByHopHeadersFilter GatewayFilter Factory
为原始请求删除IETFopen in new window组织规定的一系列Header,默认删除的Header如下:
- Connection
- Keep-Alive
- Proxy-Authenticate
- Proxy-Authorization
- TE
- Trailer
- Transfer-Encoding
- Upgrade
可以通过配置去指定仅删除哪些Header,配置示例:
spring: cloud: gateway: filter: remove-hop-by-hop: # 多个Header使用逗号(,)分隔 headers: Connection,Keep-Alive
12、RemoveRequestHeader GatewayFilter Factory
为原始请求删除某个Header,配置示例:
spring: cloud: gateway: routes: - id: removerequestheader_route uri: https://example.org filters: - RemoveRequestHeader=X-Request-Foo
删除原始请求中名为
X-Request-Foo
的请求头13、RemoveResponseHeader GatewayFilter Factory
为原始响应删除某个Header,配置示例:
spring: cloud: gateway: routes: - id: removeresponseheader_route uri: https://example.org filters: - RemoveResponseHeader=X-Response-Foo
删除原始响应中名为
X-Request-Foo
的响应头14、RewritePath GatewayFilter Factory
通过正则表达式重写原始的请求路径,配置示例:
spring: cloud: gateway: routes: - id: rewritepath_route uri: https://example.org predicates: - Path=/foo/** filters: # 参数1为原始路径的正则表达式,参数2为重写后路径的正则表达式 - RewritePath=/foo/(?
>.*), /$\{segment} 该配置使得访问
/foo/bar
时,会将路径重写为/bar
再进行转发,也就是会转发到https://example.org/bar
。需要注意的是:由于YAML语法,需用$\
替换$
15、RewriteResponseHeader GatewayFilter Factory
重写原始响应中的某个Header,配置示例:
spring: cloud: gateway: routes: - id: rewriteresponseheader_route uri: https://example.org filters: # 参数1为Header名称,参数2为值的正则表达式,参数3为重写后的值 - RewriteResponseHeader=X-Response-Foo, password=[^&]+, password=***
该配置的意义在于:如果响应头中
X-Response-Foo
的值为/42?user=ford&password=omg!what&flag=true
,那么就会被按照配置的值重写成/42?user=ford&password=***&flag=true
,也就是把其中的password=omg!what
重写成了password=***
16、SaveSession GatewayFilter Factory
在转发请求之前,强制执行
WebSession::save
操作,配置示例:spring: cloud: gateway: routes: - id: save_session uri: https://example.org predicates: - Path=/foo/** filters: - SaveSession
主要用在那种像 Spring Session 延迟数据存储(数据不是立刻持久化)的,并希望在请求转发前确保session状态保存情况。如果你将Spring Secutiry于Spring Session集成使用,并想确保安全信息都传到下游机器,你就需要配置这个filter。
17、secureHeaders GatewayFilter Factory
secureHeaders过滤器工厂主要是参考了这篇博客open in new window中的建议,为原始响应添加了一系列起安全作用的响应头。默认会添加如下Headers(包括值):
X-Xss-Protection:1; mode=block
Strict-Transport-Security:max-age=631138519
X-Frame-Options:DENY
X-Content-Type-Options:nosniff
Referrer-Policy:no-referrer
Content-Security-Policy:default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
X-Download-Options:noopen
X-Permitted-Cross-Domain-Policies:none
如果你想修改这些Header的值,那么就需要使用这些Headers对应的后缀,如下:
xss-protection-header
strict-transport-security
frame-options
content-type-options
referrer-policy
content-security-policy
download-options
permitted-cross-domain-policies
配置示例:
spring: cloud: gateway: filter: secure-headers: # 修改 X-Xss-Protection 的值为 2; mode=unblock xss-protection-header: 2; mode=unblock
如果想禁用某些Header,可使用如下配置:
spring: cloud: gateway: filter: secure-headers: # 多个使用逗号(,)分隔 disable: frame-options,download-options
18、SetPath GatewayFilter Factory
修改原始的请求路径,配置示例:
spring: cloud: gateway: routes: - id: setpath_route uri: https://example.org predicates: - Path=/foo/{segment} filters: - SetPath=/{segment}
该配置使访问
${GATEWAY_URL}/foo/bar
时会转发到https://example.org/bar
,也就是原本的/foo/bar
被修改为了/bar
19、SetResponseHeader GatewayFilter Factory
修改原始响应中某个Header的值,配置示例:
spring: cloud: gateway: routes: - id: setresponseheader_route uri: https://example.org filters: - SetResponseHeader=X-Response-Foo, Bar
将原始响应中
X-Response-Foo
的值修改为Bar
20、SetStatus GatewayFilter Factory
修改原始响应的状态码,配置示例:
spring: cloud: gateway: routes: - id: setstatusstring_route uri: https://example.org filters: # 字符串形式 - SetStatus=BAD_REQUEST - id: setstatusint_route uri: https://example.org filters: # 数字形式 - SetStatus=401
SetStatusd的值可以是数字,也可以是字符串。但一定要是Spring
HttpStatus
枚举类中的值。上面这两种配置都可以返回401这个HTTP状态码。21、StripPrefix GatewayFilter Factory
用于截断原始请求的路径,配置示例:
spring: cloud: gateway: routes: - id: nameRoot uri: http://nameservice predicates: - Path=/name/** filters: # 数字表示要截断的路径的数量 - StripPrefix=2
12
如上配置,如果请求的路径为
/name/bar/foo
,那么则会截断成/foo
后进行转发 ,也就是会截断2个路径。22、Retry GatewayFilter Factory
针对不同的响应进行重试,例如可以针对HTTP状态码进行重试,配置示例:
spring: cloud: gateway: routes: - id: retry_test uri: http://localhost:8080/flakey predicates: - Host=*.retry.com filters: - name: Retry args: retries: 3 statuses: BAD_GATEWAY
可配置如下参数:
retries
:重试次数statuses
:需要重试的状态码,取值在org.springframework.http.HttpStatus
中methods
:需要重试的请求方法,取值在org.springframework.http.HttpMethod
中series
:HTTP状态码序列,取值在org.springframework.http.HttpStatus.Series
中23、RequestSize GatewayFilter Factory
设置允许接收最大请求包的大小,配置示例:
spring: cloud: gateway: routes: - id: request_size_route uri: http://localhost:8080/upload predicates: - Path=/upload filters: - name: RequestSize args: # 单位为字节 maxSize: 5000000
如果请求包大小超过设置的值,则会返回
413 Payload Too Large
以及一个errorMessage
24、Modify Request Body GatewayFilter Factory
在转发请求之前修改原始请求体内容,该过滤器工厂只能通过代码配置,不支持在配置文件中配置。代码示例:
@Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes() .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org") .filters(f -> f.prefixPath("/httpbin") .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, (exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri)) .build(); } static class Hello { String message; public Hello() { } public Hello(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
Tips:该过滤器工厂处于 BETA 状态,未来API可能会变化,生产环境请慎用
25、Modify Response Body GatewayFilter Factory
可用于修改原始响应体的内容,该过滤器工厂同样只能通过代码配置,不支持在配置文件中配置。代码示例:
@Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes() .route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org") .filters(f -> f.prefixPath("/httpbin") .modifyResponseBody(String.class, String.class, (exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri) .build(); }
Tips:该过滤器工厂处于 BETA 状态,未来API可能会变化,生产环境请慎用
26、Default Filters
Default Filters用于为所有路由添加过滤器工厂,也就是说通过Default Filter所配置的过滤器工厂会作用到所有的路由上。配置示例:
spring: cloud: gateway: default-filters: - AddResponseHeader=X-Response-Default-Foo, Default-Bar - PrefixPath=/httpbin
StripPrefix的源码 --> 照猫画虎
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.filter.factory;
import java.util.Arrays;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory<StripPrefixGatewayFilterFactory.Config> {
public static final String PARTS_KEY = "parts";
public StripPrefixGatewayFilterFactory() {
super(StripPrefixGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("parts");
}
public GatewayFilter apply(StripPrefixGatewayFilterFactory.Config config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());
String path = request.getURI().getRawPath();
String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");
StringBuilder newPath = new StringBuilder("/");
for(int i = 0; i < originalParts.length; ++i) {
if (i >= config.getParts()) {
if (newPath.length() > 1) {
newPath.append('/');
}
newPath.append(originalParts[i]);
}
}
if (newPath.length() > 1 && path.endsWith("/")) {
newPath.append('/');
}
ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
return chain.filter(exchange.mutate().request(newRequest).build());
}
public String toString() {
return GatewayToStringStyler.filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()).toString();
}
};
}
public static class Config {
private int parts;
public Config() {
}
public int getParts() {
return this.parts;
}
public void setParts(int parts) {
this.parts = parts;
}
}
}
需求:
自定义全局过滤器步骤:
IpFilter
package com.gh.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.InetAddress;
import java.net.InetSocketAddress;
/**
* 实现Ip拦截过滤器
* 1、实现两个接口
* 2、复写方法
* 3、完成逻辑处理
*/
@Component
public class IpFilter implements GlobalFilter, Ordered {
// 写业务逻辑
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 拿到请求 和 响应
// 找远程ip
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
InetSocketAddress remoteAddress = request.getRemoteAddress(); // 客户端请求的ip
System.out.println("remoteAddress = " + remoteAddress);
String hostName = remoteAddress.getHostName(); // 获取本机主机名称
System.out.println("hostName = " + hostName);
InetAddress address = remoteAddress.getAddress();
System.out.println("address = " + address);
String hostAddress = address.getHostAddress(); // ip地址
System.out.println("hostAddress = " + hostAddress);
String hostName1 = address.getHostName();
System.out.println("hostName1 = " + hostName1);
/*
remoteAddress = /192.168.2.xx:2713
hostName = DESKTOP-KQD226K.lan
address = DESKTOP-KQD226K.lan/192.xx.2.128
hostAddress = 192.168.2.xxx
hostName1 = DESKTOP-KQD226K.lan
*/
if (hostAddress.equals("192.168.2.xxx")) {
// 让此 ip 拒绝访问 正常应该在数据库中获取
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 设置响应码 401 (未认证)
return response.setComplete();
}
//走完了,该到下一个过滤器了
return chain.filter(exchange);
}
// 返回一个数值 越小越先执行
@Override
public int getOrder() {
return 0;
}
}
测试:
http://192.168.2.128/goods/findById/5 不使用localhost 使用自己的ip进行访问
测试 --> 访问被拦截 把自己的ip设置为拒绝访问的话
UrlFilter
/**
* 某些请求路径 如“goods/findById”,危险操作,记录日志。
*/
@Component
public class UrlFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//某些请求路径 如“goods/findById”,危险操作,记录日志。
//拿到请求和响应,为所欲为
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
URI uri = request.getURI();
System.out.println("uri = " + uri);
String path = uri.getPath(); // 路径
System.out.println(path);
if(path.contains("goods/findById")){ // 包含路径
//打日志
System.out.println("path很危险!");
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}