国内漂亮大气的网站,淘宝网站如何做虚拟,建网站自学,网站开发培训达内一 服务注册发现
1 什么是服务治理 为什么需要服务治理
在没有进行服务治理前,服务之间的通信是通过服务间直接相互调用来实现的。 过程#xff1a; 武当派直接调用峨眉派和华山派#xff0c;同样#xff0c;华山派直接调用武当派和峨眉派。如果系统不复杂#xff0c;这样…一 服务注册发现
1 什么是服务治理 为什么需要服务治理
在没有进行服务治理前,服务之间的通信是通过服务间直接相互调用来实现的。 过程 武当派直接调用峨眉派和华山派同样华山派直接调用武当派和峨眉派。如果系统不复杂这样调用没什么问题。但在复杂的微服务系统中采用这样的调用方法就会产生问题。 微服务系统中服务众多这样会导致服务间的相互调用非常不便 因为要记住提供服务的IP地址、名称、端口号等。这时就需要中间代理通过中间代理来完成调用。
服务治理的解决方案 服务治理责任 你是谁服务注册 - 服务提供方自报家门你来自哪里服务发现 - 服务消费者拉取注册数据你好吗心跳检测服务续约和服务剔除一套由服务提供方和注册中心配合完成的去伪存真的过程当你死的时候服务下线 - 服务提供方发起主动下线 服务治理技术选型 注意 在架构选型的时候我们需要注意一下切记不能为了新而新 忽略了对于当前业务的支持虽然Eureka2.0不开源了但是谁知道以后会不会变化而且1.0也是可以正常使用的也有一些贡献者在维护这个项目所以我们不必要过多的担心这个问题要针对于业务看下该技术框架是否支持在做考虑。 2 Eureka概述 Spring Cloud Eureka 是Netflix 开发的注册发现组件本身是一个 基于 REST 的服务。提供注册与发现同时还提供了负载均衡、故障转移等能力。
Eureka3个角色
服务中心服务提供者服务消费者。 注意 Eureka Server服务器端。它提供服务的注册和发现功能即实现服务的治理。Service Provider服务提供者。它将自身服务注册到Eureka Server中以便“服务消费者” 能够通过服务器端提供的服务清单注册服务列表来调用它。Service Consumer服务消费者。它从 Eureka 获取“已注册的服务列表”从而消费服务。 比Zookeeper好在哪里呢 当向注册中心查询服务列表时我们可以容忍注册中心返回的是几分钟以前的注册信息但不能接受服务直接down掉不可用。也就是说服务注册功能对可用性的要求要高于一致性。 注意 Zookeeper会出现这样一种情况当Master节点因为网络故障 与其他节点失去联系时剩余节点会重新进行leader选举。问题在于选举leader的时间太长30120s且选举期间整个zk集群都是不可用的这就导致在选举期间注册服务瘫痪。 结论
Eureka看明白了这一点因此在设计时就优先保证可用性。
3 微服务聚合父工程构建
New Project 聚合总工程名称 字符编码 注解生效激活 Java编译版本选择 File Type过滤 父工程POM
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.lxx/groupIdartifactIdcloud/artifactIdversion1.0-SNAPSHOT/versionpackagingpom/packaging!-- 统一管理jar包版本 --propertiesproject.build.sourceEncodingUTF8/project.build.sourceEncodingmaven.compiler.source1.8/maven.compiler.sourcemaven.compiler.target1.8/maven.compiler.targetspring-cloud.version2021.0.0/spring-cloud.versionspring-boot.version2.6.3/spring-boot.version/properties!-- pom import 解决maven单继承问题 --dependencyManagementdependencies!--spring boot 2.6.3--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion${spring-boot.version}/versiontypepom/typescopeimport/scope/dependency!--spring cloud 2021.0.0--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-dependencies/artifactIdversion${spring-cloud.version}/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagement
/projectIDEA开启Dashboard
2022-06-16更新idea后面的版本将Dashboard改成了Services
1.在idea中打开Services窗口View Tool Windows Services 或者直接快捷键 Alt 8
2.添加service服务
4 搭建单机Eureka注册中心
创建cloud-eureka-server7001模块 pom添加依赖 dependencies!-- 服务注册发现Eureka--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-server/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies写yml文件
server:port: 7001
eureka:instance:# eureka服务端的实例名字hostname: localhostclient:# 表示是否将自己注册到Eureka Serverregister-with-eureka: false# 表示是否从Eureka Server获取注册的服务信息fetch-registry: false# 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址service-url:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/主启动类
Slf4j
SpringBootApplication
EnableEurekaServer
public class EurekaMain7001 {public static void main(String[] args) {SpringApplication.run(EurekaMain7001.class, args);log.info(*************** Eureka 服务启动成功 端口 7001 ***********);}
}测试
访问浏览器localhost:7001 5 解读Eureka注册中心UI界面 参数 Environment: 环境默认为test该参数在实际使用过程中可以不用更改Data center 数据中心使用的是默认的是 “MyOwn”Current time当前的系统时间Uptime已经运行了多少时间Lease expiration enabled是否启用租约过期 自我保护机制关闭时该值默认是true 自我保护机制开启之后为false。Renews threshold 每分钟最少续约数Eureka Server 期望每分钟收到客户端实例续约的 总数。Renews (last min) 最后一分钟的续约数量不含当前1分钟更新一次Eureka Server 最后 1 分钟收到客户端实例续约的总数。 DS Replicas 参数 这个下面的信息是这个Eureka Server相邻节点互为一个集群。注册到这个服务上的实例信息 Instances currently registered with Eureka
注册到Eureka服务上的实例信息。 参数 Application服务名称。配置的spring.application.name属性AMIsn/a (1)字符串n/a实例的数量我不了解Availability Zones实例的数量Status实例的状态 eureka.instance.instance‐id的值。 实例的状态分为UP、DOWN、STARTING、 OUT_OF_SERVICE、UNKNOWN. UP 服务正常运行特殊情况当进入自我保护模式所有的服务依然是UP状态所以需要做好熔断重试等容错机制应对灾难性网络出错情况OUT_OF_SERVICE : 不再提供服务其他的Eureka Client将调用不到该服务一般有人为的调用接口设置的如强制下线。UNKNOWN 未知状态STARTING 表示服务正在启动中DOWN 表示服务已经宕机无法继续提供服务 General Info 参数 total-avail-memory : 总共可用的内存environment : 环境名称默认testnum-of-cpus : CPU的个数current-memory-usage : 当前已经使用内存的百分比server-uptime : 服务启动时间registered-replicas : 相邻集群复制节点unavailable-replicas 不可用的集群复制节点如何确定不可用 主要是server1 向 server2和server3发送接口查询自身的注册信息。available-replicas 可用的相邻集群复制节点 Instance Info 参数 ipAddreureka服务端IPstatuseureka服务端状态 6 创建服务提供者 创建cloud-provider-payment8001模块 pom文件添加依赖 !-- 引入Eureka client依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency写yml文件
server:port: 8001eureka:client:service-url:# Eureka Server 地址defaultZone: http://localhost:7001/eureka/编写主启动类
/*** 主启动类*/
EnableEurekaClient
SpringBootApplication
Slf4j
public class PaymentMain8001 {public static void main(String[] args) {SpringApplication.run(PaymentMain8001.class, args);log.info(********* 服务提供者启动成功 ******);}
}测试
先启动EurekaServer服务访问http://locahost:7001 注意 application名字未定义。 修改yml文件
spring:application:# 设置应用名词name: cloud-payment-provider7 创建服务消费者 创建cloud-consumer-order80模块 pom文件添加依赖 !-- 引入Eureka client依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency写yml文件
spring:application:# 设置应用名字name: cloud-order-consumerserver:port: 80eureka:client:service-url:# Eureka Server地址defaultZone: http://localhost:7001/eureka/
编写主启动类
SpringBootApplication
EnableEurekaClient
Slf4j
public class OrderMain80 {public static void main(String[] args) {SpringApplication.run(OrderMain80.class, args);log.info(*************** 订单服务消费者启动成功 ***********);}
}测试
先启动EurekaServer服务访问http://locahost:7001 8 服务自保和服务剔除机制 服务剔除服务自保这两套功法一邪一正俨然就是失传多年的 上乘心法的上卷和下卷。但是往往你施展了服务剔除便无法施展服务自保而施展了服务自保便无法施展服务剔除。也就是说注册中心在同一时刻只能施展一种心法不可两种同时施展。
服务剔除 注意 服务剔除把服务节点果断剔除即使你的续约请求晚了一步也毫不留情招式凌厉重在当断则断忍痛割爱。 心法总决简明扼要 欲练此功必先自宫 服务自保 注意 服务自保把当前所有节点保留一个都不能少绝不放弃任何队友。心法的指导思想是即便主动删除也许并不能解决问题且放之任之以不变应万变。 心法总决引人深思 宫了以后未必成功 如果不宫或可成功 心法总纲
在实际应用里并不是所有无心跳的服务都不可用也许因为短暂的网络抖动等原因导致服务节点与注册中心之间续约不上但服务节点之间的调用还是属于可用状态这时如果强行剔除服务节点可能会造成大范围的业务停滞。
Euraka服务自保的触发机关
自动开关 注意 服务自保模式往往是为了应对短暂的网络环境问题在理想情况下服务节点的续约成功率应该接近100%如果突然发生网络问题比如一部分机房无法连接到注册中心这时候续约成功 率有可能大幅降低。但考虑到Eureka采用客户端的服务发现模式客户端手里有所有节点的地址如果服务节点只是因为网络原因无法续约但其自身服务是可用的那么客户端仍然可以成功发起调用请求。这样就避免了被服务剔除给错杀。 手动开关
这是服务自保的总闸以下配置将强制关闭服务自保即便上面的自动开关被触发也不能开启自保功能。
eureka:server:# 参数来关闭保护机制以确保注册中心可以将不可用的实例正确剔除默认为true。enable-self-preservation: false9 actuator微服务信息完善 SpringCloud体系里的服务实体向eureka注册时注册名默认是 IP名:应用名:应用端口名。 问题 自定义服务在Eureka上的实例名怎么弄呢 在服务提供者和消费者pom中配置Actuator依赖 !-- actuator监控信息完善 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency在服务提供者生产者application.yml中加入
eureka:instance:#实例名称(根据需要自己起名字)instance-id: cloud-payment-provider8001在服务提供者消费者application.yml中加入
eureka:instance:# 实例名称instance-id: cloud-order-consumer80测试 10 服务发现Discovery 编写payment8001的Controller
Slf4j
RestController
RequestMapping(/payment)
public class PaymentController {Autowiredprivate DiscoveryClient discoveryClient;// 获取所有微服务信息GetMapping(/discovery)public Object discovery() {ListString services discoveryClient.getServices();for (String service : services) {log.info(server:{}, service);}return this.discoveryClient;}}
RestTemplate介绍
RestTemplate 是从 Spring3.0 开始支持的一个HTTP 请求工具它 提供了常见的REST请求方案的模版例如 GET 请求、POST 请求、 PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。
在配置类中的restTemplate添加LoadBalanced注解
这个注解会 给这个组件 有负载均衡的功能
Configuration
public class CloudConfig {LoadBalancedBeanpublic RestTemplate restTemplate(){return new RestTemplate();}
}修改payment8001工程controller
Slf4j
RestController
RequestMapping(/payment)
public class PaymentController {//测试服务调用GetMapping(/index)public String index() {return payment successs;}}编写order80工程Controller
RestController
RequestMapping(/order)
public class OrderController {//发现服务Autowiredprivate DiscoveryClient discoveryClient;// HTTP 请求工具Autowiredprivate RestTemplate restTemplate;/*** 测试服务发现接口** return*/GetMapping(/index)public String index() {//服务生产者名字String hostName CLOUD-PAYMENT-PROVIDER;//远程调用方法具体URLString url /payment/index;//1.从Eureka服务的发现中获取服务生产者的实例ListServiceInstance instances discoveryClient.getInstances(hostName);//2.获取到具体的服务生产者实例ServiceInstance serviceInstance instances.get(0);//3.发起远程调用String forObject restTemplate.getForObject(serviceInstance.getUri() url, String.class);return forObject;}}
11 高可用Eureka注册中心 在微服务架构这样的分布式环境中我们需要充分考虑发生故障的情况所以在生产环境中必须对各个组件进行高可用部署对于微服务如此对于服务注册中心也一样。 问题 Spring-Cloud为基础的微服务架构所有的微服务都需要注册到注册中心如果这个注册中心阻塞或者崩了那么整个系统都无法继续正常提供服务所以这里就需要对注册中心搭 建高可用HA集群。 Eureka Server的设计一开始就考虑了高可用问题在Eureka的服 务治理设计中所有节点即是服务提供方也是服务消费方服务 注册中心也不例外。是否还记得在单节点的配置中我们设置过下 面这两个参数让服务注册中心不注册自己:
eureka:client:# 表示是否将自己注册到Eureka Serverregister-with-eureka: false# 表示是否从Eureka Server获取注册的服务信息fetch-registry: false12 高可用Eureka注册中心搭建
构建cloud-eureka-server7002工程 pom文件添加依赖 !-- 服务注册发现Eureka--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-server/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency修改7001YML文件
server:port: 7001
eureka:server:# 参数来关闭保护机制以确保注册中心可以将不可用的实例正确剔除默认为true。enable-self-preservation: falseinstance:# eureka服务端的实例名字hostname: eureka7001.comclient:# 表示是否将自己注册到Eureka Serverregister-with-eureka: false# 表示是否从Eureka Server获取注册的服务信息fetch-registry: false# 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址service-url:defaultZone: http://localhost:7002/eureka/
修改7002YML文件
server:port: 7002
eureka:server:# 关闭保护机制enable-self-preservation: falseinstance:# eureka服务端的实例名字hostname: eureka7002.comclient:# 表示是否将自己注册到Eureka Serverregister-with-eureka: false# 表示是否从Eureka Server获取注册的服务信息fetch-registry: false# 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址service-url:defaultZone: http://localhost:7001/eureka/
编写主启动类
Slf4j
SpringBootApplication
EnableEurekaServer
public class EurekaMain7002 {public static void main(String[] args) {SpringApplication.run(EurekaMain7002.class, args);log.info(*************** Eureka 服务启动成功 端口 7002 ***********);}
}将支付微服务8001发布到Eureka集群上
spring:application:# 设置应用名词name: cloud-payment-providerserver:port: 8001eureka:client:service-url:# 单机 Eureka Server 地址#defaultZone: http://localhost:7001/eureka/# 集群 Eureka Server 地址defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/instance:#实例名称(根据需要自己起名字)instance-id: cloud-payment-provider8001 将订单微服务80发布到Eureka集群上
spring:application:# 设置应用名字name: cloud-order-consumerserver:port: 80eureka:client:service-url:# Eureka Server地址defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/instance:# 实例名称instance-id: cloud-order-consumer80测试
先启动EurekaServer集群在启动服务提供者provider服务在启动消费者服务 二 客户端负载均衡
1 什么是负载均衡 为什么需要负载均衡
俗话说在生产队薅羊毛不能逮着一只羊薅在微服务领域也是这个 道理。面对一个庞大的微服务集群如果你每次发起服务调用都只盯着那一两台服务器在大用户访问量的情况下这几台被薅羊毛的服务器一定会不堪重负。
负载均衡要干什么事情
负载均衡有两大门派服务端负载均衡和客户端负载均衡。我们先来聊聊这两个不同门派的使用场景再来看看本节课的主角Spring Cloud Loadbalancer 属于哪门哪派。
服务端负载均衡
在服务集群内设置一个中心化负载均衡器比如Nginx。发起服务间调用的时候服务请求并不直接发向目标服务器而是发给这个全局负载均衡器它再根据配置的负载均衡策略将请求转发到目标服务。 优点 服务端负载均衡应用范围非常广它不依赖于服务发现技术客户端并不需要拉取完整的服务列表同时发起服务调用的客户端也不用操心该使用什么负载均衡策略。 劣势 网络消耗复杂度和故障率提升 Spring Cloud Loadbalancer 可以很好地弥补上面的劣势那么它是如何做到的呢
客户端负载均衡
Spring Cloud Loadbalancer 采用了客户端负载均衡技术每个发起服务调用的客户端都存有完整的目标服务地址列表根据配置的负载均衡策略由客户端自己决定向哪台服务器发起调用。 优势 网络开销小配置灵活 劣势 需要满足⼀个前置条件发起服务调用的客户端需要获取所有目标服务的地址这样它才能使用负载均衡规则选取要调用的服务。也就是说客户端负载均衡技术往往需要依赖服务发现技术来获取服务列表。 负载均衡需要解决两个最基本的问题:
第一个是从哪里选服务实例
在Spring Cloud的Eureka微服务系统中维护微服务实例清单的是 Eureka服务治理中心而具体微服务实例会执行服务获取获得微服务实例清单缓存到本地同时还会按照一个时间间隔更新这份实例清单因为实例清单也是在不断维护和变化的)。
第二个是如何选择服务实例
通过过负载均衡的策略从服务实例清单列表中选择具体实例。 注意 Eurka和Loadbalancer 自然而然地到了一起一个通过服务发现获取服务列表另一个使用负载均衡规则选出目标服务器然后过着没羞没躁的生活。 什么是Spring Cloud Ribbon
Spring Cloud Ribbon是NetFlix发布的负载均衡器它有助于Http 和Tcp的客户端行为。可以根据负载均衡算法轮询、随机或自定义自动帮助消费者请求默认就是轮询。 问题 状态 - 停更进维替代方案 - Spring Cloud Loadbalancer 什么是Spring Cloud LoadBalancer
但是由于Ribbon已经进入维护模式并且Ribbon 2并不与Ribbon 1 相互兼容所以Spring Cloud全家桶在Spring Cloud Commons项 目中添加了Spring cloud Loadbalancer作为新的负载均衡器并且做了向前兼容就算你的项目中继续用 Spring Cloud Netflix套 装包括RibbonEurekaZuulHystrix等等让你的项目中有这些依赖你也可以通过简单的配置把Ribbon替换成Spring Cloud LoadBalancer。
2 服务端负载均衡和客户端负载均衡的区别
服务端负载均衡示意图 客户端负载均衡示意图 通过上面两个图的对比可以发现服务端负载均衡和客户端负载均衡的主要区别就在于负载均衡发生的位置的不同服务端负载均衡是发生在服务提供方比如常见的nginx负载均衡。而客户端负载均衡则是发生在发起请求的消费者方消费者作为客户端在发起请求时就已经选好处理该请求的实例了像微服务中的远程调用就是把注册中心的服务列表缓存在本地然后发起请求时直接选择一个服务实例提供服务。
总结
其实简单点想服务列表或者说服务信息保存在哪一方是客户端还是服务端那负载均衡对应的就是客户端负载均衡或者服务端负载均衡。
3 负载均衡策略 以前的Ribbon有多种负载均衡策略
RandomRule - 随性而为 解释 随机 RoundRobinRule - 按部就班 解释 轮询 RetryRule - 卷土重来 解释 先按照RoundRobinRule的策略获取服务如果获取服务失败则在指定时间内会进行重试。 WeightedResponseTimeRule - 能者多劳
这个Rule继承自RoundRibbonRule他会根据服务节点的响应时间计算权重响应时间越长权重就越低响应越快则权重越高权重 的高低决定了机器被选中概率的高低。也就是说响应时间越小的机器被选中的概率越大。 解释 对RoundRobinRule的扩展响应速度越快的实例选择权重越大越容易被选择 BestAvailableRule - 让最闲的人来 解释 应该说这个Rule有点智能的味道了在过滤掉故障服务以后 它会基于过去30分钟的统计结果选取当前并发量最小的服务节点也就是最“闲”的节点作为目标地址。如果统计结果尚未生成则采用轮询的方式选定节点。 AvailabilityFilteringRule - 我是有底线的
这个规则底层依赖RandomRobinRule来选取节点但并非来者不拒它也是有一些底线的必须要满足它的最低要求的节点才会被选中。如果节点满足了要求无论其响应时间或者当前并发量是什么都会被选中。 解释 每次AvailabilityFilteringRule简称AFR都会请求RobinRule 挑选一个节点然后对这个节点做以下两步检查是否处于不可用节点当前的active请求连接数超过阈值超过了则表示节点目前太忙不适合接客,如果被选中的server不幸挂掉了检查那么AFR会自动重试次数最多10次让RobinRule重新选择一个服务节点。 ZoneAvoidanceRule - 我的地盘我做主 解释 默认规则复合判断server所在区域的性能和server的可用性选择服务器 但LoadBalancer只提供了两种负载均衡器
RandomLoadBalancer 随机RoundRobinLoadBalancer 轮询 注意 不指定的时候默认用的是轮询
三 服务接口调用
1 OpenFeign概述 OpenFeign是什么
Spring Cloud OpenFeign用于Spring Boot应用程序的声明式REST客户端。 OpenFeign能干嘛
Feign旨在使编写Java Http客户端变得更容易。前面在使用RestTemplate时利用RestTemplate对http请求的封装处理形成了一套模版化的调用方法。 OpenFeign和Feign两者区别
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装使其支持了Spring MVC标准注解和HttpMessageConverters。
FeignOpenFeignFeign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端Feign内置了Ribbon用来做客户端负载均衡去调用服务注册中心的服务。OpenFeign是Spring Cloud在Feign的基础上支持了 SpringMVC的注解如RequesMapping等等。OpenFeign 的Feignclient可以解析SpringMVC的RequestMapping注解下的接口并通过动态代理的方式产生实现类实现类中做负载均衡并调用其他服务。Spring-cloud-starter-feignspring-cloud-starter-openfeign 注意 接口注解。 2 入门案列
构建cloud-consumer-feign-order80工程 修改POM文件 !-- 引入Eureka 客户端依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependency!-- 引入服务调用依赖 OpenFigen --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency!-- actuator监控信息完善 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency
编写YML文件
spring:application:# 设置应用名词name: cloud-openfeign-order-consumer
server:port: 80eureka:client:# Eureka Server地址service-url:defaultZone: http://localhost:7001/eureka,http://localhost:7002/eurekainstance:instance-id: cloud-openfeign-order-consumer80编写主启动类
Slf4j
SpringBootApplication
EnableFeignClients
public class OrderFeignMain80 {public static void main(String[] args) {SpringApplication.run(OrderFeignMain80.class,args);log.info(************** OrderFeignMain80 服务启动成功 **********);}
}编写业务逻辑接口PaymentFeignService
/*** 支付远程调用Feign接口*/
// 声明为OpenFeign客户端 (value 调用的服务生产者名字)
FeignClient(value CLOUD-PAYMENT-PROVIDER)
public interface PaymentFeignService {GetMapping(/payment/index)String index();
}
编写控制层Controller
/*** 订单控制层*/
RestController
RequestMapping(/order)
public class OrderController {Autowiredprivate PaymentFeignService paymentFeignService;/*** 测试OpenFeign接口调用** return*/GetMapping(/index)public String get() {return paymentFeignService.index();}
}测试
先启动2个Eureka集群7001/7002在启动服务提供者payment8001在启动服务消费者cloud-consumer-feign-order浏览器访问http://localhost/order/index
3 日志增强 OpenFeign虽然提供了日志增强功能但是默认是不显示任何日志的不过开发者在调试阶段可以自己配置日志的级别。
OpenFeign的日志级别如下
NONE默认的不显示任何日志;BASIC仅记录请求方法、URL、响应状态码及执行时间;HEADERS除了BASIC中定义的信息之外还有请求和响应的头信息;FULL除了HEADERS中定义的信息之外还有请求和响应的正文及元数据。
配置类中配置日志级别
Configuration
public class OpenFeignConfig{/*** 日志级别定义*/BeanLogger.Level feignLoggerLevel(){return Logger.Level.FULL;}
}注意 这里的logger是feign包里的。 yaml文件中设置接口日志级别
logging:level:com.lxx.service: debug注意 这里的 com.lxx.service 是openFeign接口所在的包名当然你也可以配置一个特定的openFeign接口。 测试
请求http://localhost/order/index 4 超时机制 超时机制 问题 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形这个时候服务消费者会一直等待下去。在某个峰值时刻大呈的请求都在同时请求服务消费者会造成线程的大呈堆积势必会造成雪崩。利用超时机制来解决这个问题设置一个超时时间在这个时间段内无法完成服务访问 则自动断开连接。 服务消费方80配置超时时间
feign:client:config:default:connectTimeout: 2000 # 连接超时时间readTimeout: 2000 # 读取超时时间服务提供方8001故意写超时程序 GetMapping(/timeout)public String timeout() {try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}return timeout;}服务消费方80添加超时方法PaymentFeignService
FeignClient(value CLOUD-PAYMENT-PROVIDER)
public interface PaymentFeignService {GetMapping(/payment/timeout)String timeout();
}服务消费方80添加超时方法OrderController
/*** 订单控制层*/
RestController
RequestMapping(/order)
public class OrderController {Autowiredprivate PaymentFeignService paymentFeignService;/*** 测试超时机制* return*/GetMapping(/timeout)public String timeout() {return paymentFeignService.timeout();}
}
四 服务断路器
1 什么是灾难性雪崩效应 什么是灾难性雪崩效应
假设我们有两个访问量比较大的服务A和B这两个服务分别依赖C 和D,C和D服务都依赖E服务。 A和B不断的调用C,D处理客户请求和返回需要的数据。当E服务不能供服务的时候C和D的超时和重试机制会被执行 由于新的调用不断的产生会导致C和D对E服务的调用大量的积压产生大量的调用等待和重试调用慢慢会耗尽C和D的资源比如内存或CPU然后也down掉。 A和B服务会重复C和D的操作资源耗尽然后down掉最终整个服务都不可访问。 结论 服务与服务之间的依赖性故障会传播造成连锁反应会对整个微服务系统造成灾难性的严重后果这就是服务故障的“雪崩”效应。 造成雪崩原因是什么
服务提供者不可用硬件故障、程序bug、缓存击穿、用户大量请求重试加大流量用户重试代码逻辑重试服务调用者不可用同步等待造成的资源耗尽 注意 在高并发访问下系统所依赖的服务的稳定性对系统的影响非常大依赖有很多不可控的因素比如网络连接变慢资源突然繁忙暂时不可用服务脱机等。我们要构建稳定、可靠的分布式系统就必须要有一套容错方法。 2 服务雪崩解决方案之服务熔断 保险丝电路中正确安置保险丝保险丝就会在电流异常升高到一定的高度和热度的时候自身熔断切断电流保护了电路安全运行。
什么是熔断
熔断就跟保险丝一样当一个服务请求并发特别大服务器已经招架不住了调用错误率飙升当错误率达到一定阈值后就将这个服务熔断了。熔断之后后续的请求就不会再请求服务器了以减缓服务器的压力。 注意 当失败率如因网络故障/超时造成的失败率高达到阀值自动触发降级熔断器触发的快速失败会进行快速恢复。 3 服务雪崩解决方案之服务降级 什么是服务降级 两种场景:
当下游的服务因为某种原因响应过慢下游服务主动停掉一些不太重要的业务释放出服务器资源增加响应速度当下游的服务因为某种原因不可用上游服务主动调用本地的一些降级逻辑避免卡顿迅速返回给用户
服务降级 fallback
概念服务器繁忙请稍后重试不让客户端等待并立即返回一个友好的提示。
出现服务降级的情况
程序运行异常超时服务熔断触发服务降级线程池/信号量打满也会导致服务降级
4 服务雪崩解决方案之服务隔离 那显而易见做服务隔离的目的就是避免服务之间相互影响。毕竟谁也不能说自己的微服务百分百可用如果不做隔离一旦一个服务出现了问题整个系统的稳定性都会受到影响 因此做服务隔离是很有必要的。
什么是线程池隔离
将用户请求线程和服务执行线程分割开来同时约定了每个服务最多可用线程数。 使用线程池隔离后 解释 动物园有了新规矩-线程隔离就是说每个服务单独设置一个小房间独立线程池把大厅区域和服务区域隔离开来每个服务房间也有接待数量限制比如我设置了熊猫馆最多接纳10 人犀牛管最多5人大象馆20人。这样即便来了20个人想看熊猫我们也只能接待10人剩下的10个人就会收到Thread Pool Rejects。如此一来也不会耽搁动物园为用户提供其他服务。 什么是信号量隔离
小时候我们就知道“红灯停绿灯行”跟着交通信号的指示过马路。信号量也是这么一种放行、禁行的开关作用。它和线程池技术一样控制了服务可以被同时访问的并发数量。 线程池隔离和信号量隔离区别
隔离方式是否支持超时是否支持熔断隔离原理是否是异步调用资源消耗线程池隔离支持可直接返回支持当线程池到达 maxSize后再请求会触发 fallback接口进行熔断每 服务 单独 用线 程池可以是异步 也可以是同 步。看调用的 方法大大量线程的 上下文切换容 易造成机器负载 高信号量隔离不支持如果阻塞只能 通过调用协议如 socket超时才能返回支持当信号量达到 maxConcurrentRequests 后。再请求会触发fallback通过信号量的 计数器同步调用不 支持异步小只是个计数 器
5 服务雪崩解决方案之服务限流 服务熔断和服务隔离都属于出错后的容错处理机制而限流模式则可以称为预防模式。 限流模式主要是提前对各个类型的请求设置最高的QPS阈值若高于设置的阈值则对该请求直接返回不再调用后续资源。 注意 限流的目的是通过对并发访问/请求进行限速或者对一个时间窗口内的请求进行限速来保护系统一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。 流量控制
网关限流防止大量请求进入系统Mq实现流量消峰用户交流限流提交按钮限制点击频率限制等
6 Resilience4j介绍 什么是Hystrix
我们耳熟能详的就是Netflix Hystrix,这个断路器是SpringCloud中最早支持的一种容错方案现在这个断路器已经处于维护状态已经不再更新了你仍然可以使用这个断路器但是呢我不建议你去使用因为这个已经不再更新所以Spring官方已经出现了Netflix Hystrix的替换方案。 什么是Resilience4j
Resilience4j是一个轻量级的容错组件其灵感来自于Hystrix但主要为Java 8和函数式编程所设计,也就是我们的lambda表达式。轻量级体现在其只用 Vavr 库前身是 Javaslang没有任何外部依赖。而Hystrix依赖了Archaius Archaius本身又依赖很多第三方包例如 Guava、Apache Commons Configuration 等。
Resilience4j官网
https://resilience4j.readme.io/
Resilience4J 提供了一系列增强微服务的可用性功能
resilience4j-circuitbreaker熔断resilience4j-ratelimiter限流resilience4j-bulkhead隔离resilience4j-retry自动重试resilience4j-cache结果缓存resilience4j-timelimiter超时处理 注意 在使用Resilience4j的过程中不用引入所有的依赖只引入需要用到的依赖即可。
7 Resilience4j的断路器 断路器CircuitBreaker相对于前面几个熔断机制更复杂 CircuitBreaker通常存在三种状态CLOSE、OPEN、 HALF_OPEN并通过一个时间或数量窗口来记录当前的请求成功率或慢速率从而根据这些指标来作出正确的容错响应。 6种状态 CLOSED: 关闭状态代表正常情况下的状态允许所有请求通过,能通过状态转换为OPENHALF_OPEN: 半开状态即允许一部分请求通过,能通过状态转换为CLOSED和OPENOPEN: 熔断状态即不允许请求通过能通过状态转为为HALF_OPENDISABLED: 禁用状态即允许所有请求通过出现失败率达到给定的阈值也不会熔断不会发生状态转换。METRICS_ONLY: 和DISABLED状态一样也允许所有请求通过不会发生熔断但是会记录失败率等信息不会发生状态转换。FORCED_OPEN: 与DISABLED状态正好相反启用CircuitBreaker但是不允许任何请求通 过不会发生状态转换。 主要介绍3种状态
closed - open : 关闭状态到熔断状态 当失败的调用率比如超时、异常等默认50%达到一 定的阈值服务转为open状态在open状态下所有的请求都被拦截。open- half_open: 当经过一定的时间后CircubitBreaker中默认为60s服务调用者允许一定的请求到达服务提供者。half_open - open: 当half_open状态的调用失败率超过给定的阈值转为open状态half_open - closed: 失败率低于给定的阈值则默认转换为closed状态
8 Resilience4j超时降级
创建模块cloud-consumer-resilience4j-order80 POM引入依赖 dependencies!-- 引入Eureka 客户端依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependency!-- 引入服务调用依赖 OpenFigen --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency!-- actuator监控信息完善 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!-- 引入断路器依赖 resilience4j--dependencygroupIdio.github.resilience4j/groupIdartifactIdresilience4j-spring-cloud2/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-circuitbreaker-resilience4j/artifactId/dependency/dependencies修改YML文件
spring:application:# 设置应用名词name: cloud-resilience4j-order-consumer
server:port: 80eureka:client:# Eureka Server地址service-url:defaultZone: http://localhost:7001/eureka,http://localhost:7002/eurekainstance:instance-id: cloud-resilience4j-order-consumer80resilience4j:# 超时机制timelimiter:instances:delay:# 设置超时时间 2秒timeoutDuration: 2编写服务提供者提供超时方法沿用cloud-provider-payment8001服务提供者
Slf4j
RestController
RequestMapping(/payment)
public class PaymentController {GetMapping(/timeout)public String timeout() {try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}return timeout;}}
编写服务消费者主启动类
Slf4j
SpringBootApplication
EnableFeignClients
public class OrderResilience4jMain80 {public static void main(String[] args) {SpringApplication.run(OrderResilience4jMain80.class, args);log.info(************** OrderResilience4jMain80 服务启动成功 **********);}
}编写服务消费者业务逻辑接口PaymentFeignService
FeignClient(value CLOUD-PAYMENT-PROVIDER)
public interface PaymentFeignService {GetMapping(/payment/timeout)String timeout();
}编写服务消费者Controller
RestController
RequestMapping(/order)
Slf4j
public class OrderController {Autowiredprivate PaymentFeignService paymentFeignService;/*** 测试超时降级** return*/GetMapping(/timeout)/*** name delay* 跟application.yaml中得配置对应(我在配置中取名为delay)* resilience4j:* timelimiter:* instances:* delay:* timeoutDuration: 2*/TimeLimiter(name delay, fallbackMethod timeoutFallback)public CompletableFutureString timeout() {log.info(********* 进入方法 ******);//异步操作CompletableFutureString completableFuture CompletableFuture.supplyAsync(() - (paymentFeignService.timeout()));log.info(********* 离开方法 ******);return completableFuture;}
}
编写服务降级方法 /*** 超时服务降级方法* param e* return*/public CompletableFutureResponseEntity timeoutFallback(Exception e) {e.printStackTrace();return CompletableFuture.completedFuture(ResponseEntity.ok(超时啦));}
测试 9 Resilience4j重试机制 重试机制比较简单当服务端处理客户端请求异常时服务端将会开启重试机制重试期间内服务端将每隔一段时间重试业务逻辑处理。 如果最大重试次数内成功处理业务则停止重试视为处理成功。如果在最大重试次数内处理业务逻辑依然异常则此时系统将拒绝该请求。
修改YML文件
resilience4j:# 重试机制retry:instances:backendA:# 最大重试次数maxRetryAttempts: 3# 固定的重试间隔waitDuration: 10senableExponentialBackoff: trueexponentialBackoffMultiplier: 2编写服务提供者提供测试重试机制方法(沿用cloud-provider-payment8001服务提供者
Slf4j
RestController
RequestMapping(/payment)
public class PaymentController {//测试服务调用GetMapping(/index)public String index() {//模拟服务异常int i 1 / 0;return payment successs;}
}编写服务消费者业务逻辑接口PaymentFeignService
FeignClient(value CLOUD-PAYMENT-PROVIDER)
public interface PaymentFeignService {GetMapping(/payment/index)String index();
}编写服务消费者Controller
RestController
RequestMapping(/order)
Slf4j
public class OrderController {Autowiredprivate PaymentFeignService paymentFeignService;/*** 重试机制* return*/GetMapping(/retry)//对应配置文件中的resilience4j.retry.instances.backendARetry(name backendA)public CompletableFutureString retry() {log.info(********* 进入方法 ******);//异步操作CompletableFutureString completableFuture CompletableFuture.supplyAsync(() - (paymentFeignService.index()));log.info(********* 离开方法 ******);return completableFuture;}
}10 Resilience4j异常比例熔断降级
修改yml文件
resilience4j.circuitbreaker:configs:default:# 熔断器打开的失败阈值failureRateThreshold: 30# 默认滑动窗口大小circuitbreaker使用基于计数和时间范围滑动窗口聚合统计失败率slidingWindowSize: 10# 计算比率的最小值和滑动窗口大小去最小值即当请求发生5次才会计算失败率minimumNumberOfCalls: 5# 滑动窗口类型默认为基于计数的滑动窗口slidingWindowType: TIME_BASED# 半开状态允许的请求数permittedNumberOfCallsInHalfOpenState: 3# 是否自动从打开到半开automaticTransitionFromOpenToHalfOpenEnabled: true# 熔断器从打开到半开需要的时间waitDurationInOpenState: 2srecordExceptions:- java.lang.Exceptioninstances:backendA:baseConfig: default编写服务提供者方法(沿用cloud-provider-payment8001服务提供者
Slf4j
RestController
RequestMapping(/payment)
public class PaymentController {GetMapping(/index)public String index() {return payment successs;}}编写服务消费者业务逻辑接口PaymentFeignService
FeignClient(value CLOUD-PAYMENT-PROVIDER)
public interface PaymentFeignService {GetMapping(/payment/index)String index();
}编写服务消费者Controller
RestController
RequestMapping(/order)
Slf4j
public class OrderController {Autowiredprivate PaymentFeignService paymentFeignService;/*** 异常比例熔断降级* return*/GetMapping(/circuitBreaker)CircuitBreaker(name backendA)public String CircuitBreaker() {log.info(************ 进入方法 ***********);String index paymentFeignService.index();log.info(************ 离开方法 ***********);return index;}
}
使用JMeter进行压力测试
修改语言 创建线程组 创建取样器HTTP请求 添加查看结果树 修改线程数量 修改HTTP请求参数 关闭服务提供者模拟发生异常此时半开状态只有三次请求 编写降级方法 /*** 异常比例熔断降级** return*/GetMapping(/circuitBreaker)CircuitBreaker(name backendA, fallbackMethod circuitBreakerFallback)public String CircuitBreaker() {log.info(************ 进入方法 ***********);String index paymentFeignService.index();log.info(************ 离开方法 ***********);return index;}/*** 服务降级方法** param e* return*/public String circuitBreakerFallback(Throwable e) {e.printStackTrace();return 客官服务繁忙稍等一会。。。。;}测试降级方法
关闭服务提供者服务消费者发起请求
产生服务降级 11 Resilience4j慢调用比例熔断降级
编写yml文件(添加backendB)
resilience4j.circuitbreaker:configs:default:# 熔断器打开的失败阈值failureRateThreshold: 30# 默认滑动窗口大小circuitbreaker使用基于计数和时间范围欢动窗口聚合统计失败率slidingWindowSize: 10# 计算比率的最小值和滑动窗口大小去最小值即当请求发生5次才会计算失败率minimumNumberOfCalls: 5# 滑动窗口类型默认为基于计数的滑动窗口slidingWindowType: TIME_BASED# 半开状态允许的请求数permittedNumberOfCallsInHalfOpenState: 3# 是否自动从打开到半开automaticTransitionFromOpenToHalfOpenEnabled: true# 熔断器从打开到半开需要的时间waitDurationInOpenState: 2srecordExceptions:- java.lang.Exceptioninstances:backendA:baseConfig: defaultbackendB:# 熔断器打开的失败阈值failureRateThreshold: 50# 慢调用时间阈值 高于这个阈值的slowCallDurationThreshold: 2s# 慢调用百分比阈值断路器慢调用时间大于slowslowCallRateThreshold: 30slidingWindowSize: 10slidingWindowType: TIME_BASEDminimumNumberOfCalls: 2编写服务消费者Controller /*** 慢调用比例熔断降级** return*/GetMapping(/slowCircuitBreaker)CircuitBreaker(name backendB, fallbackMethod slowCircuitBreakerFallback)public String slowCircuitBreaker() {log.info(************ 进入方法 ***********);try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}String index paymentFeignService.index();log.info(************ 离开方法 ***********);return index;}public String slowCircuitBreakerFallback(Throwable e) {e.printStackTrace();return 太慢了。。。。;}12 Resilience4j信号量隔离实现
POM引入依赖 dependencygroupIdio.github.resilience4j/groupIdartifactIdresilience4j-bulkhead/artifactIdversion1.7.0/version/dependency信号量隔离修改YML文件
resilience4j:#信号量隔离bulkhead:instances:backendA:# 隔离允许并发线程执行的最大数量maxConcurrentCalls: 5# 当达到并发调用数量时新的线程的阻塞时间maxWaitDuration: 20ms编写controller /*** 测试信号量隔离** return*/Bulkhead(name backendA, type Bulkhead.Type.SEMAPHORE)GetMapping(/bulkhead)public String bulkhead() throws InterruptedException {log.info(************** 进入方法 *******);TimeUnit.SECONDS.sleep(10);String index paymentFeignService.index();log.info(************** 离开方法 *******);return index;}测试
配置隔离并发线程最大数量为5 13 Resilience4j线程池隔离实现
线程池隔离配置修改YML文件
resilience4j:
#线程池隔离thread-pool-bulkhead: instances:backendA:# 最大线程池大小maxThreadPoolSize: 4# 核心线程池大小coreThreadPoolSize: 2# 队列容量queueCapacity: 2编写controller /*** 测试线程池服务隔离** return*/Bulkhead(name backendA, type Bulkhead.Type.THREADPOOL)GetMapping(/thread)public CompletableFuture threadPoolBulkhead() {log.info(********** 进入方法 *******);try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}log.info(********** 离开方法 *******);return CompletableFuture.supplyAsync(() - 线程池隔离信息......);}测试
配置文件设置核心线程2个最大4个服务会一次处理4个请求 14 Resilience4j限流
限流YML配置
resilience4j:# 限流ratelimiter:instances:backendA:# 限流周期时长。 默认500纳秒limitRefreshPeriod: 5s# 周期内允许通过的请求数量。 默认50limitForPeriod: 2编写Controller /*** 限流** return*/GetMapping(/limiter)RateLimiter(name backendA)public CompletableFutureStringRateLimiter() {log.info(********* 进入方法 ******);//异步操作CompletableFutureString completableFuture CompletableFuture.supplyAsync(() - (paymentFeignService.index()));log.info(********* 离开方法 ******);return completableFuture;}JMeter压测
线程数 - 5 五 服务网关Gateway
1 微服务中的应用 没有服务网关 问题 1 地址太多 2 安全性 3 管理问题 为什么要使用服务网关
网关是微服务架构中不可或缺的部分。使用网关后客户端和微服务之间的网络结构如下。 注意 网关统一向外部系统如访问者、服务提供REST API。在 SpringCloud 中使用Zuul、Spring Cloud Gateway等作为API Gateway来实现动态路由、监控、回退、安全等功能。 认识Spring Cloud Gateway
Spring Cloud Gateway 是 Spring Cloud生态系统中的网关它是 基于Spring 5.0、SpringBoot 2.0和Project Reactor等技术开发的 旨在为微服务架构提供一种简单有效的、统一的API路由管理方式 并为微服务架构提供安全、监控、指标和弹性等功能。其目标是替代Zuul。 注意 Spring Cloud Gateway 用Netty Webflux实现不要加入 Web依赖否则会报错它需要加入Webflux依赖。 什么是WebFlux
Webflux模式替换了旧的Servlet线程模型。用少量的线程处理 request和response io操作这些线程称为Loop线程而业务交给响应式编程框架处理响应式编程是非常灵活的用户可以将业务中阻塞的操作提交到响应式框架的work线程中执行而不阻塞的操作依然可以在Loop线程中进行处理大大提高了Loop线程的利用率。 注意 Webflux虽然可以兼容多个底层的通信框架但是一般情况下 底层使用的还是Netty毕竟Netty是目前业界认可的最高性能的通信框架。而Webflux的Loop线程正好就是著名的Reactor模式IO处理模型的Reactor线程如果使用的是高性能的通信框架Netty。 温馨提示 什么是NettyNetty 是一个基于NIO的客户、服务器端的编程框架。提供异步的、事件驱动的网络应用程序框架和工具用以快速开发高性能、高可靠性的网络服务器和客户端程序。 Spring Cloud Gateway特点
易于编写谓词( Predicates )和过滤器 Filters ) 。其Predicates和Filters可作用于特定路由。支持路径重写。支持动态路由。集成了Spring Cloud DiscoveryClient。
2 三大核心概念 路由(Route)
这是网关的基本构建块。它由一个ID一个目标URI一组断言和一组过滤器定义。如果断言为真则路由匹配。
断言(predicate)
输入类型是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容例如headers或参数。
过滤器(filter)
可以在请求被路由前或者之后对请求进行修改。 举个例子 你想去动物园游玩那么你买了一张熊猫馆的门票只能进入熊猫馆的区域而不能去犀牛管瞎转。因为没有犀牛馆的门票进不去就算走到门口机器也不能识别。这里门票就相当于请求URL熊猫馆相当于路由而门口识别门卡的机器就是断言。然后我进入熊猫馆里面看到工作人员我想很有可能是过滤器结果还真是他在我进入馆之前拿手持设备对我全身扫描看看有没有危险品(请求前改代码)并且在我出熊猫馆之后要求再次检察看看我是否携带熊猫出馆(请求后改动代码)。这个工作人员相当于过滤器。 总结
首先任何请求进来网关都会把它们拦住。根据请求的URL把它们分配到不同的路由上路由上面会有断言来判断请求能不能进来。进来之后会有一系列的过滤器对请求被转发前或转发后进行改动。 具体怎么个改动法那就根据业务不同而自定义了。一般就是监控限流日志输出等等。
3 入门案例
创建cloud-gateway-gateway9527工程 pom文件引入依赖 dependencies!-- 引入网关Gateway依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-gateway/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency/dependencies新增application.yaml
server:port: 9527spring:cloud:gateway:# 路由配置routes:# 路由ID没有固定规则但要求唯一建议配合服务名- id: cloud-payment-provider# 目标服务地址uri: http://localhost:8001# 路由条件 断言会接收一个输入的参数会返回一个布尔结果值predicates:# 路径相匹配的进行路由 # /**表示匹配所有子级请求- Path/payment/**主启动类
Slf4j
SpringBootApplication
public class GatewayMain {public static void main(String[] args) {SpringApplication.run(GatewayMain.class, args);log.info(********** GatewayMain 服务启动成功 *********);}
}测试
启动注册中心 7001,7002
启动服务提供者8001
启动网关服务9527
请求http://localhost:9527/payment/index 4 Java API构建路由 代码注入RouteLocator
Configuration
public class GatewayConfig {Beanpublic RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {//获取路由RouteLocatorBuilder.Builder routes routeLocatorBuilder.routes();//设置路由routes.route(cloud-payment-provider,r - r.path(/payment/**).uri(http://localhost:8001)).build();return routes.build();}}测试
5 路由规则 Gateway 的路由规则主要有三个部分分别是路由、断言(谓词)和过滤器。
路由
路由是 Gateway 的⼀个基本单元。
断言
也称谓词实际上是路由的判断规则。一个路由中可以添加多个谓词的组合。 提示 打个比方你可以为某个路由设置⼀条谓词规则约定访问路径的匹配规则为Path/bingo/*在这种情况下只有以 /bingo 打头的请求才会被当前路由选中。 过滤器 Gateway 组件使用了⼀种 FilterChain的模式对请求进行处理每一个服务请求Request在发送到目标标服务之前都要被一串FilterChain处理。同理在 Gateway接收服务响应Response 的过程中也会被FilterChain 处理⼀把。
6 动态路由 默认情况下Gateway会根据注册中心的服务列表以注册中心上微服务名为路径创建动态路由进行转发从而实现动态路由的功能。
添加eureka依赖 !-- 引入Eureka client依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencyyml配置
server:port: 9527spring:cloud:gateway:# 路由配置routes:# 路由ID没有固定规则但要求唯一建议配合服务名- id: cloud-payment-provider# 目标服务地址 lb后边是微服务名字uri: lb://CLOUD-PAYMENT-PROVIDER# 路由条件 断言会接收一个输入的参数会返回一个布尔结果值predicates:# 路径相匹配的进行路由- Path/payment/**application:# 设置应用名词name: cloud-gatewayeureka:client:service-url:defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/instance:#实例名称instance-id: cloud-gateway9527**注意 ** 需要注意的是uri的协议lb表示启用Gateway的负载均衡的功能。 服务提供者payemt8001和payment8002工程controller添加方法
Slf4j
RestController
RequestMapping(/payment)
public class PaymentController {Value(${server.port})private String port;GetMapping(/lb)public String lb() {return port;}}测试
启动eureka服务注册发现 7001,7002
启动服务提供者payment8001,8002
启动网关服务测试 7 断言功能详解 一个请求在抵达网关层后首先就要进行断言匹配在满足所有断言之后才会进入Filter阶段。说白了Predicate就是一种路由规则 通过Gateway中丰富的内置断言的组合我们就能让一个请求找到对应的Route来处理。
After路由断言 Factory
After Route Predicate Factory采用一个参数——日期时间。在该日期时间之后发生的请求都将被匹配。
YML文件添加配置
predicates:# 路径相匹配的进行路由- Path/payment/**- After2030-02-15T14:54:23.31708:00[Asia/Shanghai]注意 UTC时间格式的时间参数。 UTC时间格式的时间参数时间生成方法 public static void main(String[] args) {ZonedDateTime now ZonedDateTime.now();System.out.println(now);
}Before路由断言 Factory
Before Route Predicate Factory采用一个参数——日期时间。在该日期时间之前发生的请求都将被匹配。
YML文件添加配置
predicates:- Before2030-02-15T14:54:23.31708:00[Asia/Shanghai]Between 路由断言 Factory
Between 路由断言 Factory有两个参数datetime1和datetime2。 在datetime1和datetime2之间的请求将被匹配。datetime2参数的实际时间必须在datetime1之后。
YML文件添加配置
predicates:- Between2020-02-15T14:54:23.31708:00[Asia/Shanghai],2030-02-15T14:54:23.31708:00[Asia/Shanghai]Cookie路由断言 Factory
顾名思义Cookie验证的是Cookie中保存的信息Cookie断言和上 面介绍的两种断言使用方式大同小异唯一的不同是它必须连同属性值一同验证不能单独只验证属性是否存在。
YML文件添加配置
predicates:# 路径相匹配的进行路由- Cookieusername,lxx使用postman测试 Header路由断言 Factory
这个断言会检查Header中是否包含了响应的属性通常可以用来验证请求是否携带了访问令牌。
YML文件添加配置
# 请求头要有X-Request-Id属性并且值为整数的正则表达式
predicates:- HeaderX-Request-Id,\d测试 Host路由断言 Factory
Host 路由断言 Factory包括一个参数host name列表。使用Ant 路径匹配规则 . 作为分隔符。访问的主机匹配http或者https, baidu.com 默认80端口, 就可以通过路由。 多个 , 号隔开。
YML文件添加配置
predicates:- Hostlxx.comHost文件新增配置
127.0.0.1 lxx.comMethod路由断言 Factory
这个断言是专门验证HTTP Method的在下面的例子中我们把 Method断言和Path断言通过一个and连接符合并起来共同作用于路由判断当我们访问“/gateway/sample”并且HTTP Method是 GET的时候将适配下面的路由。
YML文件添加配置
predicates:# 路径相匹配的进行路由- Path/payment/**- MethodGET Query路由断言 Factory
请求断言也是在业务中经常使用的它会从ServerHttpRequest中 的Parameters列表中查询指定的属性有如下两种不同的使用方式。
YML文件添加配置
predicates:# 路径相匹配的进行路由- Path/payment/**# 要有参数名称并且是正整数才能路由- Queryusername,\d 测试 8 过滤器详解 微服务系统中的服务非常多。如果每个服务都自己做鉴权、限流、 日志输出则非常不科学。所以可以通过网关的过滤器来处理这些工作。在用户访问各个服务前应在网关层统一做好鉴权、限流等工作。
Filter的生命周期
根据生命周期可以将Spring Cloud Gateway中的Filter分 为PRE和POST两种。
PRE代表在请求被路由之前执行该过滤器此种过滤器可用来实现参数校验、权限校验、流量监控、日志输出、协议转换等功能。POST代表在请求被路由到微服务之后执行该过滤器。此种过滤器可用来实现响应头的修改如添加标准的HTTP Header )、收集统计信息和指标、将响应发送给客户端、输出日志、流量监控等功能。
Filter分类
根据作用范围Filter可以分为以下两种。
GatewayFilter网关过滤器此种过滤器只应用在单个路由或者一个分组的路由上。GlobalFilter全局过滤器此种过滤器会应用在所有的路由上。
9 网关过滤器GatewayFilter 网关过滤器( GatewayFilter )允许以某种方式修改传入的HTTP请求, 或输出的HTTP响应。网关过滤器作用于特定路由。Spring Cloud Gateway内置了许多网关过滤器工厂来编写网关过滤器。
过滤器工厂作用参数AddRequestHeader为原始请求添加HeaderHeader的名称及值AddRequestParameter为原始请求添加请求参数参数名称及值AddResponseHeader为原始响应添加HeaderHeader的名称及值DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名称FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称PrefixPath为原始请求路径添加前缀前缀路径PreserveHostHeader为请求添加一个preserveHostHeadertrue的属性路由过滤器 会检查该属性以决定是否要发送原始的Host无RequestRateLimiter用于对请求限流限流算法为令牌桶keyResolver、rateLimiter、statusCode、 denyEmptyKey、emptyKeyStatusRedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的urlRemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用可以通过配置指定仅删除哪些 HeaderRemoveRequestHeader为原始请求删除某个HeaderHeader名称RemoveResponseHeader为原始响应删除某个HeaderHeader名称RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式RewriteResponseHeader重写原始响应中的某个HeaderHeader名称值的正则表达式重写后的值SaveSession在转发请求之前强制执行WebSession::save操作无secureHeaders为原始响应添加一系列起安全作用的响应头无支持修改这些安全响应头的值SetPath修改原始的请求路径修改后的路径SetResponseHeader修改原始响应中某个Header的值Header名称修改后的值SetStatus修改原始响应的状态码HTTP 状态码可以是数字也可以是字符串StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量Retry针对不同的响应进行重试retries、statuses、methods、seriesRequestSize设置允许接收最大请求包的大小。如果请求包大小超过设置的值则返回 413 Payload Too Large请求包大小单位为字节默认值为5MModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容ModifyResponseBody修改原始响应体的内容修改后的响应体内容Default为所有路由添加过滤器过滤器工厂名称及值
常用内置过滤器的使用
#过滤器请求在传递过程中可以通过过滤器对其进行一定的修改
filters:- SetStatus250 # 修改原始响应的状态码启动测试 10 自定义网关过滤器 需求通过过滤器配置是否在控制台输出日志信息以及是否记录日志。 实现步骤 1、类名必须叫做XxxGatewayFilterFactory注入到Spring容器后使用时的名称就叫做Xxx。 2、创建一个静态内部类Config 里面的属性为配置文件中配置的参数 - 过滤器名称参数 1,参数2… 2、类必须继承 AbstractGatewayFilterFactory让父类帮实现配置参数的处理。 3、重写shortcutFieldOrder()方法返回List参数列表为Config中属性集合。return Arrays.asList(“参数1”,参数2…) 4、无参构造方法中super(Config.class) 5、编写过滤逻辑 public GatewayFilter apply(Config config) 在配置文件中添加一个Log的过滤器配置
# 过滤器请求在传递过程中可以通过过滤器对其进行一定的修改
filters: # 控制日志是否开启- Logtrue自定义一个过滤器工厂实现里面的方法
/*** 自定义局部过滤器*/
Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactoryLogGatewayFilterFactory.Config {public LogGatewayFilterFactory() {super(Config.class);}Overridepublic ListString shortcutFieldOrder() {return Arrays.asList(consoleLog);}Overridepublic GatewayFilter apply(Config config) {return ((exchange, chain) - {if (config.consoleLog) {System.out.println(console 日志已开启...);}return chain.filter(exchange);});}Datapublic static class Config {private boolean consoleLog;}
}运行测试
请求http://localhost:9527/payment/lb 11 过滤器之全局过滤器 全局过滤器作用于所有路由无需配置。通过全局过滤器可以实现对权限的统一校验安全性验证等功能。
内置的全局过滤器
SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理的。
路由过滤器Forward路由过滤器LoadBalancerClientNetty路由过滤器 Netty写响应过滤器Netty Write Response FRouteToRequestUrl 过滤器路由过滤器 (Websocket Routing Filter)网关指标过滤器Gateway Metrics Filter)组合式全局过滤器和网关过滤器排序Combined Global Filter and GatewayFilter Ordering路由Marking An Exchange As Routed
自定义全局过滤器 开发中的鉴权逻辑
当客户端第一次请求服务时服务端对用户进行信息认证登录 认证通过将用户信息进行加密形成token返回给客户端作为登录凭证以后每次请求客户端都携带认证的token服务端对token进行解密判断是否有效。 对于验证用户是否已经登录及鉴权的过程可以在网关统一校验。 下面我们通过自定义一个GlobalFIlter去校验所有请求的请求参数 中是否包含“token”如果不包含请求参数“token”则不转发路由 否则执行正常逻辑。
Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {String token exchange.getRequest().getQueryParams().getFirst(token);if (StringUtils.isEmpty(token)) {System.out.println(鉴权失败。确少token参数。);exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}if (!jack.equals(token)) {System.out.println(token无效...);exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}// 继续执行filter链return chain.filter(exchange);}/*** 顺序数值越小优先级越高** return*/Overridepublic int getOrder() {return 0;}
}
测试 12 网关的cors跨域配置 为什么会出现跨域问题
出于浏览器的同源策略限制。同源策略是一种约定它是浏览器最核心也最基本的安全功能如果缺少了同源策略则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的浏览器只是针对同源策略的一种实现。
什么是跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url 不同即为跨域
跨域问题演示
编写index页面
!DOCTYPE html
html langen
headmeta charsetUTF-8titleTitle/title
/head
body
/body
scriptsrchttp://libs.baidu.com/jquery/2.0.0/jquery.min.js/script
script$.get(http://localhost:9527/payment/lb?token jack, function (data, status) {alert(Data: data \nStatus: status);});
/script
/html用idea内置服务器打开该页面
问题出现 Gateway解决如何允许跨域 CORS
如何允许跨域一种解决方法就是目的域告诉请求者允许什么来源域来请求那么浏览器就会知道 B域是否允许A域发起请求。CORS“跨域资源共享”(Cross-origin resource sharing)就是这样一种解决手段。
CORS使得浏览器在向目的域发起请求之前先发起一个OPTIONS方式的请求到目的域获取目的域的信息比如获取目的域允许什么域来请求的信息。
spring:cloud:gateway:globalcors:cors-configurations:[/**]:allowCredentials: trueallowedOriginPatterns: *allowedMethods: *allowedHeaders: *add-to-simple-url-handler-mapping: true六 服务网关Gateway实现用户鉴权
1 什么是JWT 什么是JWT
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录 SSO场景。
什么是SSO单点登录
SSOSingle Sign On单点登录。SSO是在多个应用系统中用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
当用户第一次访问应用系统1的时候因为还没有登录会被引导到认证系统中进行登录根据用户提供的登录信息认证系统进行身份校验如果通过校验应该返回给用户一个认证的凭据ticket用户再访问别的应用的时候就会将这个ticket带上作为自己认证的凭据应用系统接受到请求之后会把ticket送到认证系统进行校验检查ticket的合法性。如果通过校验用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。
传统的session认证
每次提到无状态的 JWT 时相信都会看到另一种基于 Session 的用户认证方案介绍这里也不例外Session 的认证流程通常会像这样 缺点 安全性CSRF攻击因为基于cookie来进行用户识别, cookie如果被截获用户就会很容易受 到跨站请求伪造的攻击。 扩展性对于分布式应用需要实现 session 数据共享 性能每一个用户经过后端应用认证之后后端应用都要在服务端做一次记录以方便用户 下次请求的鉴别通常而言session都是保存在内存中而随着认证用户的增多服务端的开 销会明显增大与REST风格不匹配。因为它在一个无状态协议里注入了状态。 JWT方式 优点 无状态适合移动端应用单点登录友好 2 JWT原理 JWT 的原理是服务器认证以后生成一个 JSON 对象发回给用户就像下面这样。
{姓名: 张三,角色: 管理员,到期时间: 2030年7月1日0点0分
}注意 用户与服务端通信的时候都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据服务器在生成这个对象的时候会加上签名服务器就不保存任何 session 数据了也就是说服务器变成无状态了从而比较容易实现扩展。 JWT的结构 注意 它是一个很长的字符串中间用点 . 分隔成三个部分。注 意JWT 内部是没有换行的这里只是为了便于展示将它写成了几行。 JWT 的三个部分依次如下:
头部header)载荷payload)签证signature)
Header
JSON对象描述 JWT 的元数据。其中 alg 属性表示签名的算法 algorithm默认是 HMAC SHA256写成 HS256typ属性表示这个令牌token的类型type统一写为 JWT。
{alg: HS256,typ: JWT
}注意 上面代码中 alg 属性表示签名的算法algorithm默认是 HMAC SHA256写成 HS256 typ 属性表示这个令牌 token的类型typeJWT 令牌统一写为 JWT 然后将头部进行Base64编码构成了第一部分Base64是一种用64个字符来表示任意二进制数据的方法Base64是一种任意二进制到文本字符串的编码方法常用于在URL、Cookie、网页中传输少量二进制数据。 Payload
内容又可以分为3种标准
标准中注册的声明公共的声明私有的声明
payload-标准中注册的声明 (建议但不强制使用)
iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间这个过期时间必须要大于签发时间nbf: 定义在什么时间之前该jwt都是不可用的.iat: jwt的签发时间jti: jwt的唯一身份标识主要用来作为一次性token,从而回避重放攻击。
payload-公共的声明
公共的声明可以添加任何的信息。一般这里我们会存放一下用户的基本信息非敏感信息。
payload-私有的声明
私有声明是提供者和消费者所共同定义的声明。需要注意的是不要存放敏感信息不要存放敏感信息不要存放敏感信息 因为这里也是base64编码任何人获取到jwt之后都可以解码 {sub: 1234567890,name: John Doe,iat: 1516239022
}注意 sub和iat是标准声明分别代表所面向的用户和jwt签发时间。 sub这个是发给一个账号是1234567890的用户也许是IDname名字叫John Doeiat签发时间是15162390222030/1/18 9:30:22 Signature
这部分就是 JWT 防篡改的精髓其值是对前两部分 base64UrlEncode 后使用指定算法签名生成以默认 HS256 为例指定一个密钥secret就会按照如下公式生成
HMACSHA256(base64UrlEncode(header) . base64UrlEncode(payload), secret,
)注意 算出签名以后把 Header、Payload、Signature 三个部分拼成一个字符串每个部分之间用点 . 分隔就可以返回给用户。 JWT 的使用方式 流程 客户端收到服务器返回的 JWT可以储存在 Cookie 里面也可以储存在 localStorage。此后客户端每次与服务器通信都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送但是这样不能跨域所以更好的做法是放在 HTTP 请求的头信息 Authorization 字段里面。 3 用户微服务 创建cloud-auth-user6500工程 引入POM依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- eureka client 依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency创建主启动类
EnableEurekaClient
SpringBootApplication
Slf4j
public class AuthUserMain6500 {public static void main(String[] args) {SpringApplication.run(AuthUserMain6500.class, args);log.info(********* 认证用户启动成功 ******);}
}4 JWT工具类
引入JWT依赖 dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.79/version/dependencydependencygroupIdcom.auth0/groupIdartifactIdjava-jwt/artifactIdversion3.7.0/version/dependency创建JWT工具类JWTUtils
/*** JWT工具类*/
public class JWTUtils {// 签发人private static final String ISSUSER lxx;// 过期时间 5分钟private static final long TOKEN_EXPIRE_TIME 5 * 60 * 1000;// 秘钥private static final String KEY wer21w3e2r904923;/*** 生成令牌** return*/public static String token() {Date now new Date();// key 用来加密数据签名秘钥Algorithm algorithm Algorithm.HMAC256(KEY);// 1. 创建JWTString token JWT.create()//签发人.withIssuer(ISSUSER)// 签发时间.withIssuedAt(now)// 过期时间.withExpiresAt(new Date(now.getTime() TOKEN_EXPIRE_TIME)).sign(algorithm);return token;}/*** 验证令牌** return*/public static boolean verify(String token) {try {Algorithm algorithm Algorithm.HMAC256(KEY);// 1. 验证令牌JWTVerifier verifier JWT.require(algorithm)// 签发人.withIssuer(ISSUSER).build();// 如果校验有问题会抛出异常verifier.verify(token);return true;} catch (IllegalArgumentException e) {e.printStackTrace();} catch (JWTVerificationException e) {e.printStackTrace();}return false;}public static void main(String[] args) {//1. 生成令牌
// String token token();
// System.out.println(token);//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpdGJhaXpoYW4iLCJleHAiOjE2NDU5NDQzMjEsImlhdCI6MTY0NTk0NDAyMX0.BLHAKsHsVW4NUo7K_yZgaIq-64eI4R7_ewCl4svGntg// 令牌String token eyJ0eXAiOiJKVCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpdCJleHAiOjE2NDU5NDQzMjEsImlhdCI6MTY0NTk0NDAyMX0.BLHAKsHsVW4NUo7K_yZgaIq-64eI4R7_ewCl4svGntg;boolean verify verify(token);System.out.println(verify);}}5 用户服务实现JWT鉴权
编写统一返回实体类
/*** 统一返回实体类*/
AllArgsConstructor
NoArgsConstructor
Data
Builder
public class Result {// 返回状态码private int code;// 返回描述信息private String msg;// 返回token信息 令牌private String token;}编写LoginController类,编写用户登录接口
RequestMapping(/user)
RestController
Slf4j
public class LoginController {/*** 登录** param username 用户名* param password 密码*/PostMapping(/login)public Result login(String username, String password) {// 1. 验证用户名和密码// TODO 模拟数据库操作if (admin.equals(username) 123456.equals(password)) {// 2. 生成令牌String token JWTUtils.token();return Result.builder().code(200).msg(succes).token(token).build();} else {return Result.builder().code(500).msg(用户名或密码不正确).build();}}}YML文件编写
spring:application:# 设置应用名词name: cloud-auth-userserver:port: 6500eureka:client:service-url:# 单机 Eureka Server 地址defaultZone: http://localhost:7001/eureka/instance:#实例名称(根据需要自己起名字)instance-id: cloud-auth-user6500
6 网关全局过滤器加入JWT 鉴权 在cloud-gateway-gateway9527工程中操作
配置跳过验证路由
org:my:jwt:# 跳过认证路由skipAuthUrls:- /user/login创建UserAuthGlobalFilter全局过滤器
Data
ConfigurationProperties(org.my.jwt)
Component
Slf4j
public class UserAuthGlobalFilter implements GlobalFilter, Ordered {private String[] skipAuthUrls;/*** 过滤器逻辑** param exchange* param chain* return*/Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {// user/login// 获取请求url地址String path exchange.getRequest().getURI().getPath();// 跳过不需要验证的路径if (null ! skipAuthUrls isSkip(path)) {return chain.filter(exchange);}// 1. 从请求头中获取tokenString token exchange.getRequest().getHeaders().getFirst(token);// 2. 判断tokenif (StringUtils.isEmpty(token)) {// 3. 设置响应ServerHttpResponse response exchange.getResponse();// 4. 设置响应状态码response.setStatusCode(HttpStatus.OK);// 5. 设置响应头response.getHeaders().add(Content-Type, application/json;charsetUTF-8);// 6. 创建响应对象Response res new Response(200, token 参数缺失);// 7. 对象转字符串byte[] bytes JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8);// 8. 数据流返回数据DataBuffer wrap response.bufferFactory().wrap(bytes);return response.writeWith(Flux.just(wrap));}// 验证tokenboolean verify JWTUtils.verify(token);if (!verify) {// 3. 设置响应ServerHttpResponse response exchange.getResponse();// 4. 设置响应状态码response.setStatusCode(HttpStatus.OK);// 5. 设置响应头response.getHeaders().add(Content-Type, application/json;charsetUTF-8);// 6. 创建响应对象Response res new Response(200, token 失效);// 7. 对象转字符串byte[] bytes JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8);// 8. 数据流返回数据DataBuffer wrap response.bufferFactory().wrap(bytes);return response.writeWith(Flux.just(wrap));}// 如果各种判断都通过。。return chain.filter(exchange);}Overridepublic int getOrder() {return 0;}/*** 判断当前访问的url是否开头URI是在配置的忽略url列表中** param url* return*/private boolean isSkip(String url) {for (String skipurl : skipAuthUrls) {if (url.startsWith(skipurl)) {return true;}}return false;}}
测试 文章转载自: http://www.morning.lnrhk.cn.gov.cn.lnrhk.cn http://www.morning.vvdifactory.com.gov.cn.vvdifactory.com http://www.morning.dbfj.cn.gov.cn.dbfj.cn http://www.morning.fykqh.cn.gov.cn.fykqh.cn http://www.morning.wbqt.cn.gov.cn.wbqt.cn http://www.morning.symgk.cn.gov.cn.symgk.cn http://www.morning.cklld.cn.gov.cn.cklld.cn http://www.morning.fnfhs.cn.gov.cn.fnfhs.cn http://www.morning.qsy39.cn.gov.cn.qsy39.cn http://www.morning.fbdkb.cn.gov.cn.fbdkb.cn http://www.morning.hlnys.cn.gov.cn.hlnys.cn http://www.morning.fsnhz.cn.gov.cn.fsnhz.cn http://www.morning.fbmjw.cn.gov.cn.fbmjw.cn http://www.morning.mdmxf.cn.gov.cn.mdmxf.cn http://www.morning.tkztx.cn.gov.cn.tkztx.cn http://www.morning.bmrqz.cn.gov.cn.bmrqz.cn http://www.morning.monstercide.com.gov.cn.monstercide.com http://www.morning.jfcbz.cn.gov.cn.jfcbz.cn http://www.morning.nflpk.cn.gov.cn.nflpk.cn http://www.morning.wlnr.cn.gov.cn.wlnr.cn http://www.morning.dqpd.cn.gov.cn.dqpd.cn http://www.morning.lslin.com.gov.cn.lslin.com http://www.morning.qkgwx.cn.gov.cn.qkgwx.cn http://www.morning.langlaitech.cn.gov.cn.langlaitech.cn http://www.morning.xnqjs.cn.gov.cn.xnqjs.cn http://www.morning.pxwjp.cn.gov.cn.pxwjp.cn http://www.morning.ybgt.cn.gov.cn.ybgt.cn http://www.morning.fqqcd.cn.gov.cn.fqqcd.cn http://www.morning.dgng.cn.gov.cn.dgng.cn http://www.morning.gnyhc.cn.gov.cn.gnyhc.cn http://www.morning.qghjc.cn.gov.cn.qghjc.cn http://www.morning.gjlst.cn.gov.cn.gjlst.cn http://www.morning.mpxbl.cn.gov.cn.mpxbl.cn http://www.morning.qgjxy.cn.gov.cn.qgjxy.cn http://www.morning.gqfjb.cn.gov.cn.gqfjb.cn http://www.morning.rmyt.cn.gov.cn.rmyt.cn http://www.morning.fflnw.cn.gov.cn.fflnw.cn http://www.morning.zcwwb.cn.gov.cn.zcwwb.cn http://www.morning.xfwnk.cn.gov.cn.xfwnk.cn http://www.morning.rchsr.cn.gov.cn.rchsr.cn http://www.morning.ghxzd.cn.gov.cn.ghxzd.cn http://www.morning.fgsct.cn.gov.cn.fgsct.cn http://www.morning.mhcys.cn.gov.cn.mhcys.cn http://www.morning.tgfjm.cn.gov.cn.tgfjm.cn http://www.morning.wnmdt.cn.gov.cn.wnmdt.cn http://www.morning.kmcby.cn.gov.cn.kmcby.cn http://www.morning.xrpjr.cn.gov.cn.xrpjr.cn http://www.morning.ctlbf.cn.gov.cn.ctlbf.cn http://www.morning.cwskn.cn.gov.cn.cwskn.cn http://www.morning.xxgfl.cn.gov.cn.xxgfl.cn http://www.morning.rhnn.cn.gov.cn.rhnn.cn http://www.morning.ljzqb.cn.gov.cn.ljzqb.cn http://www.morning.qsbcg.cn.gov.cn.qsbcg.cn http://www.morning.txgjx.cn.gov.cn.txgjx.cn http://www.morning.rbkdg.cn.gov.cn.rbkdg.cn http://www.morning.qsmmq.cn.gov.cn.qsmmq.cn http://www.morning.zyndj.cn.gov.cn.zyndj.cn http://www.morning.qbccg.cn.gov.cn.qbccg.cn http://www.morning.wkws.cn.gov.cn.wkws.cn http://www.morning.tqwcm.cn.gov.cn.tqwcm.cn http://www.morning.pgcmz.cn.gov.cn.pgcmz.cn http://www.morning.ktblf.cn.gov.cn.ktblf.cn http://www.morning.wyrkp.cn.gov.cn.wyrkp.cn http://www.morning.pqnps.cn.gov.cn.pqnps.cn http://www.morning.ykmkz.cn.gov.cn.ykmkz.cn http://www.morning.xmpbh.cn.gov.cn.xmpbh.cn http://www.morning.zhghd.cn.gov.cn.zhghd.cn http://www.morning.wqrk.cn.gov.cn.wqrk.cn http://www.morning.rkck.cn.gov.cn.rkck.cn http://www.morning.gjwkl.cn.gov.cn.gjwkl.cn http://www.morning.yskhj.cn.gov.cn.yskhj.cn http://www.morning.ghslr.cn.gov.cn.ghslr.cn http://www.morning.mkkcr.cn.gov.cn.mkkcr.cn http://www.morning.wcgfy.cn.gov.cn.wcgfy.cn http://www.morning.yltyz.cn.gov.cn.yltyz.cn http://www.morning.rcdmp.cn.gov.cn.rcdmp.cn http://www.morning.psdbf.cn.gov.cn.psdbf.cn http://www.morning.dskzr.cn.gov.cn.dskzr.cn http://www.morning.qpsxz.cn.gov.cn.qpsxz.cn http://www.morning.jpjpb.cn.gov.cn.jpjpb.cn