SpringCloud相关总结

  • 认识微服务
    • 微服务框架技术体系
    • 技术栈
    • 服务架构演变
      • 单体架构
      • 分布式架构
      • 微服务
    • 微服务技术对比
  • Spring Cloud
    • 什么是Spring Cloud
    • Spring Cloud与SpringBoot的版本兼容关系
    • Spring Cloud项目构建
    • 微服务远程调用RestTemplate
  • 服务注册与发现
    • Eureka注册中心
      • 远程调用的问题
      • Eureka注册中心的作用
      • Eureka实例搭建
      • Eureka自我保护机制
    • Ribbon负载均衡
      • 负载均衡原理
      • 负载均衡策略
      • 负载均衡策略定义
      • 饥饿加载
    • Nacos
      • Nacos服务注册中心
        • Nacos的安装
        • 服务注册到Nacos
        • Nacos服务分级存储
        • Nacos根据权重负载均衡
        • Nacos环境隔离 - namespace
        • Nacos和Eureka的区别
      • Nacos服务配置中心
        • Nacos配置管理
        • 配置热更新
        • 多环境配置共享
        • 配置集
      • nacos集群搭建
  • 服务调用feign
    • Feign替代RestTemplate
    • Feign自定义配置
    • Feign使用优化
    • 最佳实践
  • 服务网关Gateway
    • 为什么需要网关
    • Gateway快速搭建
    • 路由断言工厂Route Predicate Factory
    • 网关过滤器
      • 过滤器工厂GatewayFilter
      • 过滤器的定义
        • 当前路由过滤器
        • 默认过滤器
        • 全局过滤器
      • 过滤器执行顺序
      • 跨域问题处理
    • 限流过滤器(转)
      • 计算器算法
      • 漏桶算法
      • 令牌桶算法

认识微服务

微服务框架技术体系

技术栈

服务架构演变

单体架构

单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:架构简单,部署成本低
缺点:耦合度高

分布式架构

分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。
优点:降低服务耦合,有利于服务升级拓展

分布式架构要考虑的问题。
服务拆分粒度如何?
服务集群地址如何维护?
服务之间如何实现远程调用?
服务健康状态如何感知?

微服务

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

总结
单体架构特点?
简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
分布式架构特点?
松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
微服务:一种良好的分布式架构方案
优点:拆分粒度更小、服务更独立、耦合度更低
缺点:架构非常复杂,运维、监控、部署难度提高

微服务技术对比

微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo。

企业需求

Spring Cloud

什么是Spring Cloud

SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:

Spring Cloud与SpringBoot的版本兼容关系

Spring Cloud项目构建

POM工程的pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.zcc</groupId><artifactId>springcloud</artifactId><version>0.0.1-SNAPSHOT</version><modules><module>product</module><module>order</module><module>common</module></modules><packaging>pom</packaging><name>springcloud</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><mybatisplus.version>3.4.2</mybatisplus.version><springboot.version>2.5.4</springboot.version><springcloud.version>2020.0.3</springcloud.version><springcloud.alibaba.version>2.2.1.RELEASE</springcloud.alibaba.version><mysql.version>8.0.21</mysql.version><druid.version>1.1.16</druid.version><lombok.version>1.18.20</lombok.version></properties><dependencyManagement><dependencies><!--SpringCloud的版本控制--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${springcloud.version}</version><type>pom</type><scope>import</scope></dependency><!--SpringBoot的版本控制--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${springboot.version}</version><type>pom</type><scope>import</scope></dependency><!--SpringCloudAlibaba的版本控制--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${springcloud.alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--mybatis-plus的依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatisplus.version}</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!--druid依赖--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><optional>true</optional></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

微服务远程调用RestTemplate

1)注册RestTemplate
在order-service的OrderApplication中注册RestTemplate

@SpringBootApplication
@MapperScan("com.zcc.springcloud.dao")
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class);}@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}

2)OrderServiceImpl中注入RestTemplate,发送http请求给商品模块

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> implements OrderService {@Autowiredprivate RestTemplate restTemplate;public Category getById(Long catId){//远程调用商品服务获取分类数据Category category = restTemplate.getForObject("http://localhost:9001/category?id="+catId, Category.class);return category;}
}

微服务最基本调用方式是基于RestTemplate发起的http请求实现远程调用
http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。

提供者与消费者
服务A调用服务B,服务B调用服务C,那么服务B是什么角色?

服务调用关系
服务提供者:暴露接口给其它微服务调用
服务消费者:调用其它微服务提供的接口
提供者与消费者角色其实是相对的
一个服务可以同时是服务提供者和服务消费者

服务注册与发现

Eureka注册中心

远程调用的问题

  • 服务消费者该如何获取服务提供者的地址信息?
  • 如果有多个服务提供者,消费者该如何选择?
  • 消费者如何得知服务提供者的健康状态?

Eureka注册中心的作用


在Eureka架构中,微服务角色有两类:

  • EurekaServer:服务端,注册中心

    • 记录服务信息
    • 心跳监控
  • EurekaClient:客户端
    • Provider:服务提供者,例如案例中的 user-service

      • 注册自己的信息到EurekaServer
      • 每隔30秒向EurekaServer发送心跳
    • consumer:服务消费者,例如案例中的 order-service
      • 根据服务名称从EurekaServer拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

Eureka实例搭建


1.搭建EurekaServer
搭建EurekaServer服务步骤如下:
(1)创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

(2)添加application.yml文件,编写下面的配置:

server:port: 10086
spring:application:name: eureka-server
#这里服务注册中心也相当于一个客户端也需要向Eureka注册
eureka:client:service-url:defaultZone: http://localhost:10086/eureka

(3)编写启动类,添加@EnableEurekaServer注解

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {public static void main(String[] args){SpringApplication.run(EurekaServerApplication.class,args);}
}

2.服务注册
将order-service,product-service服务注册到EurekaServer步骤如下:
(1)在order-service项目引入spring-cloud-starter-netflix-eureka-client的依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)添加application.yml文件,编写下面的配置:

server:port: 8000
spring:application:name: order-service
eureka:client:service-url:defaultZone: http://localhost:10086/eureka

product-service注册跟以上步骤一样。

3.服务发现
服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡

(1)修改order模块远程调用的代码,修改访问的url路径,用服务名代替ip、端口:

Category category = restTemplate.getForObject("http://product-service/category?id="+catId, Category.class);

(2)在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate(){return new RestTemplate();
}

总结

搭建EurekaServer
引入eureka-server依赖
添加@EnableEurekaServer注解
在application.yml中配置eureka地址
服务注册
引入eureka-client依赖
在application.yml中配置eureka地址
服务发现
引入eureka-client依赖
在application.yml中配置eureka地址
给RestTemplate添加@LoadBalanced注解
用服务提供者的服务名称远程调用

Eureka自我保护机制

默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。

自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:

  • Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
  • Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
  • 当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
  • 因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。

自我保护开关
Eureka自我保护机制,通过配置 eureka.server.enable-self-preservation=true打开/=false禁用自我保护机制,默认打开状态,建议生产环境打开此配置。

Ribbon负载均衡

负载均衡原理

负载均衡策略

内置负载均衡规则类 规则描述
RoundRobinRule 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule 对以下两种服务器进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RandomRule 重试机制的选择逻辑

负载均衡策略定义

通过定义IRule实现可以修改负载均衡规则,有两种方式:
代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:

@Bean
public IRule randomRule(){return new RandomRule();
}

配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:

userservice:ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule# 负载均衡规则 

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

总结

Ribbon负载均衡规则

  • 规则接口是IRule
  • 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询

负载均衡自定义方式

  • 代码方式:配置灵活,但修改时需要重新打包发布
  • 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置

饥饿加载

  • 开启饥饿加载
  • 指定饥饿加载的微服务名称

Nacos

Nacos服务注册中心

Nacos的安装

  • 下载安装包
  • 解压
  • Linux系统为例在bin目录下运行指令:./startup.sh -m standalone

服务注册到Nacos

(1)POM父工程<dependencyManagement>导入spring-cloud-alibaba-dependencies依赖

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2.2.5.RELEASE</version><type>pom</type><scope>import</scope>
</dependency>

(2)在服务中添加nacos客户端的依赖。

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

(3)配置文件中添加nacos的地址

spring:application:name: product-servicecloud:nacos:discovery:server-addr: 8.140.19.47:8848

Nacos服务分级存储


服务跨集群调用问题
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其它集群

配置文件配置服务集群属性cluster-name

cloud:nacos:discovery:server-addr: 8.140.19.47:8848cluster-name: SH

(1)我们开启三台Product服务 2台配置到SH集群 1台配置到HZ集群

(2)修改order-service中的application.yml,设置集群为SH:

spring:cloud:nacos:discovery:server-addr: 8.140.19.47:8848cluster-name: SH


(3)最后我们设置order-service的负载均衡策略为NacosRule,这个规则优先会寻找与自己同集群的服务。有两种配置方法选择。
1)全局配置:在@configuation的配置类中注入IRule

@SpringBootApplication
@MapperScan("com.zcc.springcloud.dao")
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class);}@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}@Beanpublic IRule iRule(){return new NacosRule();}
}

2)单独服务配置:在配置文件中配置NFLoadBalancerRuleClassName
Order服务中只有远程调用Product服务时,这种负载均衡策略才生效

product-service:ribbon:NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则

NacosRule负载均衡策略总结
优先选择同集群服务实例列表
本地集群找不到提供者,才去其它集群寻找,并且会报警告
确定了可用实例列表后,再采用随机负载均衡挑选实例

Nacos根据权重负载均衡

1.在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮

2.将权重设置为0.1,测试可以发现被访问到的频率大大降低

实例的权重控制总结

  • Nacos控制台可以设置实例的权重值,0~1之间
  • 同集群内的多个实例,权重越高被访问的频率越高
  • 权重设置为0则完全不会被访问

Nacos环境隔离 - namespace

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离

1.在Nacos控制台可以创建namespace,用来隔离不同环境

2.然后填写一个新的命名空间信息:

3.保存后会在控制台看到这个命名空间的id:

4.修改order-service的application.yml,添加namespace:

spring:cloud:nacos:discovery:server-addr: 8.140.19.47:8848cluster-name: SHnamespace: 4b033a39-7d3f-4866-9268-cf8ae49db697 #命名空间的ID

5.重启order-service后,再来查看控制台:

6.此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:

Nacos环境隔离总结
每个namespace都有唯一id
服务设置namespace时要写id而不是名称
不同namespace下的服务互相不可见

Nacos和Eureka的区别

nacos注册中心细节分析

临时实例和非临时实例
服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置:

spring:cloud:nacos:discovery:server-addr: 8.140.19.47:8848ephemeral: false #设置为非临时实例

临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会

Nacos与eureka的共同点
(1)都支持服务注册和服务拉取
(2)都支持服务提供者心跳方式做健康检测

Nacos与Eureka的区别
(1)Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
(2)临时实例心跳不正常会被剔除,非临时实例则不会被剔除
(3)Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
(4)Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

Nacos服务配置中心

Nacos除了可以做注册中心,同样可以做配置管理来使用。

Nacos配置管理

统一配置管理

当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

1.在nacos中添加配置文件

如何在nacos中管理配置呢?

然后在弹出的表单中,填写配置信息:

注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。

2.从微服务拉取配置

微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。
但如果尚未读取application.yml,又如何得知nacos地址呢?
因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:

1)引入nacos-config依赖

首先,在order-service服务中,引入nacos-config的客户端依赖:需要注意SprinCloud2.4版本后不再自动加载bootstrap配置文件,需要手动引入bootstrap依赖

    <!--nacos配置管理依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--SpringCloud2.4版本以后需要手动引入此依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency>

2)添加bootstrap.yaml

然后,在order-service中添加一个bootstrap.yaml文件,内容如下:

    spring:application:name: userservice # 服务名称profiles:active: dev #开发环境,这里是dev cloud:nacos:server-addr: 8.140.19.47:8848 # Nacos地址config:file-extension: yaml # 文件后缀名

重点:这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。

本例中,就是去读取order-service-dev.yaml:

3)读取nacos配置
在order-service中的OrderController中添加业务逻辑,读取pattern.dateFormat配置:

@RestController
@RefreshScope
public class OrderController {@Value("${pattern.dateFormat}")private String dateFormat;@GetMapping("order/config")public String config(){String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateFormat));return format;}

在页面访问,可以看到效果:

统一配置总结:

将配置交给Nacos管理的步骤
在Nacos中添加配置文件
在微服务中引入nacos的config依赖,高版本引入bootstrap依赖
在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件

配置热更新

我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。
要实现配置热更新,可以使用两种方式:

方式一
在@Value注入的变量所在类上添加注解@RefreshScope:

方式二
使用@ConfigurationProperties注解代替@Value注解。

在OrderController中使用这个类代替@Value:

@Data
@ConfigurationProperties(prefix = "pattern")
@Component
public class PatternProperties {private String dateFormat;private String information;
}

热更新总结:

Nacos配置更改后,微服务可以实现热更新,方式:
通过@Value注解注入,结合@RefreshScope来刷新
通过@ConfigurationProperties注入,自动刷新
注意事项:
不是所有的配置都适合放到配置中心,维护起来比较麻烦
建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置

多环境配置共享

微服务启动时会从nacos读取多个配置文件:
[spring.application.name]-[spring.profiles.active].yaml,例如:order-service-dev.yaml
[spring.application.name].yaml,例如:order-service.yaml
无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件

1)添加一个环境共享配置
我们在nacos中添加一个order-service.yaml文件:

2)在order-service中读取共享配置
在order-service服务中,修改PatternProperties类,读取新添加的属性:

测试发现dev环境下也能读取到order-service.yaml配置文件

多种配置的优先级:

配置集

1)、命名空间:配置隔离;
默认:public(保留空间);默认新增的所有配置都在public空间。
1、开发,测试,生产:利用命名空间来做环境隔离。
注意:在bootstrap.properties;配置上,需要使用哪个命名空间下的配置,
spring.cloud.nacos.config.namespace=9de62e44-cd2a-4a82-bf5c-95878bd5e871
2、每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置

2)、配置集ID:类似文件名。
Data ID:类似文件名

3)、配置分组:
默认所有的配置集都属于:DEFAULT_GROUP;

4)、配置集:所有的配置的集合
默认情况下我们服务只会去配置中心读取以下两种情况命名符合的配置文件。
[spring.application.name]-[spring.profiles.active].yaml
[spring.application.name].yaml
如果配置项非常多,都放在这两个配置文件中找起来也会相当麻烦。如果我们把配置文件中的属性按照类型分成多个文件,以后更新就能很方便定位到。例如redis的放一个文件,Mybatis框架的放一个,数据源放一个等等。由于符合默认命名规则的配置文件有限,那么我们如何读取其它命名的配置文件就需要配置配置集

spring:application:name: order-servicecloud:nacos:config:file-extension: yamlserver-addr: 8.140.19.47:8848namespace: 43a86d1b-a66e-4b4b-80d1-5742dfc4a60c #指定配置中心命名空间extension-configs[0]: #加载多配制集data-id: serverport.yaml #指定远程配置文件的名称group: dev #必须配置分组refresh: true #配置热更新extension-configs[1]:data-id: datasource.yamlgroup: devrefresh: trueextension-configs[2]:data-id: order-service-dev.yamlgroup: devrefresh: trueprofiles:active: dev

由于配置文件中配置了spring.profiles.active=dev,那默认就会去配置中心寻找
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。

本例会找到order-service-dev.yaml,默认是从DEFAULT_GROUP分组中找。如果想去其它分组去找可以配置spring.cloud.nacos.config.gropu=dev,指定分组。

如果我们配置集中也配置了order-service-dev.yaml,但是是dev分组的。那么这两个配置文件配置了相同属性的部分谁的优先级高?

测试证明默认寻找的配置文件的优先级大于配置集中指定的配置文件

nacos集群搭建

1.集群结构图

三个nacos节点的地址:
节点 ip port
nacos1 192.168.50.46 8848
nacos2 192.168.50.46 8849
nacos3 192.168.50.46 8850

2.搭建集群步骤

搭建集群的基本步骤:

  • 搭建数据库,初始化数据库表结构
  • 下载nacos安装包
  • 配置nacos
  • 启动nacos集群
  • nginx反向代理

2.1.初始化数据库

Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。

官方推荐的最佳实践是使用带有主从的高可用数据库集群,主从模式的高可用数据库可以参考传智教育的后续高手课程。

这里我们以单点的数据库为例来讲解。

首先新建一个数据库,命名为nacos_config,而后导入SQL文件创建表

SQL文件在nacos的conf目录下nacos-mysql.sql


2.2.下载nacos

nacos在GitHub上有下载地址:https://github.com/alibaba/nacos/tags,可以选择任意版本下载。

本例中用1.4.2版本:

2.3.配置Nacos

将这个包解压到任意非中文目录下,如图:

目录说明:

  • bin:启动脚本
  • conf:配置文件

进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf

然后添加内容:注意这里最好写成指定的IP地址例如192.168.50.46,尽量不要写127.0.0.1。

接着修改application.properties文件,添加数据库配置

2.4.启动

将nacos文件夹复制三份,分别命名为:nacos8848、nacos8849、nacos8850

然后分别修改三个文件夹中的application.properties

nacos8848:

server.port=8848

nacos8849:

server.port=8849

nacos8850:

server.port=8850

然后分别启动三个nacos节点:

./startup.sh

2.5.nginx反向代理
修改conf/nginx.conf文件,配置如下:

http {include       mime.types;default_type  application/octet-stream;upstream nacos-cluster {server 192.168.50.46:8848;server 192.168.50.46:8849;server 192.168.50.46:8850;}server {listen 80 ;server_name 127.0.0.1;location /nacos {proxy_pass http://nacos-cluster;}}
}

而后在浏览器访问:http://192.168.50.46/nacos即可。

代码中application.yml文件配置如下:

spring:cloud:nacos:server-addr: 192.168.50.46:80 # Nacos地址,一定要写nginx代理的端口,否则默认去找8848端口

2.6.优化

  • 实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置.
  • Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离

服务调用feign

先来看我们以前利用RestTemplate发起远程调用的代码:

//远程调用商品服务获取分类数据
String url="http://product-service/category?id="+catId;
Category category = restTemplate.getForObject(url, Category.class);

存在下面的问题:

  • 代码可读性差,编程体验不统一
  • 参数复杂URL难以维护


Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign

其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。

Feign替代RestTemplate

Fegin的使用步骤如下:

1)引入依赖
我们在order-service服务的pom文件中引入feign的依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2)启动类添加@EnableFeignClients注解

3)编写Feign的客户端

在order-service中新建一个接口,内容如下:

@FeignClient("product-service")
public interface ProductFeignService {@GetMapping("/category")Category findById(@RequestParam("id") Long id);
}

重点:这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:product-service
  • 请求方式:GET
  • 请求路径:/category
  • 请求参数:Long id 注意请求参数必须要加上@RequestParam注解,不然报错
  • 返回值类型:Category

这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。

4)测试

修改order-service中的OrderService类中的getById方法,使用Feign客户端代替RestTemplate:

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> implements OrderService {//@Autowired//private RestTemplate restTemplate;@Autowiredprivate ProductFeignService productFeignService;public Category getById(Long catId){//远程调用商品服务获取分类数据//String url="http://product-service/category?id="+catId;//Category category = restTemplate.getForObject(url, Category.class);//使用feign客户端代替RestTemplateCategory byId = productFeignService.findById(catId);return byId;}
}

总结Feign的使用步骤

  • 引入依赖
  • 添加@EnableFeignClients注解
  • 编写FeignClient接口
  • 使用FeignClient中定义的方法代替RestTemplate

Feign自定义配置

Feign可以支持很多的自定义配置,如下表所示:

一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。

下面以日志为例来演示如何自定义配置。

配置文件方式

基于配置文件修改feign的日志级别可以针对单个服务:

feign:  client:config: product-service: # 针对某个微服务的配置loggerLevel: FULL #  日志级别

也可以针对所有服务:

feign:  client:config: default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置loggerLevel: FULL #  日志级别

而日志的级别分为四种:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Java代码方式

也可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象:

public class DefaultFeignConfiguration  {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; // 日志级别为BASIC}
}

如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中:

如果是局部生效,则把它放到对应的@FeignClient这个注解中:

控制台日志显示
2021-09-23 16:28:33.046 DEBUG 17064 — [nio-8000-exec-2] c.z.s.feign.ProductFeignService : [ProductFeignService#findById] —> GET http://product-service/category?id=1 HTTP/1.1
2021-09-23 16:28:33.069 DEBUG 17064 — [nio-8000-exec-2] c.z.s.feign.ProductFeignService : [ProductFeignService#findById] <— HTTP/1.1 200 (23ms)

Feign使用优化

Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。

这里我们用Apache的HttpClient来演示。

1)引入依赖
在order-service的pom文件中引入Apache的HttpClient依赖:

<!--httpClient的依赖 -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>

2)配置连接池

在order-service的application.yml中添加配置:

feign:client:config:default: # default全局的配置loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息httpclient:enabled: true # 开启feign对HttpClient的支持max-connections: 200 # 最大的连接数max-connections-per-route: 50 # 每个路径的最大连接数

接下来,在FeignClientFactoryBean中的loadBalance方法中打断点:

Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient:

总结Feign的优化:

1.日志级别尽量用basic或者默认的none,尽量不要使用full

2.使用HttpClient或OKHttp代替URLConnection

  • ① 引入feign-httpClient依赖
  • ② 配置文件开启httpClient功能,设置连接池参数

最佳实践

所谓最佳实践,就是使用过程中总结的经验,最好的一种使用方式。

自习观察可以发现,Feign的客户端与服务提供者的controller代码非常相似:

feign客户端:

CategoryController:

有没有一种办法简化这种重复的代码编写呢?

1.继承方式

一样的代码可以通过继承来共享:

1)定义一个API接口,利用定义方法,并基于SpringMVC注解做声明。

2)Feign客户端和Controller都集成改接口

优点:

  • 简单
  • 实现了代码共享

缺点:

  • 服务提供方、服务消费方紧耦合
  • 参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解

2.抽取方式

将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。

例如,将ProductFeignService、Category类、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。

实现基于抽取的最佳实践

1)抽取

首先创建一个module,命名为feign-api,然后引入feign的starter依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

然后将order-service中编写的ProductFeignService、Category、DefaultFeignConfiguration都复制到feign-api项目中

2)在order-service中使用feign-api

首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。
在order-service的pom文件中中引入feign-api的依赖:

<dependency><groupId>com.zcc</groupId><artifactId>feign-api</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

修改order-service中的所有与上述三个组件有关的导包部分,改成导入feign-api中的包

3)重启测试

重启后,发现服务报错了:

***************************
APPLICATION FAILED TO START
***************************Description:Field productFeignService in com.zcc.springcloud.service.impl.OrderServiceImpl required a bean of type 'com.zcc.clients.ProductFeignService' that could not be found.

这是因为ProductFeignService现在在feign-api模块的com.zcc.clients包下,

而order-service的@EnableFeignClients注解是在com.zcc.springcloud包下,不在同一个包,无法扫描到ProductFeignService。

4)解决扫描包问题

方式一:

指定Feign应该扫描的包:

@EnableFeignClients(basePackages = "com.zcc.clients")

方式二:

指定需要加载的Client接口:

@EnableFeignClients(clients = {ProductFeignService.class})

总结

Feign的最佳实践:
方法一、让controller和FeignClient继承同一接口
方法二、将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用

不同包的FeignClient的导入有两种方式:
在@EnableFeignClients注解中添加basePackages,指定FeignClient所在的包
在@EnableFeignClients注解中添加clients,指定具体FeignClient的字节码

服务网关Gateway

为什么需要网关

Gateway网关是我们服务的守门神,所有微服务的统一入口。

网关的核心功能特性:

  • 请求路由
  • 权限控制
  • 限流

架构图:

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

在SpringCloud中网关的实现包括两种:

  • gateway
  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

Gateway快速搭建

下面,我们就演示下网关的基本路由功能。基本步骤如下:

1) 创建SpringBoot工程gateway,引入网关依赖

<dependencies><!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--nacos服务发现依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
</dependencies>

2) 编写启动类

@SpringBootApplication
public class GatewayApplication {public static void main(String[] args){SpringApplication.run(GatewayApplication.class,args);}
}

3) 编写基础配置和路由规则

server:port: 10001
spring:application:name: gateway-servicecloud:nacos:discovery:server-addr: 192.168.50.46:80gateway:routes: #网关路由配置- id: order-serviceuri: lb://order-service #路由的目标地址lb就是负载均衡,后面跟服务名称predicates: #路由断言,也就是判断请求是否符合路由规则的条件- Path=/order/** #这个就是按照路径匹配,只要以/order开头就符合要求

我们将符合Path 规则的一切请求,都代理到 uri参数指定的地址。
本例中,我们将 /order/**开头的请求,代理到lb://order-service,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。

4)启动网关服务进行测试
重启网关,访问http://localhost:10001/order/12时,符合/order/**规则,请求转发到uri:
http://order-service/order/12,得到了结果:

5)网关路由的流程图

路由断言工厂Route Predicate Factory

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个:

名称 说明 示例
After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些header - Header=X-Request-Id, \d+
Host 请求必须是访问某个host(域名) - Host=.somehost.org,.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理

网关过滤器

过滤器工厂GatewayFilter

https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#the-rewritepath-gatewayfilter-factory

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

Spring提供了31种不同的路由过滤器工厂。例如:

名称 说明
centered 文本居中 right-aligned 文本居右
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除有一个响应头
RequestRateLimiter 限制请求的流量

过滤器的定义

当前路由过滤器

下面我们以RewritePathGatewayFilter为例。

需求: 让所有请求路径以/api/order开头的路由到order模块,并重写路径为/order/**

只需要修改gateway服务的application.yml文件,添加路由过滤即可:

gateway:routes: #网关路由配置- id: order-serviceuri: lb://order-service #路由的目标地址lb就是负载均衡,后面跟服务名称predicates: #路由断言,也就是判断请求是否符合路由规则的条件- Path=/api/order/** #这个就是按照路径匹配,只要以/order开头就符合要求filters:- RewritePath=/api(?<segment>/?.*),$\{segment}

默认过滤器

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

gateway:routes: #网关路由配置- id: order-serviceuri: lb://order-service #路由的目标地址lb就是负载均衡,后面跟服务名称predicates: #路由断言,也就是判断请求是否符合路由规则的条件- Path=/api/order/** #这个就是按照路径匹配,只要以/order开头就符合要求filters:- RewritePath=/api(?<segment>/?.*),$\{segment}default-filters:- AddRequestHeader=X-Request-red,blue #默认给所有经过网关的请求添加请求头

由于浏览器只能看到我们发送请求时携带的请求头,看不到网关后来给我们额外设置的请求头,为了验证,我们需要在order模块写个处理器方法

@GetMapping("order/header")
public String getHeader(@RequestHeader(value = "X-Request-red",required = false)String header){return header;
}

测试:浏览器输入localhost:10001/api/order/header就会转发到order-service/order/header

全局过滤器

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。

定义方式是实现GlobalFilter接口。

public interface GlobalFilter {/***  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理** @param exchange 请求上下文,里面可以获取Request、Response等信息* @param chain 用来把请求委托给下一个过滤器 * @return {@code Mono<Void>} 返回标示当前过滤器业务结束*/Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

自定义全局过滤器

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:- 参数中是否有authorization,
- authorization参数值是否为admin如果同时满足则放行,否则拦截
@Component
@Order(-1)
public class AuthGlobalFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求参数ServerHttpRequest request = exchange.getRequest();MultiValueMap<String, String> paramMaps = request.getQueryParams();// 2.获取authorization参数String authorization = paramMaps.getFirst("authorization");// 3.校验if("admin".equals(authorization)){// 放行return chain.filter(exchange);}// 4.拦截// 4.1.禁止访问,设置状态码ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.FORBIDDEN);// 4.2.结束处理return response.setComplete();}
}

测试

过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器DefaultFilterGlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:


排序的规则是什么呢?

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

详细内容,可以查看源码:

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链

跨域问题处理

什么是跨域问题

跨域:域名不一致就是跨域,主要包括:

  • 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
  • 域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

模拟跨域问题
创建一个html文件写一个异步发给网关的ajax请求,放到tomcat服务器或者nginx服务器中。

解决跨域问题

方法一:配置文件方式
在gateway服务的application.yml文件中,添加下面的配置:

spring:cloud:gateway:globalcors: # 全局的跨域处理add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题cors-configurations:'[/**]':allowedOrigins: # 允许哪些网站的跨域请求,如果写成"*",允许所有网站跨域- "http://localhost:8080"allowedMethods: # 允许的跨域ajax的请求方式- "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowedHeaders: "*" # 允许在请求中携带的头信息allowCredentials: true # 是否允许携带cookiemaxAge: 360000 # 这次跨域检测的有效期

方法二:java代码方式
在容器中注册CorsWebFilter

@Configuration
public class CorsConfig {@Beanpublic CorsWebFilter corsWebFilter(){CorsConfiguration cors = new CorsConfiguration();cors.addAllowedHeader("*");cors.addAllowedMethod("*");cors.addAllowedOrigin("*");cors.setAllowCredentials(true);cors.setMaxAge(360000L);UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",cors);return new CorsWebFilter(source);}
}

Gateway网关总结

网关搭建步骤:
创建项目,引入nacos服务发现和gateway依赖
配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
路由id:路由的唯一标示
路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
路由断言(predicates):判断路由的规则,
路由过滤器(filters):对请求或响应做处理
PredicateFactory的作用是什么?
读取用户定义的断言条件,对请求做出判断
Path=/user/是什么含义?
路径是以/user开头的就认为是符合的
过滤器的作用是什么?
对路由的请求或响应做加工处理,比如添加请求头
配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
对所有路由都生效的过滤器
全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑
实现全局过滤器的步骤?
实现GlobalFilter接口
添加@Order注解或实现Ordered接口
编写处理逻辑
路由过滤器、defaultFilter、全局过滤器的执行顺序?
order值越小,优先级越高
当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器
CORS跨域要配置的参数包括哪几个?
允许哪些域名跨域?
允许哪些请求头?
允许哪些请求方式?
是否允许使用cookie?
有效期是多久?

限流过滤器(转)

限流:对应用服务器的请求做限制,避免因过多请求而导致服务器过载甚至宕机。限流算法常见的包括两种:

  1. 计数器算法,又包括窗口计数器算法、滑动窗口计数器算法
  2. 漏桶算法(Leaky Bucket)
  3. 令牌桶算法(Token Bucket)

计算器算法

固定窗口计数器算法概念如下:
采用计数器实现限流有点简单粗暴,一般我们会限制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。

具体的实现可以是这样的:对于每次服务调用,可以通过 AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。

这种实现方式,相信大家都知道有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”

漏桶算法

为了消除"突刺现象",可以采用漏桶算法实现限流,漏桶算法这个名字就很形象,算法内部有一个容器,类似生活用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。

不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。

在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池定期从队列中获取请求并执行,可以一次性获取多个并发执行。

这种算法,在使用过后也存在弊端:无法应对短时间的突发流量。

令牌桶算法

从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。

在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌、或者直接拒绝。

放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。


实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。

通过Google开源的guava包,我们可以很轻松的创建一个令牌桶算法的限流器。

SpringCloud相关总结相关推荐

  1. java springcloud面试题_JAVA语言之springboot+springcloud相关面试题

    本文主要向大家介绍了JAVA语言之springboot+springcloud相关面试题,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助. 什么是springboot 用来简化sprin ...

  2. 阿里云maven仓库无法下载SpringCloud相关依赖问题

    1.问题 最近在学习springCloud相关组件,习惯性的把代码放到Github中.当换了一台电脑,Clone后,进行coding时,发现微服务相关依赖不能正常下载.在另一台电脑是可以正常下载的,所 ...

  3. springboot+springcloud相关问题

    什么是springboot1.用来简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置(properties或yml文件)2.创建独立的spring引用程序 main方法运行3.创建独 ...

  4. SpringCloud相关重难点解析

    什么是微服务:微服务是一种经过良好架构设计的分布式架构方案 .它提倡将单一的应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程内,服务之间相互协调,相互配置,为用户提供最终价值. 微服务的架 ...

  5. springboot+springcloud相关面试题

    什么是springboot         用来简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置(properties或yml文件)                  创建独立的s ...

  6. SpringCloud相关jar maven管理工具不能下载(Finchley.M8)

    问题原因: 1.学习开源项目得时候,正好用到了SpringCloud的Finchley.M8,发现用得maven,采用git下载代码到了idea 2.各种下载jar  使用阿里云 镜像最后发现Spri ...

  7. SpringCloud 之 Zuul 网关搭建及配置

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:Anakki blog.csdn.net/qq_29 ...

  8. 聊聊 SpringCloud 中的父子容器

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 来源公号:吉姆餐厅ak 概述 在引入 SpringCloud 的项目中会多次创建 Spring 容 ...

  9. 与springcloud整合的框架源码读取入口

    前提 最近开始准备系统全面的学习springcloud相关组件,过程中会记录并发表出来,今天就是从阅读源码的入口开始. 提疑 就引入一个依赖 一个注解 怎么就可以将相关功能与springcloud给整 ...

最新文章

  1. android程序贴吧,【Android 教程总结贴】归纳所有android贴
  2. 牛客多校3 - Two Matchings(dp)
  3. leetcode-Excel Sheet Column Title
  4. if...else..的错误用法
  5. java对象不会被改变_Java 并发编程(二)对象的不变性和安全的公布对象
  6. 聚焦效率与目标差距,数据才是远程办公的内核!
  7. c语言 数据结构 list、queue、tree抽象数据类型的定义与实现 详尽代码和注释
  8. Spring依赖注入方式
  9. 没有人愿意把自己放在被动的位置,一切皆是有原因的
  10. 数值计算详细笔记(二):非线性方程解法
  11. Windows 安装配置Java开发环境《jdk8》
  12. 中国互联网20年简史(1998-2018),告诉你本质是什么、规律是什么
  13. 网络广告CPS/CPC/CPV/CPM/CPA分别是什么意思
  14. 腾讯的星星海服务器芯片,腾讯云星星海重磅发布两款自研新品 打造软硬一体云计算基础设施...
  15. mysql 未找到 WinSxS_清理WinSxs释放 Win7 C盘所占的空间
  16. 英飞凌XC2000系列CAN BootLoader(CAN_BSL)的实现
  17. NVMe驱动解析-注册设备
  18. Typora快捷键全
  19. MySQL数据库编程01
  20. java支付宝当面付接口_支付宝当面付秘钥生成教程(加对接案例)

热门文章

  1. Anaconda下安装labelimg环境
  2. 用户密码的加密解密操作(前端加密,后端解密)
  3. vlanif接口实现跨越三层网络通信配置
  4. Sparrow项目疑问解答
  5. 动态合并单元格行和列方法封装 ~~~起手可用
  6. ecplise工具栏大小设置
  7. 逐梦AI路,你的孩子气,让你足够强大
  8. repeat()方法的使用
  9. ResNet结构与论文理解
  10. 证件识别技术新升级,让机器为你效劳