09 服务门户:Spring Cloud Gateway 如何把好微服务的大门

上一讲我们学习了 Dubbo 是如何与 Nacos 协同作业。通过对比 RESTful 与 RPC,我们介绍了两种通信方式的区别,再通过实例讲解如何将 Dubbo 与 Nacos 进行整合。但你是否发现无论是基于 OpenFeign 的 RESTful 通信,还是基于 Dubbo 的 RPC 通信,它们都在强调的是微服务间的信息传递,属于微服务架构内部的事情。而对于用户端从外侧访问微服务如何有效管理,微服务又是如何将接口暴露给用户呢?这就需要通过 API 网关实现需求了,本讲咱们就针对 API 网关学习三方面知识:

  1. 介绍 API 网关的用途与产品;

  2. 讲解 Spring Cloud Gateway 的配置技巧;

  3. 讲解 Gateway执行原理与自定义过滤器(Filter)。

API 网关的作用

如下图所示,对于整个微服务来说如果将每一个微服务的接口直接暴露给用户是错误的做法,这里主要体现出三个问题:

  • 服务将所有 API 接口对外直接暴露给用户端,这本身就是不安全和不可控的,用户可能越权访问不属于它的功能,例如普通的用户去访问管理员的高级功能。

  • 后台服务可能采用不同的通信方式,如服务 A 采用 RESTful 通信,服务 B 采用 RPC 通信,不同的接入方式让用户端接入困难。尤其是 App 端接入 RPC 过程更为复杂。

  • 在服务访问前很难做到统一的前置处理,如服务访问前需要对用户进行鉴权,这就必须将鉴权代码分散到每个服务模块中,随着服务数量增加代码将难以维护。

用户端直接访问微服务

为了解决以上问题,API 网关应运而生,加入网关后应用架构变为下图所示。

引入 API 网关后的微服务架构

当引入 API 网关后,在用户端与微服务之间建立了一道屏障,通过 API 网关为微服务访问提供了统一的访问入口,所有用户端的请求被 API 网关拦截并在此基础上可以实现额外功能,例如:

  • 针对所有请求进行统一鉴权、熔断、限流、日志等前置处理,让微服务专注自己的业务。

  • 统一调用风格,通常 API 网关对外提供 RESTful 风格 URL 接口。用户传入请求后,由 API 网关负责转换为后端服务需要的 RESTful、RPC、WebService 等方式,这样便大幅度简化用户的接入难度。

  • 更好的安全性,在通过 API 网关鉴权后,可以控制不同角色用户访问后端服务的权利,实现了服务更细粒度的权限控制。

  • API 网关是用户端访问 API 的唯一入口,从用户的角度来说只需关注 API 网关暴露哪些接口,至于后端服务的处理细节,用户是不需要知道的。从这方面讲,微服务架构通过引入 API 网关,将用户端与微服务的具体实现进行了解耦。

以上便是 API 网关的作用,那 API 网关有哪些产品呢?

API 网关主流产品

API 网关是微服务架构中必要的组件,具体的实现产品在软件市场上层出不穷,下面我列举三款在国内外主流的开源产品。

OpenResty

OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty可以快速构造出足以胜任 10K 以上并发连接响应的超高性能 Web 应用系统。360、UPYUN、阿里云、新浪、腾讯网、去哪儿网、酷狗音乐等都是 OpenResty 的深度用户。

OpenResty 因为性能强大在微服务架构早期深得架构师的喜爱。但 OpenResty 是一款独立的产品,与主流的注册中心存在一定兼容问题,需要架构师独立实现其服务注册、发现的功能。后来 Spring Cloud 官方极力推崇自家封装的 Zuul 或者 Spring Cloud Gateway,渐渐 OpenResty 便淡出了我们的视野。但不能否认,OpenResty 仍是一款优秀的 API 网关产品。

Spring Cloud Zuul

Zuul 是 Netflix 开源的微服务网关,它的主要职责是对用户请求进行路由转发与过滤。早期Spring Cloud 与 Netfilx 合作,使用 Zuul 作为微服务架构的首选网关产品。Zuul 是基于 J2EE Servlet 实现路由转发,网络通信采用同步方式,使用简单部署方便。经过 Spring Cloud 对 Zuul 的封装,Spring Cloud Zuul 应运而生。Spring Cloud Zuul 在原有 Zuul 的基础上,增加对注册中心的支持,同时在基于 Spring Boot Starter 机制基础上,可以在极短的时间内完成 API 网关的开发部署任务。

Zuul 基于 Servlet 的请求响应处理过程

但好景不长,后来 Netflix 内部产生分歧,Netflix 官方宣布 Zuul 停止维护,这让 Spring 机构也必须转型。于是 Spring Cloud 团队决定开发自己的第二代 API 网关产品:Spring Cloud Gateway。

Spring Cloud Gateway

与 Zuul 是“别人家的孩子”不同,Spring Cloud Gateway 是 Spring 自己开发的新一代 API 网关产品。它基于 NIO 异步处理,摒弃了 Zuul 基于 Servlet 同步通信的设计,因此拥有更好的性能。同时,Spring Cloud Gateway 对配置进行了进一步精简,比 Zuul 更加简单实用。

以下是 Spring Cloud Gateway 的关键特征:

  • 基于 JDK 8+ 开发;

  • 基于 Spring Framework 5 + Project Reactor + Spring Boot 2.0 构建;

  • 支持动态路由,能够匹配任何请求属性上的路由;

  • 支持基于 HTTP 请求的路由匹配(Path、Method、Header、Host 等);

  • 过滤器可以修改 HTTP 请求和 HTTP 响应(增加/修改 Header、增加/修改请求参数、改写请求 Path 等等);

  • ...

当下 Spring Cloud Gateway 已然是 Spring Cloud 体系上API 网关标准组件。Spring Cloud Gateway 十分优秀,Spring Cloud Alibaba 也默认选用该组件作为网关产品,下面我们就通过实例讲解 Spring Cloud Gateway 的使用办法。

Spring Cloud Gateway的配置技巧

Spring Cloud Gateway使用入门

示例说明:

假设“service-a”微服务提供了三个 RESTful 接口。

假设 “service-b” 微服务提供了三个 RESTful 接口。

如何通过部署 Spring Cloud Gateway 实现 API 路由功能来屏蔽后端细节呢?

第一步,利用 Spring Initializr 向导创建 Gateway 工程,确保 pom.xml 引入以下依赖:

<!-- Nacos客户端 -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Gateway Starter -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 对外提供Gateway应用监控指标 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

第二步,在 application.yml 增加如下配置。

spring:application:name: gateway #配置微服务idcloud:nacos:discovery:server-addr: 192.168.31.101:8848 #nacos通信地址username: nacospassword: nacosgateway: #让gateway通过nacos实现自动路由转发discovery:locator:enabled: true #locator.enabled是自动根据URL规则实现路由转发
server:port: 80 #服务端口号
management: endpoints:web:exposure:include: '*' #对外暴露actuator所有监控指标,便于监控系统收集跟踪

在上面的配置中最重要的一句是:

spring.cloud.gateway.discovery.locator.enabled=true

这是一个自动项,允许 Gateway 自动实现后端微服务路由转发, Gateway 工程启动后,在浏览器地址栏按下面格式访问后端服务。

http://网关IP:端口/微服务id/URI

例如,网关 IP 为:192.168.31.103,我们需要通过网关执行 service-a 的 list 方法,具体写法为:

http://192.168.31.103:80/service-a/list

访问后 Gateway 按下图流程进行请求路由转发。

基于网关自动路由处理流程

咱们来梳理下路由转发流程:

  1. Gateway、service-a 这些都是微服务实例,在启动时向 Nacos 注册登记;

  2. 用户端向 Gateway 发起请求,请求地址http://192.168.31.103:80/service-a/list;

  3. Gateway 网关实例收到请求,解析其中第二部分 service-a,即微服务 Id,第三部分 URI 为“/list”。之后向 Nacos 查询 service-a 可用实例列表;

  4. Nacos 返回 120 与 121 两个可用微服务实例信息;

  5. Spring Cloud Gateway 内置 Ribbon,根据默认轮询策略将请求转发至 120 实例,转发的完整 URL 将附加用户的 URI,即http://192.168.31.120:80/list;

  6. 120 实例处理后返回 JSON 响应数据给 Gateway;

  7. Gateway 返回给用户端,完成一次完整的请求路由转发过程。

讲到这,我们已理解了 Spring Cloud Gateway 的执行过程。但是真实项目中,存在着各种特殊的路由转发规则,而非自动路由能简单解决的,在 Spring Cloud Gateway 项目中内置了强大的“谓词”系统,可以满足企业应用中的各种转发规则要求,下一小节咱们就来介绍常见的谓词用法。

谓词(Predicate)与过滤器(Filter)

在讲解前需要引入 Gateway 网关三个关键名词:路由(Route)、谓词(Predicate)、过滤器(Filter)。

路由(Route)是指一个完整的网关地址映射与处理过程。一个完整的路由包含两部分配置:谓词(Predicate)与过滤器(Filter)。前端应用发来的请求要被转发到哪个微服务上,是由谓词决定的;而转发过程中请求、响应数据被网关如何加工处理是由过滤器决定的。说起来有些晦涩,我们通过实例进行讲解就容易理解了。

谓词(Predicate)

这里我们给出一个实例,将原有 Gateway 工程的 application.yml 文件修改为下面的设置:

spring:application:name: gatewaycloud:nacos:discovery:server-addr: 192.168.31.10:8848username: nacospassword: nacosgateway: discovery:locator:enabled: false #不再需要Gateway路由转发routes:  #路由规则配置#第一个路由配置,service-a路由规则- id: service_a_route #路由唯一标识#lb开头代表基于gateway的负载均衡策略选择实例uri: lb://service-a #谓词配置predicates:#Path路径谓词,代表用户端URI如果以/a开头便会转发到service-a实例- Path=/a/** #After生效时间谓词,2020年10月15日后该路由才能在网关对外暴露- After=2020-10-05T00:00:00.000+08:00[Asia/Shanghai]#谓词配置filters:#忽略掉第一层前缀进行转发- StripPrefix=1 #为响应头附加X-Response=Blue- AddResponseHeader=X-Response,Blue #第二个路由配置,service-b路由规则- id: service_b_routeuri: lb://service-bpredicates:- Path=/b/**filters:- StripPrefix=1
server:port: 80
management:endpoints:web:exposure:include: '*'

我来翻译下上面的配置:
在 2020 年 10 月 15 日后,当用户端发来/a/...开头的请求时,Spring Cloud Gateway 会自动获取 service-a 可用实例,默认采用轮询方式将URI附加至实例地址后,形成新地址,service-a处理后 Gateway 网关自动在响应头附加 X-Response=Blue。至于第二个 service_b_route,比较简单,只说明当用户访问/b开头 URL 时,转发到 service-b 可用实例。

完整的路由配置格式固定如下:

spring:gateway: discovery:locator:enabled: false  #不再需要Gateway路由转发routes: - id: xxx #路由规则iduri: lb://微服务id  #路由转发至哪个微服务predicates: //具体的谓词filters://具体的过滤器

其中 predicates 是重点,说明路由生效条件,在这里我将常见的谓词使用形式列出来。

  • After 代表在指定时点后路由规则生效。

predicates:- After=2020-10-04T00:00:00.000+08:00
  • Before 代表在指定时点前路由规则生效。

predicates:- Before=2020-01-20T17:42:47.789-07:00[America/Denver]
  • Path 代表 URI 符合映射规则时生效。

predicates:- Path=/b/**
  • Header 代表包含指定请求头时生效。

predicates:- Header=X-Request-Id, \d+

这里额外解释下,如果请求具有名为 X-Request-Id 的 Header,其值与\d+正则表达式匹配(具有一个或多个数字的值),则该路由匹配。

  • Method 代表要求 HTTP 方法符合规定时生效。

predicates:- Method=GET

谓词是 Gateway 网关中最灵活的部分,刚才列举的是最常用的谓词,还有很多谓词是在文中没有提到,如果你对这部分感兴趣可以翻阅https://spring.io/projects/spring-cloud-gateway进行学习。

过滤器(Filter)

过滤器(Filter)可以对请求或响应的数据进行额外处理,这里我们列出三个最常用的内置过滤器进行说明。

  • AddRequestParameter 是对所有匹配的请求添加一个查询参数。

filters:
- AddRequestParameter=foo,bar #在请求参数中追加foo=bar
  • AddResponseHeader 会对所有匹配的请求,在返回结果给客户端之前,在 Header 中添加响应的数据。

#在Response中添加Header头,key=X-Response-Foo,Value=Bar。
filters:
- AddResponseHeader=X-Response,Blue
  • Retry 为重试过滤器,当后端服务不可用时,网关会根据配置参数来发起重试请求。

filters:
#涉及过滤器参数时,采用name-args的完整写法
- name: Retry #name是内置的过滤器名args: #参数部分使用args说明retries: 3status: 503

以上片段含义为,当后端服务返回 503 状态码的响应后,Retry 过滤器会重新发起请求,最多重试 3 次。

以上是三种最常用的内置过滤器的使用案例,因为 Spring Cloud 内置过滤器将近 30 个,这里咱们就不一一列举,有兴趣的同学可以查询官方资料。

https://docs.spring.io/spring-cloud-gateway/docs/2.2.6.RELEASE/reference/html/#gatewayfilter-factories

Gateway 的执行原理与自定义过滤器

Spring Cloud Gateway 的执行原理

在初步掌握 Spring Cloud Gateway 的配置技巧与谓词用法后,我们来关注 Gateway 底层的实现细节。

下图是 Spring Cloud Gateway 的执行流程。

按执行顺序可以拆解以下几步:

  1. Spring Cloud Gateway 启动时基于 Netty Server 监听指定的端口(该端口可以通过 server.port 属性自定义)。当前端应用发送一个请求到网关时,进入 Gateway Handler Mapping 处理过程,网关会根据当前 Gateway 所配置的谓词(Predicate)来决定是由哪个微服务进行处理。

  2. 确定微服务后,请求向后进入 Gateway Web Handler 处理过程,该过程中 Gateway 根据过滤器(Filters)配置,将请求按前后顺序依次交给 Filter 过滤链进行前置(Pre)处理,前置处理通常是对请求进行前置检查,例如:判断是否包含某个指定请求头、检查请求的 IP 来源是否合法、请求包含的参数是否正确等。

  3. 当过滤链前置(Pre)处理完毕后,请求会被 Gateway 转发到真正的微服务实例进行处理,微服务处理后会返回响应数据,这些响应数据会按原路径返回被 Gateway 配置的过滤链进行后置处理(Post),后置处理通常是对响应进行额外处理,例如:将处理过程写入日志、为响应附加额外的响应头或者流量监控等。

可以看到,在整个处理过程中谓词(Predicate)与过滤器(Filter)起到了重要作用,谓词决定了路径的匹配规则,让 Gateway 确定应用哪个微服务,而 Filter 则是对请求或响应作出实质的前置、后置处理。

在项目中功能场景多种多样,像日常的用户身份鉴权、日志记录、黑白名单、反爬虫等基础功能都可以通过自定义 Filter 为 Gateway 进行功能扩展,下面我们通过“计时过滤器”为例,讲解如何为 Gateway 绑定自定义全局过滤器。

自定义全局过滤器

在 Spring Cloud Gateway 中,自定义过滤器分为两种,全局过滤器与局部过滤器。两者唯一的区别是:全局过滤器默认应用在所有路由(Route)上,而局部过滤器可以为指定的路由绑定。下面咱们通过“计时过滤器”这个案例讲解全局过滤器的配置。所谓计时过滤器是指任何从网关访问的请求,都要在日志中记录下从请求进入到响应退出的执行时间,通过这个时间运维人员便可以收集并分析哪些功能进行了慢处理,以此为依据进行进一步优化。下面是计时过滤器的代码,重要的部分我通过注释进行了说明。

package com.lagou.gateway.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component //自动实例化并被Spring IOC容器管理
//全局过滤器必须实现两个接口:GlobalFilter、Ordered
//GlobalFilter是全局过滤器接口,实现类要实现filter()方法进行功能扩展
//Ordered接口用于排序,通过实现getOrder()方法返回整数代表执行当前过滤器的前后顺序
public class ElapsedFilter implements GlobalFilter, Ordered {//基于slf4j.Logger实现日志输出private static final Logger logger = LoggerFactory.getLogger(ElapsedFilter.class);//起始时间属性名private static final String ELAPSED_TIME_BEGIN = "elapsedTimeBegin";/*** 实现filter()方法记录处理时间* @param exchange 用于获取与当前请求、响应相关的数据,以及设置过滤器间传递的上下文数据* @param chain Gateway过滤器链对象* @return Mono对应一个异步任务,因为Gateway是基于Netty Server异步处理的,Mono对就代表异步处理完毕的情况。*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//Pre前置处理部分//在请求到达时,往ServerWebExchange上下文环境中放入了一个属性elapsedTimeBegin,保存请求执行前的时间戳exchange.getAttributes().put(ELAPSED_TIME_BEGIN, System.currentTimeMillis());

//chain.filter(exchange).then()对应Post后置处理部分
        //当响应产生后,记录结束与elapsedTimeBegin起始时间比对,获取RESTful API的实际执行时间
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> { //当前过滤器得到响应时,计算并打印时间
                    Long startTime = exchange.getAttribute(ELAPSED_TIME_BEGIN);
                    if (startTime != null) {
                        logger.info(exchange.getRequest().getRemoteAddress() //远程访问的用户地址
                                + " | “ +  exchange.getRequest().getPath()  //Gateway URI
                                + ” | cost " + (System.currentTimeMillis() - startTime) + “ms”); //处理时间
                    }
                })
        );
    }
    //设置为最高优先级,最先执行ElapsedFilter过滤器
    //return Ordered.LOWEST_PRECEDENCE; 代表设置为最低优先级
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

运行后通过 Gateway 访问任意微服务便会输出日志:

2021-01-10 12:36:01.765  INFO 14052 --- [ctor-http-nio-4] com.lagou.gateway.filter.ElapsedFilter   : /0:0:0:0:0:0:0:1:57873 | /test-service/test | cost 821ms

日志包含四部分:

  • 日志的基础信息包括时间、日志级别、线程、产生的类与方法等。

  • /0:0:0:0:0:0:0:1:57873 代表访问者的远程 IP 端口等信息。

  • /test-service/test 是通过 Gateway 访问的完整 URI,第一部分是服务名,第二部分是 RESTful 接口。

  • cost 821ms 是具体的执行时间。

以上就是全局过滤器的开发方法,至于局部过滤器的配置方法与全局过滤器极为相似,有兴趣的同学通过下面的官方文档了解更详细的内容。

https://docs.spring.io/spring-cloud-gateway/docs/2.2.7.BUILD-SNAPSHOT/reference/html/

小结与预告

这一讲咱们学了三方面内容,首先讲解了什么是 API 网关,API 网关是负责微服务请求统一路由转发的组件,用户端所有的请求都要经过 API 网关路由、加工、过滤后送达给后端微服务。其次,讲解了 Spring Cloud Gateway 网关的部署方式,了解到谓词(Predicate)与过滤器(Filter)的作用。最后,咱们通过实例介绍了 Spring Cloud Gateway 的执行原理并实现了计时功能的全局过滤器。

这里给你出一道思考题:结合你当前的项目思考下,API 网关除了路由还能额外实现哪些功能呢?

下一讲我们将开始一个新篇章,介绍在微服务环境下如何通过服务降级、熔断等机制保护我们的微服务架构,避免雪崩效应的产生。


10 系统保护:微服务架构雪崩效应与服务限流

前面我们介绍了 OpenFeign 微服务间通信与 Spring Cloud Gateway 网关通信,这些是日常业务中的正常处理情况,但是在微服务环境下受制于网络、机器性能、算法、程序各方面影响,运行异常的情况也在显著提升,如果不做好异常保护,微服务架构就像空中楼阁一样随时可能会崩溃,从本节我们开始一个新话题:微服务的系统保护。通过这个话题,我们就可以了解在 Spring Cloud Alibaba 生态下解决雪崩等一系列危机整体架构安全的方法。

这一讲我们先来对 Alibaba Sentinel 做入门讲解,涉及三方面内容:

  • 通过真实的生产案例讲解微服务的雪崩效应;

  • Alibaba Sentinel 的部署与配置;

  • 配置的接口限流规则。

微服务的雪崩效应

一次线上的服务雪崩事故

“雪崩”一词指的是山地积雪由于底部溶解等原因造成的突然大块塌落的现象,具有很强的破坏力。在微服务项目中指由于突发流量导致某个服务不可用,从而导致上游服务不可用,并产生级联效应,最终导致整个系统不可用,使用雪崩这个词来形容这一现象最合适不过。

之前我在国内一家著名的金融服务机构负责架构设计与维护,经历过不止一次服务雪崩,这也给我造成了一些心理阴影。在日后的架构设计中,我总是情不自禁地从最坏的情况考虑,尽我所能规避最差的情况发生。

下面我跟大家分享一次险些给公司带来重大损失的雪崩事故,希望各位能引以为戒。

在 2015 年 “618” 前几天,当时我负责公司普惠金融业务线架构。

普惠金融业务流程

平时从借款人门户的订单接口的访问峰值约为 100 TPS,因为并发压力不大,我对系统性能与可用性考虑不足,为节约成本也只为每一个子系统部署了单台高性能服务器。没想到在 6 月 16 日上午 10 点 30 分,技术部接到紧急通知,除催收系统外,各节点服务全线崩溃。崩溃前借款人门户订单接口 TPS 峰值高达 2000,出现大量请求积压,之后运维迅速重启服务,但没过5分钟,系统再次崩溃。技术部大佬和架构师们都发觉情况不妙,迅速启用 Nginx 限流,设置最多放行 200 TPS,多余请求直接拒绝,并且总部电话远程调度各区域客户经理分时间段录入借款数据,这才度过了本次难关。

事后,架构组对本次问题进行复盘,发现 2015 年 618 是某东大促,客户购物热情激增,此时公司为配合 618 大促,推出了优惠力度最大的借款活动,这导致在这几天的业务量是平时的几十倍。而分散在全国的 20000 多名客户经理的工作习惯是在上午十点多集中录入昨日客户借款单据,如此庞大的短时并发让“贷后系统”不堪重负,出现长时未响应。同时上游的“借款人门户”与“信审风控”也处于阻塞等待的状态。而在客户经理这边看到表单提交无响应,便会 F5 刷新页面重发请求,这无疑又为“贷后系统”雪上加霜,之后便是贷后系统崩溃、信审系统崩溃、借款人门户网站崩溃这一连锁反应。

发现了问题根源,解决也就简单了,经过多方权衡在以下几方面作出应对:

  • 提高可用性,将单台设备转为多台负载均衡集群。

  • 提高性能,检查慢 SQL、优化算法、引入缓存来缩短单笔业务的处理时间。

  • 预防瞬时 TPS 激增,将系统限流作为常态加入系统架构。

  • 完善事后处理,遇到长响应,一旦超过规定窗口时间,服务立即返回异常,中断当前处理。

  • 加强预警与监控,引入 ELK,进行流量实时监控与风险评估,及时发现系统风险。

  • 完善制度,要求客户单据当日录入系统。

刚才所描述的是一次典型的雪崩效应,下面我们来梳理下分布式架构为什么会产生雪崩?

为什么微服务会产生雪崩效应

如下图所示,假如我们开发了一套分布式应用系统,前端应用分别向 A/H/I/P 四个服务发起调用请求:

前端应用需要四个服务

但随着时间推移,假如服务 I 因为优化问题,导致需要 20 秒才能返回响应,这就必然会导致 20 秒内该请求线程会一直处于阻塞状态。

其中一个出现长延时,会导致前端应用线程阻塞

但是,如果这种状况放在高并发场景下,就绝对不允许出现,假如在 20 秒内有 10 万个请求通过应用访问到后端微服务。容器会因为大量请求阻塞积压导致连接池爆满,而这种情况后果极其严重!轻则“服务无响应”,重则前端应用直接崩溃。

以上这种因为某一个节点长时间处理导致应用请求积压崩溃的现象被称为微服务的“雪崩效应”。

当大量线程积压后,前端应用崩溃,雪崩效应产生

如何有效避免雪崩效应?

刚才我们分析了雪崩效应是因为出现瞬间大流量+微服务响应慢造成的。针对这两点在架构设计时要采用不同方案。

  • 采用限流方式进行预防:可以采用限流方案,控制请求的流入,让流量有序的进入应用,保证流量在一个可控的范围内。

  • 采用服务降级与熔断进行补救:针对响应慢问题,可以采用服务降级与熔断进行补救。服务降级是指当应用处理时间超过规定上限后,无论服务是否处理完成,便立即触发服务降级,响应返回预先设置的异常信息。

这么说有些晦涩,以下图为例。在用户支付完成后,通过消息通知服务向用户邮箱发送“订单已确认”的邮件。假设消息通知服务出现异常,需要 10 秒钟才能完成发送请求, 这是不能接受的。为了预防雪崩,我们可以在微服务体系中增加服务降级的功能,预设 2 秒钟有效期,如遇延迟便最多允许 2 秒,2 秒内服务未处理完成则直接降级并返回响应,此时支付服务会收到“邮件发送超时”的错误信息。这也就意味着消息通知服务最多只有两秒钟的处理时间。处理结果,要么发送成功,要么超时降级。 因此阻塞时间缩短,产生雪崩的概率会大大降低。

通过服务降级减少阻塞时间

Alibaba Sentinel

有了解决问题的方案,下面咱们就可以聊聊落地实现的事情。在 Spring Cloud Alibaba 生态中有一个重要的流控组件 Sentinel。Sentinel 以流量为切入点,从流量控制熔断降级系统负载保护等多个维度保护服务的稳定性。

Alibaba Sentinel

Sentinel 具有以下特征。

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

  • 广泛的开源生态:Sentinel 提供开箱即用的与其他开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 整合只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。

  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Alibaba Sentinel特性图

Sentinel 配置入门

Sentinel 分为两个部分:Sentinel DashboardSentinel 客户端

  • Sentinel Dashboard:Sentinel Dashboard 是 Sentinel 配套的可视化控制台与监控仪表盘套件,它支持节点发现,以及健康情况管理、监控(单机和集群)、规则管理和推送的功能。Sentinel Dashboard 是基于 Spring Boot 开发的 WEB 应用,打包后可以直接运行,目前最新版本为 1.8.0。

Sentinel Dashboard

  • Sentinel 客户端:Sentinel 客户端需要集成在 Spring Boot 微服务应用中,用于接收来自 Dashboard 配置的各种规则,并通过 Spring MVC Interceptor 拦截器技术实现应用限流、熔断保护。

部署 Sentinel Dashboard(仪表盘)

1. 访问:https://github.com/alibaba/Sentinel/releases,下载最新版 Sentinel Dashboard。

Sentinel Dashboard GitHub 下载页

2. 利用下面的命令启动 Dashboard。

java -jar -Dserver.port=9100 sentinel-dashboard-1.8.0.jar

这个命令的含义是启动 Sentinel Dashboard 应用,Sentinel Dashboard 会监听9100端口实现与微服务的通信。同时我们可以访问下面的网址查看 Sentinel Dashboard 的UI界面。
http://192.168.31.10:9100

在用户名、密码处输入 sentinel/sentinel,便可进入 Dashboard。

Sentinel Dashboard 登录页

![在这里插入图片描述](https://img-blog.csdnimg.cn/2f04129258a5466faf3e32b2aea13051.png#pic_center)

Sentinel Dashboard 首页

微服务内置 Sentinel 客户端

第一步,利用 Spring Initializr 向导创建 sentinel-sample 工程,pom.xml 增加以下三项依赖。

<!-- Nacos客户端Starter-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Sentinel客户端Starter-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 对外暴露Spring Boot监控指标-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

第二步,配置 Nacos 与 Sentinel 客户端。

spring:application:name: sentinel-sample #应用名&微服务idcloud:sentinel: #Sentinel Dashboard通信地址transport:dashboard: 192.168.31.10:9100eager: true #取消控制台懒加载nacos: #Nacos通信地址server-addr: 192.168.31.10:8848username: nacospassword: nacos
server:port: 80
management:endpoints:web: #将所有可用的监控指标项对外暴露exposure: #可以访问 /actuator/sentinel进行查看Sentinel监控项include: '*'

第三步,验证配置。

访问下面网址,在 Sentinel Dashboard 左侧看到 sentinel-sample 服务出现,则代表 Sentinel 客户端与 Dashboard 已经完成通信。

http://192.168.31.10:9100/

确认 sentinel-sampl e与 Dashboard 通信成功

下面咱们通过 Dashboard 配置应用的限流规则,来体验下 Sentinel 的用法。

配置限流规则

第一步,在 sentinel-sample 服务中,增加 SentinelSampleController 类,用于演示 Sentinel 限流规则。

package com.lagou.sentinelsample.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SentinelSampleController {@GetMapping("/test_flow_rule")public String testFlowRule(){return "SUCCESS";}
}

启动 sentinel-sample,访问http://localhost/test_flow_rule,无论刷新多少次,都会看到“SUCCESS”。

正常访问结果

第二步,访问 Sentinel Dashboard 配置限流规则。

在左侧找到簇点链路,右侧定位到 /test_flow_rule,点击后面的“流控”按钮。

设置接口流控规则

在弹出界面,按下图配置,其含义为 /test_flow_rule 接口每秒钟只允许 1QPS 访问,超出的请求直接服务降级返回异常。最后点击“新增”完成规则设置。

设置流控规则

此时针对 /test_flow_rule 接口的流控规则已生效,可以在“流控规则”面板看到。

现有流控规则列表

第三步,验证流控效果。

重新访问http://localhost/test_flow_rule,浏览器反复刷新。

第一次刷新时会出现“SUCCESS”文本代表处理成功。

![在这里插入图片描述](https://img-blog.csdnimg.cn/fea3626db92c4e3fb5436da96475a9f7.png#pic_center)

第一次执行成功

同一秒内再次刷新便会出现 “Blocked by Sentinel (flow limiting)”,代表服务已被限流降级。

第二次限流降级

到这里,我们已经利用 Sentinel 对微服务接口实施了初步的限流降级控制,Sentinel 还有很多高级的用法,我们在后面继续深入讲解。

小结与预告

这一讲我们主要对 Sentinel 进行入门学习,让你有个感性认识。本节讲解三方面内容,首先通过在工作中真实的案例分析了雪崩效应的产生与预防办法,其次介绍 Alibaba Sentinel Dashboard 与客户端的配置过程,最后演示了如何对微服务接口进行限流降级。

这里我预留一道讨论题:在架构设计时,你是如何预估某个接口线上运行时的 QPS 范围呢?你可以把自己的经验写在评论中,我们一起探讨。

下一讲,我们继续深入 Sentinel,了解 Sentinel 的高级特性与执行原理。


11 限流与熔断:Sentinel 在项目中的最佳实践

上一讲我们讲解了微服务的雪崩效应与如何基于 Sentinel 实现初步微服务限流,掌握了部署 Sentinel Dashboard与配置 Sentinel Core 客户端的技巧。本讲咱们继续 Sentinel 这个话题,将更有针对性的讲解 Sentinel 底层的细节与限流、熔断的各种配置方式。

本讲咱们主要学习三方面内容:

  • Sentinel 通信与降级背后的技术原理;

  • Sentinel 限流降级的规则配置;

  • Sentinel 熔断降级的规则配置。

下面咱们先开始第一部分。

Sentinel Dashboard通信与降级原理

Sentinel Dashboard 是Sentinel的控制端,是新的限流与熔断规则的创建者。当内置在微服务内的 Sentinel Core(客户端)接收到新的限流、熔断规则后,微服务便会自动启用的相应的保护措施。

按执行流程,Sentinel 的执行流程分为三个阶段:

  1. Sentinel Core 与 Sentinel Dashboard 建立连接;

  2. Sentinel Dashboard 向 Sentinel Core 下发新的保护规则;

  3. Sentinel Core 应用新的保护规则,实施限流、熔断等动作。

第一步,建立连接。

Sentine Core 在初始化的时候,通过 application.yml 参数中指定的 Dashboard 的 IP地址,会主动向 dashboard 发起连接的请求。

#Sentinel Dashboard通信地址
spring: cloud:sentinel: transport:dashboard: 192.168.31.10:9100

该请求是以心跳包的方式定时向 Dashboard 发送,包含 Sentinel Core 的 AppName、IP、端口信息。这里有个重要细节:Sentinel Core为了能够持续接收到来自 Dashboard的数据,会在微服务实例设备上监听 8719 端口,在心跳包上报时也是上报这个 8719 端口,而非微服务本身的 80 端口。

Sentinel Core 向 Dashboard 建立连接

在 Sentinel Dashboard 接收到心跳包后,来自 Sentinel Core的AppName、IP、端口信息会被封装为 MachineInfo 对象放入 ConcurrentHashMap 保存在 JVM的内存中,以备后续使用。

第二步,推送新规则。

如果在 Dashboard 页面中设置了新的保护规则,会先从当前的 MachineInfo 中提取符合要求的微服务实例信息,之后通过 Dashboard内置的 transport 模块将新规则打包推送到微服务实例的 Sentinel Core,Sentinel Core收 到新规则在微服务应用中对本地规则进行更新,这些新规则会保存在微服务实例的 JVM 内存中。

Sentinel Dashboard 向Sentinel Core推送新规则

第三步,处理请求。

Sentinel Core 为服务限流、熔断提供了核心拦截器 SentinelWebInterceptor,这个拦截器默认对所有请求 /** 进行拦截,然后开始请求的链式处理流程,在对于每一个处理请求的节点被称为 Slot(槽),通过多个槽的连接形成处理链,在请求的流转过程中,如果有任何一个 Slot 验证未通过,都会产生 BlockException,请求处理链便会中断,并返回“Blocked by sentinel" 异常信息。

SentinelWebInterceptor 实施请求拦截与保护

那这些 Slot 都有什么作用呢?我们需要了解一下,默认 Slot 有7 个,前 3 个 Slot为前置处理,用于收集、统计、分析必要的数据;后 4 个为规则校验 Slot,从Dashboard 推送的新规则保存在“规则池”中,然后对应 Slot 进行读取并校验当前请求是否允许放行,允许放行则送入下一个 Slot 直到最终被 RestController 进行业务处理,不允许放行则直接抛出 BlockException 返回响应。

以下是每一个 Slot 的具体职责:

  • NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;

  • ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT(运行时间), QPS, thread count(线程总数)等,这些信息将用作为多维度限流,降级的依据;

  • StatistcSlot 则用于记录,统计不同维度的runtime 信息;

  • SystemSlot 则通过系统的状态,例如CPU、内存的情况,来控制总的入口流量;

  • AuthoritySlot 则根据黑白名单,来做黑白名单控制;

  • FlowSlot 则用于根据预设的限流规则,以及前面 slot 统计的状态,来进行限流;

  • DegradeSlot 则通过统计信息,以及预设的规则,来做熔断降级。

到这里我们理解了 Sentinel 通信与降级背后的执行过程,下面咱们学习如何有效配置 Sentinel 的限流策略。

Sentinel 限流降级的规则配置

滑动窗口算法

实现限流降级的核心是如何统计单位时间某个接口的访问量,常见的算法有计数器算法、令牌桶算法、漏桶算法、滑动窗口算法。Sentinel 采用滑动窗口算法来统计访问量。

滑动窗口算法并不复杂,咱们举例说明:某应用限流控制 1 分钟最多允许 600 次访问。采用滑动窗口算法是将每 1 分钟拆分为 6(变量)个等份时间段,每个时间段为 10 秒,6 个时间段为 1 组在下图用红色边框区域标出,而这个红色边框区域就是滑动窗口。当每产生 1 个访问在对应时间段的计数器自增加 1,当滑动窗口内所有时间段的计数器总和超过 600,后面新的访问将被限流直接拒绝。同时每过 10 秒,滑动窗口向右移动,前面的过期时间段计数器将被作废。

总结下,滑动窗口算法的理念是将整段时间均分后独立计数再汇总统计,滑动窗口算法被广泛应用在各种流控场景中,请你理解它的实现过程。

滑动窗口算法

基于 Sentinel Dashboard 的限流设置

在 Sentinel Dashboard 中“簇点链路”,找到需要限流的 URI,点击“+流控”进入流控设置。小提示,sentinel-dashboard 基于懒加载模式,如果在簇点链路没有找到对应的 URI,需要先访问下这个功能的功能后对应的 URI 便会出现。

流控设置界面

流控规则项目说明主要有以下几点。

  • 资源名:要流控的 URI,在 Sentinel 中 URI 被称为“资源”;

  • 针对来源:默认 default 代表所有来源,可以针对某个微服务或者调用者单独设置;

  • 阈值类型:是按每秒访问数量(QPS)还是并发数(线程数)进行流控;

  • 单机阈值:具体限流的数值是多少。

默认流控规则

点击对话框中的“高级选项”,就会出现更为详细的设置项。

其中流控模式是指采用什么方式进行流量控制。Sentinel支持三种模式:直接、关联、链路,下面咱们分别讲解这三种模式。

  • 直接模式:

以下图为例,当 List 接口 QPS 超过 1个时限流,浏览器会出现“Blocked by Sentinel”。

流控模式-直接

  • 关联模式:

如下图所示,当同 List 接口关联的update 接口 QPS 超过 1 时,再次访问List 接口便会响应“Blocked by Sentinel”。

流控模式-关联

  • 链路模式:

链路模式相对复杂,我们举例说明,现在某公司开发了一个单机的电商系统,为了满足完成“下订单”的业务,程序代码会依次执行订单创建方法->减少库存方法->微信支付方法->短信发送方法。方法像链条一样从前向后依次执行,这种执行的链条被称为调用链路,而链路模式限流就是为此而生。

以下图为例,在某个微服务中 List 接口,会被 Check 接口调用。在另一个业务,List 接口也会被 Scan 接口调用。

调用链路

但如果按下图配置,将入口资源设为“/check”,则只会针对 check 接口的调用链路生效。当访问 check 接口的QPS 超过 1 时,List 接口就会被限流。而另一条链路从 scan 接口到List 接口的链路则不会受到任何影响。链路模式与关联模式最大的区别是 check 接口与 List 接口必须是在同一个调用链路中才会限流,而关联模式是任意两个资源只要设置关联就可以进行限流。

流控模式-链路

讲完了直接、关联、链路三种流控模式,下面咱们聊一聊高级选项中的“流控效果”。

流控效果是指在达到流控条件后,对当前请求如何进行处理。流控效果有三种:快速失败Warm UP(预热)排队等待

  • 快速失败:

快速失败是指流浪当过限流阈值后,直接返回响应并抛出 BlockException,快速失败是最常用的处理形式。如下图所示,当 List 接口每秒 QPS 超过 1 时,可以直接抛出“Blocked By Sentinel”异常。

流控效果-快速失败

  • Warm Up(预热):

Warm Up 用于应对瞬时大并发流量冲击。当遇到突发大流量 Warm Up 会缓慢拉升阈值限制,预防系统瞬时崩溃,这期间超出阈值的访问处于队列等待状态,并不会立即抛出 BlockException。

如下图所示,List 接口平时单机阈值 QPS 处于低水位:默认为 1000/3 (冷加载因子)≈333,当瞬时大流量进来,10 秒钟内将 QPS 阈值逐渐拉升至 1000,为系统留出缓冲时间,预防突发性系统崩溃。

流控效果-Warm Up

  • 排队等待:

排队等待是采用匀速放行的方式对请求进行处理。如下所示,假设现在有100个请求瞬间进入,那么会出现以下几种情况:

  1. 单机 QPS 阈值=1,代表每 1 秒只能放行 1 个请求,其他请求队列等待,共需 100 秒处理完毕;

  2. 单机 QPS 阈值=4,代表 250 毫秒匀速放行 1 个请求,其他请求队列等待,共需 25 秒处理完毕;

  3. 单机 QPS 阈值=200,代表 5 毫秒匀速放行一个请求,其他请求队列等待,共需 0.5 秒处理完毕;

  4. 如果某一个请求在队列中处于等待状态超过 2000 毫秒,则直接抛出 BlockException。

注意,匀速队列只支持 QPS 模式,且单机阈值不得大于 1000。

流控效果-排队等待

讲到这,我为你讲解了从滑动窗口统计流量到 Sentinel Dashboard 如何进行流控配置。下面咱们再来讲解 Sentinel的熔断降级策略。

Sentinel 熔断降级的规则配置

什么是熔断?

先说现实中的股市熔断机制。2016 年 1 月 4 日,A 股遇到史上首次熔断,沪指开盘后跌幅超过 5%,直接引发熔断。三家股市交易所暂停交易15分钟,但恢复交易之后股市继续下跌,三大股市交易所暂停交易至闭市。通过现象可以看出熔断是一种保护机制,当事物的状态达到某种“不可接受”的情况时,便会触发“熔断”。在股市中,熔断条件就是大盘跌幅超过 5%,而熔断的措施便是强制停止交易 15 分钟,之后尝试恢复交易,如仍出现继续下跌,便会再次触发熔断直接闭市。但假设 15分钟后,大盘出现回涨,便认为事故解除继续正常交易。这是现实生活中的熔断,如果放在软件中也是一样的。

微服务的熔断是指在某个服务接口在执行过程中频繁出现故障的情况,我们便认为这种状态是“不可接受”的,立即对当前接口实施熔断。在规定的时间内,所有送达该接口的请求都将直接抛出 BlockException,在熔断期过后新的请求进入看接口是否恢复正常,恢复正常则继续运行,仍出现故障则再次熔断一段时间,以此往复直到服务接口恢复。

下图清晰的说明了 Sentinel的熔断过程:

  1. 设置熔断的触发条件,当某接口超过20%的请求访问出现故障,便启动熔断;

  2. 在熔断状态下,若干秒内所有该接口的请求访问都会直接抛出BlockException拒绝访问。

  3. 熔断器过后,下一次请求重新访问接口,当前接口为“半开状态”,后续处理以下分两种情况。

  • 当前请求被有效处理,接口恢复到正常状态。

  • 当前请求访问出现故障,接口继续熔断。

Sentinel 熔断机制

基于SentinelDashboard的熔断设置

Sentinel Dashboard可以设置三种不同的熔断模式:慢调用比例、异常比例、异常数,下面我们分别讲解:

  • 慢调用比例是指当接口在1秒内“慢处理”数量超过一定比例,则触发熔断。

熔断模式-慢调用比例

结合上图的设置,介绍下“慢调用比例”熔断规则。

  • 异常比例是指 1 秒内按接口调用产生异常的比例(异常调用数/总数量)触发熔断。

熔断模式-异常比例

结合上图的设置,介绍下“异常比例”熔断规则。

  • 异常数是指在 1 分钟内异常的数量超过阈值则触发熔断。

熔断模式-异常数

结合上图的设置,介绍下“异常数”熔断规则。

以上就是三种熔断模式的介绍,熔断相对流控配置比较简单,只需要设置熔断检查开启条件与触发熔断条件即可。讲到这关于限流与熔断的配置暂时告一段落,下面对本讲内容进行下总结。

小结与预告

本讲咱们介绍了三部分内容,第一部分讲解了 Sentinel Dashboard 与 Sentinel Core的通信机制与执行原理,了解 Sentinel Core 是通过拦截器实现了限流与熔断;第二部分讲解了滑动窗口算法与 Dashboard 流控配置的每一种情况;第三部分讲解了熔断机制与 Dashboard 的熔断配置。

这里留一个讨论话题:假如你遇到像春运 12306 热门车次购票这种高并发场景,为了保证应用的稳定和用户的体验,我们要采取哪些措施呢?可以把你的经验写在评论中,咱们一起探讨。

下一讲,将进入生产集群保护这个话题,看 Sentinel 是如何对整个服务集群实施保护的。


SpringCloud Alibaba实战第三课 Gateway、Sentinel实战相关推荐

  1. 量化交易 实战第三课 金融时间序列分析 Part 2

    量化交易 实战第三课 金融时间序列分析 Part 2 黄金交叉 时间序列重采样 需求 resample 函数 代码 时间窗口 需求 rolling 函数 代码 股票序列分析策略 需求 代码 回归分析 ...

  2. Docker容器化实战第三课 dockerfile介绍、容器安全与监控讲解

    06 最佳实践:如何在生产中编写最优 Dockerfile? 在介绍 Dockerfile 最佳实践前,这里再强调一下,生产实践中一定优先使用 Dockerfile 的方式构建镜像. 因为使用 Doc ...

  3. 靶机渗透测试实战(三)——Bulldog1渗透实战

    目录 一. 实验环境 二. 实验流程 三. 实验步骤 (1)信息收集--主机发现 (2)信息收集--端口扫描 (3)信息收集--操作系统扫描 (4)Web渗透 四. 实验总结 一. 实验环境 靶机:I ...

  4. 微服务实战系列之SpringCloud Alibaba学习(四)

    微服务实战系列之SpringCloud Alibaba: 微服务实战系列之SpringCloud Alibaba学习(一) 微服务实战系列之SpringCloud Alibaba学习(二) 微服务实战 ...

  5. SpringCloud Alibaba Sentinel实现熔断与限流-微服务(三十三)

    @SentinelResource 按资源名称限流+后续处理 启动Nacos成功 http://localhost:8848/nacos/#/login 启动Sentinel成功 java -jar ...

  6. SpringCloud Alibaba Sentinel实现熔断与限流-微服务(三十四)

    服务熔断功能 sentinel整合ribbon+openFeign+fallback Ribbon系列 启动nacos和sentinel 提供者9003/9004 新建cloudalibaba-pro ...

  7. SpringCloud Alibaba微服务实战(五) - Sentinel实现限流熔断

    什么是Sentinel? 请查看文章:SpringCloud Alibaba微服务实战(一) - 基础环境搭建 构建服务消费者cloud-sentinel进行服务调用 服务创建请查看文章:Spring ...

  8. SpringCloud Alibaba Sentinel 项目基础环境搭建

    本示例项目采用在maven父工程SpringCloud下面创建子module(sentinel_order_service_rest)形式. 文件清单列表: 父工程pom.xml 子工程pom.xml ...

  9. todo谷粒商城三分布式组件-SpringCloud Alibaba与SpringCloud

    简介 Spring Cloud Alibaba中文官方地址网址打不开的话可以用手机QQ浏览器. Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用 ...

  10. SpringCloud Alibaba Sentinel 分布式系统的流量防未兵

    一.服务雪崩 如果启动的下单服务不可用,就会出现线程池里所有线程都因等待响应而被阻塞,从而造成整个服务链路不可用,进而导致整个系统的服务雪崩.如图所示: 二.服务雪崩效应 服务雪崩效应:因服务提供者的 ...

最新文章

  1. Weex第一天:手势
  2. Rserve 实现java与R的互通
  3. iOS开发-ViewController的生命周期和切换
  4. DedeCms 5.7后台去除版权及去除广告的方法
  5. react-native 环境配置遇到的问题
  6. java native堆_Java Native Memory比堆更快吗?
  7. VMvare虚拟机简介、VMvare虚拟机的相关操作和管理
  8. 华为删除dhcp地址池_配置设备作为DHCP中继示例
  9. Sharepoint2013搜索学习笔记之自定义查询规则(十)
  10. 北理工远程教育在线作业统考计算机,北理工19春《操作系统》在线作业【标准答案】...
  11. 风好大,我好冷——个人分工理解
  12. 数据结构——>单向环形链表
  13. c语言程序设计何钦铭颜晖pdf,C语言程序设计(第3版)何钦铭颜晖第12章文件.pdf
  14. html——js简单计时器实现
  15. OV2640摄像头模块
  16. VCS带UPF的RTL低功耗仿真介绍
  17. ROS的激光雷达、 加速度计、 陀螺仪传感器
  18. Python网络爬虫第一课----网络爬虫之数据解析方式大全
  19. 模拟器+Appium+Python抓取App内容
  20. 冒泡排序 ← C语言实现

热门文章

  1. 软件开发的心得体会(一)
  2. 五百强各大行业简介+面试流程+tips
  3. iOS 几种打包方式
  4. pcm5102a解码芯片音质评测_音乐更重要,iQOO Pro配备独立解码芯片,Hi-Fi音质更懂你...
  5. CAD椭圆怎么画?CAD椭圆绘制步骤
  6. 查找计算机网络方面文献正确检索,文献检索第二次计算机检索实习题目(2016.4.10)...
  7. 华北黄淮江淮等地有大雾 江南华南等地有小到中雨
  8. word另存为html 图片模糊,Word中插入图片模糊、不清晰的解决方法
  9. Win11退Win10/重装Win10如何查询系统密钥
  10. 获取Linux服务器公网和内网IP地址