SpringCloud 网关组件Gateway
官网文档: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/
1. 概述
1.1 什么是网关
微服务架构里面还有一个非常重要的组件,就是网关,
在Spring Cloud 全家桶里面也有这个角色, 在 1.x 版本中 采用的是 Zuul 网关,
但是因为 zuul的升级一直跳票,一直放鸽子, Spring Cloud 在2.x 中研发了一个自己的网关 替代了 Zuul, 那就是 Gateway
网关常见的功能有路由转发、权限校验、限流控制等作用。
例如在微服务架构中, 如果可以使用网关对 请求进行转发, 前端只需访问一个地址,并携带需要调用的目标地址,由网关进行统一管理. 并且在请求过程中 对请求进行过滤,鉴权,使 微服务的 服务地址不直接暴露,保护了 微服务节点的安全
微服务架构中网关的位置:
1.2 Gateway 网关的基本特性
- Gateway 是在Spring 生态体系之上构建的 API 网关服务,由于底层使用 netty, 所以是基于 Spring5, Spring Boot2 和 Project Reactor等技术,Gateway旨在 提供一种简单而有效的方式来对 API 进行路由, 以及提供一些强大的过滤器功能,例如 熔断,限流,重试等
- Gateway作为 Spring Cloud 生态体系中的网关,目标是替代 Zuul, 在Spring Cloud 2.0 以上的版本中,没有对新版本的Zuul 2.0 以上最新高性能版本进行集成, 仍然使用老的 非 Reactor 模式的 ,
- 为了提高性能, Gateway 是基于 WebFlux框架实现的, 而WebFlux 框架底层使用了高性能的 Reactor 模式的通信框架 Netty
- Gateway 的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如 : 安全, 监控/指标, 和限流
源码架构:
什么是WebFlux
这里做基本介绍,详细请自行官网学习
- 传统的 Web框架, 比如说 spring mvc 等 都是 Servlet API 与 Servlet 容器基础上运行的
- 但是, 在 Servlet 3.1 之后, 有了异步非阻塞的支持, 而WebFlux 是一个典型的非阻塞异步的框架,它的核心是基于Reactor模式的相关API 实现的,相对于传统的web 框架来说, 它可以 运行在诸如Netty,Undertow 等支持Servlet3.1 的容器上, 非阻塞+ 函数式编程(Spring5 必须使用java8)
- Spring WebFlux 是 Spring 5.0 引入的新的响应式框架, 区别于Springmvc, 它不需要依赖 Servlet API ,他是完全异步非阻塞的,并且基于Reactor模式来实现响应式流规范
基本核心组件
Gateway 三个组件
- 路由: 网关的基本构建模块,它是由ID、目标URl、断言集合和过滤器集合定义, 如果集合断言为真,则匹配路由。
- Predicate(断言):这是java 8的一个函数式接口predicate,可以用于lambda表 达式和方法引用,输入类型是:Spring Framework ServerWebExchange,允许 开发人员匹配来自HTTP请求的任何内容,例如请求头headers和参数paramers ,如果请求与断言相匹配,则进行对应的路由
- Filter(过滤器):这些是使用特定工厂构建的Spring Framework GatewayFilter 实例,这里可以在发送请求之前或之后修改请求和响应
2. 基本使用
2.1 工程搭建及测试
需要搭建 Gateway 网关的微服务, 并注册到注册中心.
pom依赖:
<dependencies>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- <!– web Gateway不需要web依赖 –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
yml配置:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址,即路由跳转地址
predicates:
- Path=/payment/get/** #路径类型的断言,路径相匹配的则匹配路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
主启动类:
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run( GateWayMain9527.class,args);
}
}
上面的代码中, 将微服务启动, 并注册到7001
微服务,并在配置文件中, 对 路径/payment/get/**
和 /payment/lb/**
进行拦截,这就是断言,若断言为true,则匹配该路由,并跳转到对应的uri
属性下的 地址中
测试结果:
直接访问
8001
微服务的 接口http://127.0.0.1:8001/payment/lb
返回结果 : "8001" 此接口返回
8001
微服务的端口访问
9527
Gateway 微服务的 地址:http://127.0.0.1:9527/payment/lb
断言成功, 跳转路由, 返回结果: "8001", 成功调用
若访问的路径,没有任何路由匹配,则报错404:
2.2 编码方式配置路由
上面使用 yml 配置文件的方式进行 配置路由规则, 也可以使用编码的方式进行配置
下面我们使用编码的方式配置路由,跳转到百度的国内新闻
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
//路由构建器
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//配置路由规则,对比 yml 文件配置
// id: path_route , predicates: /guonei , uri: http://news.baidu.com/guonei
routes.route("path_route"
, r->r.path("/guonei").uri("http://news.baidu.com/guonei"))
.build();
return routes.build();
}
}
下面进行测试:
- 直接访问百度国内新闻
http://news.baidu.com/guonei
,成功跳转 - 通过Gateway 微服务访问
http://127.0.0.1:9527/guonei
,也可以跳转
2.3 使用微服务名跳转
上面的代码中,我们跳转到某个微服务,都是 直接写对方的ip 地址,
Gateway 会自动 从注册中心中获取服务列表, 可以通过微服务的名称作为路由转发,那么上面的代码就不用写成
http://localhost:8001
而是 lb://cloud-payment-service
lb
为负载均衡,若该微服务有两个实现,则进行负载均衡
代码演示:
首先必须先开启注册中心路由功能: spring.cloud.gateway.discovery.locator.enabled=true
pom修改:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
开启 8001
,8002
微服务,调用9527
地址: http://127.0.0.1:9527/payment/lb
,
轮流返回 "8001","8002" 对应微服务的地址,调用成功,并负载均衡
3. 断言工厂
3.1 概述
Gateway网关中 另一个非常重要的组件: 断言, 它决定一个请求是否由匹配此路由. 在前面的案例中使用的就是其中的Path 断言工厂生成的 断言类, 并匹配请求,跳转到指定路径,
GateWay给我们提供了很多不同类型的断言工厂,都是抽象类AbstractRoutePredicateFactory
的子类
详细使用,请查看官网文档 : https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories
分类:
时间相关:
AfterRoutePredicateFactory
: 匹配在指定日期时间之后发生的请求示例:
# 表示在 2017年1月20日17:42:47 之后
#此时间格式 可以使用 ZonedDateTime 类获取
#ZonedDateTime.now(); // 默认时区
#ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
BeforeRoutePredicateFactory
匹配在指定日期时间之前发生的请求BetweenRoutePredicateFactory
匹配在datetime1
之后和在datetime2
之前的请求。该datetime2
参数必须datetime1
之后示例:
#表示在2017年1月20日17:42:47之后 并且 在2017年1月21日17:42:47之前
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie相关:
CookieRoutePredicateFactory
匹配具有指定Cookie,并且值与指定的正则匹配的请求示例:
# 表示Cookie 中携带 键为chocolate,值为可以匹配正则"ch.p" 的字符串
predicates:
- Cookie=chocolate, ch.p
Header相关:
HeaderRoutePredicateFactory
, 匹配 请求头中有指定的名,并且值匹配指定的正则表达式示例:
predicates:
- Header=X-Request-Id, \d+
HostRoutePredicateFactory
, 匹配Host 域名列表示例:
# 匹配路径中host 为 *.baidu.com 的 和 *.souhu.com的
predicates:
- Host=**.baidu.com,**.sohu.com
RemoteAddrRoutePredicateFactory
,匹配请求的Remote(客户端i来源ip)示例:
# 匹配Remote 为 192.168.1.1 至 192.168.1.254
# 斜杠后面的24 表示最后一位的最大值 即254
#16 表示最后两位 即 255.254
#8 表示最后三位 即 255.255.254
predicates:
- RemoteAddr=192.168.1.1/24
请求相关:
MethodRoutePredicateFactory
匹配指定的请求方式示例:
#匹配 GET,POST 请求
predicates:
- Method=GET,POST
QueryRoutePredicateFactory
匹配请求有指定的参数key,并且值匹配指定的正则示例:
# 请求键为 red 值匹配正则 "gree."
predicates:
- Query=red, gree.
PathRoutePredicateFactory
匹配url 路径,也就是我们上面案例中用到的
3.2 断言工厂的工作原理
下面使用MethodRoutePredicateFactory
来进行演示
源码
public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory<MethodRoutePredicateFactory.Config> {
/** @deprecated */
@Deprecated
public static final String METHOD_KEY = "method";
public static final String METHODS_KEY = "methods";
public MethodRoutePredicateFactory() {
super(MethodRoutePredicateFactory.Config.class);
}
/**
* 封装是 config 类中使用哪个字段接受参数
*/
public List<String> shortcutFieldOrder() {
return Arrays.asList("methods");
}
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
/**
* 实际生产 断言操作类的方法
*/
public Predicate<ServerWebExchange> apply(MethodRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
/**
* 断言操作类的test方法,判断请求是否符合条件
*/
public boolean test(ServerWebExchange exchange) {
HttpMethod requestMethod = exchange.getRequest().getMethod();
return Arrays.stream(config.getMethods()).anyMatch((httpMethod) -> {
return httpMethod == requestMethod;
});
}
public String toString() {
return String.format("Methods: %s", Arrays.toString(config.getMethods()));
}
};
}
/**
* 配置类,接受配置文件中的配置的信息,
*/
@Validated
public static class Config {
// 一个枚举数组, 接受请求方式,例如 [GET,POST]
private HttpMethod[] methods;
public Config() {
}
/** @deprecated */
@Deprecated
public HttpMethod getMethod() {
return this.methods != null && this.methods.length > 0 ? this.methods[0] : null;
}
/** @deprecated */
@Deprecated
public void setMethod(HttpMethod method) {
this.methods = new HttpMethod[]{method};
}
public HttpMethod[] getMethods() {
return this.methods;
}
public void setMethods(HttpMethod... methods) {
this.methods = methods;
}
}
}
上面的代码中, 可以看出其实MethodRoutePredicateFactory
的实现比较简单.生产一个GatewayPredicate
进行断言.主要做了如下两个操作
- 获取配置文件中配置的参数
- 判断请求的方法是否匹配其中任意一个参数
3.3 自定义断言工厂
根据上面的规则,我们可以实现自己的自定义断言工厂
接收参数的Config 类:
public class TulingTimeBetweenConfig {
private LocalTime startTime;
private LocalTime endTime;
public LocalTime getStartTime() {
return startTime;
}
public void setStartTime(LocalTime startTime) {
this.startTime = startTime;
}
public LocalTime getEndTime() {
return endTime;
}
public void setEndTime(LocalTime endTime) {
this.endTime = endTime;
}
}
断言工厂类,注意工厂类的类名必须以"RoutePredicateFactory"开头, "RoutePredicateFactory" 之前的一部分则作为配置文件中的键
@Component
public class TulingTimeBetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<TulingTimeBetweenConfig> {
public TulingTimeBetweenRoutePredicateFactory() {
super(TulingTimeBetweenConfig.class);
}
/**
* 真正的业务判断逻辑
*/
@Override
public Predicate<ServerWebExchange> apply(TulingTimeBetweenConfig config) {
LocalTime startTime = config.getStartTime();
LocalTime endTime = config.getEndTime();
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
LocalTime now = LocalTime.now();
//判断当前时间是否在在配置的时间范围类
return now.isAfter(startTime) && now.isBefore(endTime);
}
};
}
/**
* 用于接受yml中的配置 ‐ TulingTimeBetween=上午7:00,下午11:00
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("startTime", "endTime");
}
}
yaml配置文件,使用逗号分隔
predicates:
- TulingTimeBetween=上午7:00,下午11:00
测试,当请求时间为上午七点到下午十一点前的所有请求,都会被拦截
4. 过滤器工厂
上面的操作中,我们仅仅只是将 请求拦截,并跳转到某个地址,貌似没做什么操作,作用很小,下面介绍过滤器的使用,将在拦截过程中做一些操作
,SpringCloudGateway 也提供了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等.
4.1 常用过滤器简单介绍
下面简单介绍几种常用的:
添加请求头:
给拦截到请求中加入指定的请求头和指定的值
predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ AddRequestHeader=X‐Request‐Company,tuling
添加请求参数
给请求加上指定的 Parameter 参数,和指定的值
predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ AddRequestParameter=company,tuling
为匹配的路由统一添加前缀
给请求加上指定的前缀,比如下面的配置中,请求http://localhost:8888/selectProductInfoById/1
会转发到
http://localhost:8888/product‐api/selectProductInfoById/1
中
predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ PrefixPath=/product‐api
更多详细用户请参考官网,提供了丰富的过滤器工厂
4.1 自定义过滤器工厂
过滤器工厂的实现思路和断言工厂类似,也可以参考着自定义自己的过滤器工厂,下面我们来实现一个记录整个过滤链执行时间的过滤器工厂类
过滤器操作类:
在查看源码过程中,发现其过滤器工厂返回过滤器操作类代码中,都是使用匿名内部类的方式,但是这样过滤器的执行顺序无法保证,只能按照加载顺序执行,所以这里我们将操作类单独定义,实现Ordered
接口,保证加载顺序优先
public class TimeMonitorGatewayFilter implements GatewayFilter, Ordered {
private static final String COUNT_START_TIME = "countStartTime";
private AbstractNameValueGatewayFilterFactory.NameValueConfig config;
public TimeMonitorGatewayFilter(AbstractNameValueGatewayFilterFactory.NameValueConfig config) {
this.config = config;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain
chain) {
//获取配置文件yml中的
// filters:
// ‐ TimeMonitor=enabled,true
String name = config.getName();
String value = config.getValue();
if (value.equals("false")) {
return null;
}
//在请求中记录开始时间
exchange.getAttributes().put(COUNT_START_TIME,
System.currentTimeMillis());
//then方法相当于aop的后置通知一样,当整个过滤链执行完毕时 ,将调用此方法,
return chain.filter(exchange).then(Mono.fromRunnable(new Runnable() {
@Override
public void run() {
//结束时间
Long startTime = exchange.getAttribute(COUNT_START_TIME);
//获取开始时间 并计算差值
if (startTime != null) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": ")
.append(System.currentTimeMillis() - startTime)
.append("ms");
sb.append(" params:").append(exchange.getRequest().getQueryParams());
//打印执行时间
System.out.println(sb.toString());
}
}
}));
}
/**
* 数字越小 Spring 加载此类越优先
* @return
*/
@Override
public int getOrder() {
return -100;
}
}
此类在执行链开始时执行,并记录开始时间,并定义了结束过滤链结束时,计算差值
过滤器工厂类:
和断言工厂一样, 也是使用指定的后缀,来确定配置文件中的配置方式,必须为"GatewayFilterFactory" 结尾
并继承了 AbstractNameValueGatewayFilterFactory
类, 可以接受配置文件中的 name,value 形式的参数
但是本例中只使用 value来定义
@Component
public class TimeMonitorGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new TimeMonitorGatewayFilter(config) ;
}
}
yaml配置文件:
接受到的参数 : name为enabled ,value为true,但是上面的代码中 只用到了 true参数,含义为开启此功能
predicates:
‐ Query=company,product
filters:
‐ TimeMonitor=enabled,true
测试: 调用本网关,[127.0.0.1:9527/payment/lb?name=10](http://127.0.0.1:9527/payment/lb?name=10)
打印日志信息: /payment/lb: 8ms params:{name=[10]}
4.3 自定义全局过滤器
GateWay 框架中,还有一种特殊的过滤器, 为全局过滤器,只要是被拦截的请求,都会被执行,上面的负载均衡功能就是
LoadBalancerClientFilter
全局过滤器起的作用
其他全局过滤器使用,请查看官网: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#global-filters
同样,我们也可以自定义全局过滤器:
@Component
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("进来");
//获取 url上第一个 uname param
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 过滤链的排序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
上面的过滤器中,进行了简单的鉴权操作,若请求参数中没有username,则拒绝转发,
SpringCloud 网关组件Gateway的更多相关文章
- 小D课堂 - 新版本微服务springcloud+Docker教程_6-02 springcloud网关组件zuul
笔记 2.SpringCloud的网关组件zuul基本使用 简介:讲解zuul网关基本使用 1.加入依赖 2.启动类加入注解 @EnableZuulProxy 默认集成断路器 ...
- SpringCloud系列之网关(Gateway)应用篇
@ 目录 前言 项目版本 网关访问 鉴权配置 限流配置 前言 由于项目采用了微服务架构,业务功能都在相应各自的模块中,每个业务模块都是以独立的项目运行着,对外提供各自的服务接口,如没有类似网关之类组件 ...
- SpringCloud Alibaba(三) - GateWay网关
1.基本环境搭建 1.1 依赖 <!-- Gatway 网关会和springMvc冲突,不能添加web依赖 --> <dependency> <groupId>or ...
- 微服务架构案例(05):SpringCloud 基础组件应用设计
本文源码:GitHub·点这里 || GitEE·点这里 更新进度(共6节): 01:项目技术选型简介,架构图解说明 02:业务架构设计,系统分层管理 03:数据库选型,业务数据设计规划 04:中间件 ...
- Java生鲜电商平台-深入理解微服务SpringCloud各个组件的关联与架构
Java生鲜电商平台-深入理解微服务SpringCloud各个组件的关联与架构 概述 毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术.不过大多数讲解还停留 ...
- SpringCloud及其组件详解
SpringCloud及其组件详解 1.Spring Cloud 1.1 Spring Cloud和Dubbo的区别图解 1.2 微服务的技术栈 2.Spring Cloud 概述 2.1 Sprin ...
- 如何设计一个亿级网关(API Gateway)?
1.背景 1.1 什么是API网关 API网关可以看做系统与外界联通的入口,我们可以在网关进行处理一些非业务逻辑的逻辑,比如权限验证,监控,缓存,请求路由等等. 1.2 为什么需要API网关 RPC协 ...
- 高质量API网关组件实现
PI网关组件的作用? 1.网关直接代替MVC当中的Controller层,减少编码量提高开发效率 2.统一API接口的出入参格式,提高API的友好性 3.自动检测API接口规范,提高接口的质量 4.统 ...
- 包容网关 Inclusive Gateway
包容网关 Inclusive Gateway 作者:Jesai 2018年3月25日 22:59:56 什么是包容网关? 包容网关(Inclusive Gateway)就是并行网关(Parallel ...
- 并行网关 Parallel Gateway
并行网关 Parallel Gateway 作者:Jesai 2018年3月25日 00:26:21 前言: 做工作流时间长后,慢慢的就会发现,很多客户会需要会签的功能,会签的情况也有很多种,实现的方 ...
随机推荐
- 你不知道的Promise构造函数Promise(excutor)
Promise构造函数Promise(excutor) // 说明一下:excutor会在Promise内部立刻同步调用:(异步操作在执行器执行) var p = new Promise((resol ...
- ngnix在linux安装并设置反向代理
一.nginx安装 1.安装Nginx依赖的环境 安装Nginx依赖的gcc的编译环境: yum install gcc-c++ Nginx的http模块需要使用pcre来解析正则表达式,需要安装pc ...
- Dubbo3应用开发—Dubbo注册中心引言
Dubbo注册中心引言 什么是Dubbo注册中心 Dubbo的注册中心,是Dubbo服务治理的⼀个重要的概念,他主要用于 RPC服务集群实例的管理. 注册中心的运行流程 使用注册中心的好处 可以有效的 ...
- .NET 云原生架构师训练营(RGCA 四步架构法)--学习笔记
RGCA Requirement:从利益相关者获取需求 Goal:将需求转化为目标(功能意图) Concept:将目标扩展为完整概念 Architecture:将概念扩展为架构 目录 从利益相关者获取 ...
- OGG-将PostgreSQL通过OGG_BigData同步到Kafka后数据存在8小时时间差
问题描述: 将PostgreSQL通过OGG_BigData同步到Kafka后数据存在8小时时间差. 问题原因: kafka.properties中的参数goldengate.userexit.tim ...
- MySQL 8 查询优化新工具 Explain Analyze
1. Explain Analyze 介绍 Explain 是我们常用的查询分析工具,可以对查询语句的执行方式进行评估,给出很多有用的线索.但他仅仅是评估,不是实际的执行情况,比如结果中的 rows, ...
- 什么是TDD(一)
引子 回顾 虽然我很早以前就听说单元测试,也曾经多次在项目中引入单元测试框架和单元测试的实践为代码质量的提升带来了一丝助力. 但这种方式更多的是从软件调试的角度出发,即将单元测试作为一种测试方法可用性 ...
- ES6学习 第六章 数值的扩展
前言 本章介绍数值的扩展.新增了很多方法,有些不常用的方法了解即可. 本章原文链接:数值的扩展 进制表示法 ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示. ...
- NC50390 布局 Layout
题目链接 题目 题目描述 FJ有N头奶牛 \((2 \leq N \leq1000)\) ,编号为 \(1 \ldots N\) .奶牛们将按照编号顺序排成一列队伍(可能有多头奶牛在同一位置上).换句 ...
- Libata Error Message 解析
Libata error messages Contents [hide] 1 Overview 2 Prefix 3 Exception line 4 Input taskfile 5 O ...