文章很长,建议收藏起来,慢慢读! 备注:持续更新中…
推荐1:进大厂、升架构、拿高薪 必备 的 经典图书和资料:
高薪必备1 : 《Netty Zookeeper Redis 高并发实战》 为你打造NIO、Netty 高性能底层原理知识底座
高薪必备2 : 《SpringCloud、Nginx高并发核心编程》 为你打造微服务、分布式 高并发底层原理知识底座
高薪必备3 :价值 1000元网盘资源大礼包 ,免费拿 【博客园总入口 】
推荐2: 地表最强 开发环境 系列
工欲善其事 必先利其器
地表最强 开发环境: vagrant+java+springcloud+redis+zookeeper镜像下载(&制作详解)
地表最强 热部署:java SpringBoot SpringCloud 热部署 热加载 热调试
地表最强 发请求工具(再见吧, PostMan ):IDEA HTTP Client(史上最全)
地表最强 PPT 小工具: 屌炸天,像写代码一样写PPT
无编程不创客,无编程不创客,一大波编程高手正在疯狂创客圈交流、学习中! 找组织,GO
推荐3: springCloud 微服务 系列
推荐阅读
nacos 实战(史上最全)
sentinel (史上最全+入门教程)
springcloud + webflux 高并发实战
Webflux(史上最全)
SpringCloud gateway (史上最全)
无编程不创客,无编程不创客,一大波编程高手正在疯狂创客圈交流、学习中! 找组织,GO
sentinel 基本概念
开发的原因,需要对吞吐量(TPS)、QPS、并发数、响应时间(RT)几个概念做下了解,查自百度百科,记录如下:
响应时间(RT) 响应时间是指系统对请求作出响应的时间。直观上看,这个指标与人对软件性能的主观感受是非常一致的,因为它完整地记录了整个计算机系统处理请求的时间。由于一个系统通常会提供许多功能,而不同功能的处理逻辑也千差万别,因而不同功能的响应时间也不尽相同,甚至同一功能在不同输入数据的情况下响应时间也不相同。所以,在讨论一个系统的响应时间时,人们通常是指该系统所有功能的平均时间或者所有功能的最大响应时间。当然,往往也需要对每个或每组功能讨论其平均响应时间和最大响应时间。 对于单机的没有并发操作的应用系统而言,人们普遍认为响应时间是一个合理且准确的性能指标。需要指出的是,响应时间的绝对值并不能直接反映软件的性能的高低,软件性能的高低实际上取决于用户对该响应时间的接受程度。对于一个游戏软件来说,响应时间小于100毫秒应该是不错的,响应时间在1秒左右可能属于勉强可以接受,如果响应时间达到3秒就完全难以接受了。而对于编译系统来说,完整编译一个较大规模软件的源代码可能需要几十分钟甚至更长时间,但这些响应时间对于用户来说都是可以接受的。
吞吐量(Throughput) 吞吐量是指系统在单位时间内处理请求的数量。对于无并发的应用系统而言,吞吐量与响应时间成严格的反比关系,实际上此时吞吐量就是响应时间的倒数。前面已经说过,对于单用户的系统,响应时间(或者系统响应时间和应用延迟时间)可以很好地度量系统的性能,但对于并发系统,通常需要用吞吐量作为性能指标。 对于一个多用户的系统,如果只有一个用户使用时系统的平均响应时间是t,当有你n个用户使用时,每个用户看到的响应时间通常并不是n×t,而往往比n×t小很多(当然,在某些特殊情况下也可能比n×t大,甚至大很多)。这是因为处理每个请求需要用到很多资源,由于每个请求的处理过程中有许多不走难以并发执行,这导致在具体的一个时间点,所占资源往往并不多。也就是说在处理单个请求时,在每个时间点都可能有许多资源被闲置,当处理多个请求时,如果资源配置合理,每个用户看到的平均响应时间并不随用户数的增加而线性增加。实际上,不同系统的平均响应时间随用户数增加而增长的速度也不大相同,这也是采用吞吐量来度量并发系统的性能的主要原因。一般而言,吞吐量是一个比较通用的指标,两个具有不同用户数和用户使用模式的系统,如果其最大吞吐量基本一致,则可以判断两个系统的处理能力基本一致。
并发用户数 并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量。与吞吐量相比,并发用户数是一个更直观但也更笼统的性能指标。实际上,并发用户数是一个非常不准确的指标,因为用户不同的使用模式会导致不同用户在单位时间发出不同数量的请求。一网站系统为例,假设用户只有注册后才能使用,但注册用户并不是每时每刻都在使用该网站,因此具体一个时刻只有部分注册用户同时在线,在线用户就在浏览网站时会花很多时间阅读网站上的信息,因而具体一个时刻只有部分在线用户同时向系统发出请求。这样,对于网站系统我们会有三个关于用户数的统计数字:注册用户数、在线用户数和同时发请求用户数。由于注册用户可能长时间不登陆网站,使用注册用户数作为性能指标会造成很大的误差。而在线用户数和同事发请求用户数都可以作为性能指标。相比而言,以在线用户作为性能指标更直观些,而以同时发请求用户数作为性能指标更准确些。
QPS每秒查询率(Query Per Second) 每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 (看来是类似于TPS,只是应用于特定场景的吞吐量)
1、什么是Sentinel:
Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。 官网:https://github.com/alibaba/Sentinel/wiki
2012年,Sentinel诞生于阿里巴巴,其主要目标是流量控制。2013-2017年,Sentinel迅速发展,并成为阿里巴巴所有微服务的基本组成部分。 它已在6000多个应用程序中使用,涵盖了几乎所有核心电子商务场景。2018年,Sentinel演变为一个开源项目。2020年,Sentinel Golang发布。
Sentinel 具有以下特征:
丰富的应用场景 :Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。 完备的实时监控 :Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机 器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 广泛的开源生态 :Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
完善的 SPI 扩展点 :Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快 速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel的生态圈
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vmB72nnn-1611672626743)(https://pics1.baidu.com/feed/500fd9f9d72a6059c55d49a66cf6bb9d023bba02.png?token=14715c498bc885222d716066ff4a15c0)]
Sentinel主要特性:
关于Sentinel与Hystrix的区别见:https://yq.aliyun.com/articles/633786/
到这已经学习Sentinel的基本的使用,在很多的特性和Hystrix有很多类似的功能。以下是Sentinel和Hystrix的对比。
Sentinel 的使用
Sentinel 的使用可以分为两个部分:
在这里我们看下控制台的使用
Sentinel中的管理控制台
2.1 获取 Sentinel 控制台
您可以从 release 页面 下载最新版本的控制台 jar 包。
您也可以从最新版本的源码自行构建 Sentinel 控制台:
下载 控制台 工程
使用以下命令将代码打包成一个 fat jar: mvn clean package
2.2 sentinel服务启动
java -server -Xms64m -Xmx256m -Dserver.port=8849 -Dcsp.sentinel.dashboard.server=localhost:8849 -Dproject.name=sentinel-dashboard -jar /work/sentinel-dashboard-1.7.1.jar
开机启动:启动命令可以加入到启动的 rc.local 配置文件, 之后做到开机启动
#启动 sentinel
/usr/bin/su - root -c “nohup java -server -Xms64m -Xmx256m -Dserver.port=8849 -Dcsp.sentinel.dashboard.server=localhost:8849 -Dproject.name=sentinel-dashboard -jar /work/sentinel-dashboard-1.7.1.jar 2>&1 &”
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。
由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
关于熔断降级的介绍见:Sentinel熔断降级。
下面就使用基于注解的方式实现Sentinel的熔断降级的demo。
注意 :启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
使用如下命令启动控制台:
nohup java -server -Xms64m -Xmx256m -Dserver.port=8849 -Dcsp.sentinel.dashboard.server=localhost:8849 -Dproject.name=sentinel-dashboard -jar /work/sentinel-dashboard-1.7.1.jar &
其中 -Dserver.port=8849用于指定 Sentinel 控制台端口为 8849 , 这个端口可以按需指定。
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录 功能,默认用户名和密码都是 sentinel
。可以参考 鉴权模块文档 配置用户名和密码。
注:若您的应用为 Spring Boot 或 Spring Cloud 应用,您可以通过 Spring 配置文件来指定配置,详情请参考 Spring Cloud Alibaba Sentinel 文档。(1)获取 Sentinel 控制台 您可以从官方 网站中 下载最新版本的控制台 jar 包,下载地址如下:
https://github.com/alibaba/Sentinel/releases/download/1.6.3/sentinel-dashboard-1.7.1.jar
(2)启动 使用如下命令启动控制台: 其中 - Dserver.port=8888 用于指定 Sentinel 控制台端口为 8888 。 从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel 。可以参考 鉴权模块文档 配置用户名和密码。
[root@192 ~]# java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.6.3.jar
INFO: log base dir is: /root/logs/csp/
INFO: log name use pid is: false
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.5.RELEASE)
2020-02-08 13:07:29.316 INFO 114031 --- [ main] c.a.c.s.dashboard.DashboardApplication : Starting DashboardApplication on 192.168.180.137 with PID 114031 (/root/sentinel-dashboard-1.6.3.jar started by root in /root)
2020-02-08 13:07:29.319 INFO 114031 --- [ main] c.a.c.s.dashboard.DashboardApplication : No active profile set, falling back to default profiles: default
2020-02-08 13:07:29.456 INFO 114031 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@59690aa4: startup date [Sat Feb 08 13:07:29 CST 2020]; root of context hierarchy
2020-02-08 13:07:33.783 INFO 114031 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8888 (http)
启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
查看机器列表以及健康情况 默认情况下Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。也可以配置 sentinel.eager=true ,取消Sentinel控制台懒加载。 打开浏览器即可展示Sentinel的管理控制台
客户端能接入控制台
控制台启动后,客户端需要按照以下步骤接入到控制台。
父工程引入 alibaba实现的SpringCloud
org.springframework.cloud
spring-cloud-dependencies
Greenwich.RELEASE
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.1.0.RELEASE
pom
import
子工程中引入 sentinel
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
( 2)配置启动参数 在工程的application.yml中添加Sentinel 控制台配置信息
spring:
cloud:
sentinel:
transport:
dashboard: 192.168.180.137:8888 #sentinel控制台的请求地址
这里的 spring.cloud.sentinel.transport.dashboard 配置控制台的请求路径。
Sentinel与Hystrix的区别
迁移方案 Sentinel 官方提供了详细的由Hystrix 迁移到Sentinel 的方法
2 使用 Sentinel 来进行熔断与限流
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。 使用 Sentinel 来进行熔断保护,主要分为几个步骤:
定义资源
资源:可以是任何东西,一个服务,服务里的方法,甚至是一段代码。
定义规则
规则:Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规则。
检验规则是否生效
Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效. 先把可能需要保护的资源定义好,之后再配置规则。
也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保 护,就将之定义为一个资源。
1. 定义资源
资源 是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,RPC接口方法,甚至可以是一段代码。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
把需要控制流量的代码用 Sentinel的关键代码 SphU.entry(“资源名”) 和 entry.exit() 包围起来即可。
实例代码:
Entry entry = null;
try {
// 定义一个sentinel保护的资源,名称为test-sentinel-api
entry = SphU.entry(resourceName);
// 模拟执行被保护的业务逻辑耗时
Thread.sleep(100);
return a;
} catch (BlockException e) {
// 如果被保护的资源被限流或者降级了,就会抛出BlockException
log.warn("资源被限流或降级了", e);
return "资源被限流或降级了";
} catch (InterruptedException e) {
return "发生InterruptedException";
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
在下面的例子中, 用 try-with-resources 来定义资源。参考代码如下:
public static void main ( String[ ] args) {
initFlowRules ( ) ;
while ( true ) {
try ( Entry entry = SphU. entry ( "HelloWorld" ) ) {
System. out. println ( "hello world" ) ;
} catch ( BlockException ex) {
System. out. println ( "blocked!" ) ;
}
}
}
资源注解@SentinelResource
也可以使用Sentinel提供的注解@SentinelResource来定义资源,实例如下:
@SentinelResource ( "HelloWorld" )
public void helloWorld ( ) {
System. out. println ( "hello world" ) ;
}
@SentinelResource 注解
注意:注解方式埋点不支持 private 方法。
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource
注解包含以下属性:
value:资源名称,必需项(不能为空)
entryType:entry 类型,可选项(默认为 EntryType.OUT)
blockHandler / blockHandlerClass:
blockHandler 对应处理 BlockException的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
fallback /fallbackClass
:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。
defaultFallback
(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。
fallback 函数签名和位置要求:
defaultFallback 函数签名要求:
返回值类型必须与原函数返回值类型一致;
方法参数列表需要为空,或者可以额外多一个 Throwable
类型的参数用于接收对应的异常。
defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
2. 定义规则
规则主要有流控规则、 熔断降级规则、系统规则、权限规则、热点参数规则等:
一段硬编码的方式定义流量控制规则如下:
private void initSystemRule() {
List rules = new ArrayList<>();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(10);
rules.add(rule);
SystemRuleManager.loadRules(rules);
}
加载规则:
FlowRuleManager. loadRules ( List< FlowRule> rules) ;
DegradeRuleManager. loadRules ( List< DegradeRule> rules) ;
SystemRuleManager. loadRules ( List< SystemRule> rules) ;
AuthorityRuleManager. loadRules ( List< AuthorityRule> rules) ;
3 sentinel 熔断降级
1 什么是熔断降级
熔断降级对调用链路中不稳定的资源进行熔断降级是保障高可用的重要措施之一。
由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)
2. 熔断降级规则
熔断降级规则包含下面几个重要的属性:
Field
说明
默认值
resource
资源名,即规则的作用对象
grade
熔断策略,支持慢调用比例/异常比例/异常数策略
慢调用比例
count
慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow
熔断时长,单位为 s
minRequestAmount
熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)
5
statIntervalMs
统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)
1000 ms
slowRatioThreshold
慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
3 几种降级策略
我们通常用以下几种降级策略:
平均响应时间 (DEGRADE_GRADE_RT):
当资源的平均响应时间超过阈值(DegradeRule 中的 count,以 ms 为单位)之后,资源进入准降级状态。如果接下来 1s 内持续进入 5 个请求(即 QPS >= 5),它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。
注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):
当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。
异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。
注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
4 熔断降级代码实现
可以通过调用 DegradeRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。
@PostConstruct
public void initSentinelRule ( )
{
List< DegradeRule> degradeRules = new ArrayList < > ( ) ;
DegradeRule rule = new DegradeRule ( ) ;
rule. setResource ( "queryGoodsInfo" ) ;
rule. setCount ( 5 ) ;
rule. setGrade ( RuleConstant. DEGRADE_GRADE_EXCEPTION_COUNT ) ;
rule. setTimeWindow ( 5 ) ;
degradeRules. add ( rule) ;
DegradeRuleManager. loadRules ( degradeRules) ;
}
具体源码,请参见疯狂创客圈crazy-springcloud 源码工程
5 控制台降级规则
配置
参数
Field
说明
默认值
resource
资源名,即限流规则的作用对象
count
阈值
grade
降级模式,根据 RT 降级还是根据异常比例降级
RT
timeWindow
降级的时间,单位为 s
6 与Hystrix的熔断对比:
Hystrix常用的线程池隔离会造成线程上下切换的overhead比较大;Hystrix使用的信号量隔离对某个资源调用的并发数进行控制,效果不错,但是无法对慢调用进行自动降级;
Sentinel通过并发线程数的流量控制提供信号量隔离的功能;此外,Sentinel支持的熔断降级维度更多,可对多种指标进行流控、熔断,且提供了实时监控和控制面板,功能更为强大。
4 Sentinel 流控(限流)
流量控制(Flow Control),原理是监控应用流量的QPS或并发线程数等指标,当达到指定阈值时对流量进行控制,避免系统被瞬时的流量高峰冲垮,保障应用高可用性。
通过流控规则来指定允许该资源通过的请求次数,例如下面的代码定义了资源 HelloWorld 每秒最多只能通过 20 个请求。 参考的规则定义如下:
private static void initFlowRules ( ) {
List< FlowRule> rules = new ArrayList < > ( ) ;
FlowRule rule = new FlowRule ( ) ;
rule. setResource ( "HelloWorld" ) ;
rule. setGrade ( RuleConstant. FLOW_GRADE_QPS ) ;
rule. setCount ( 20 ) ;
rules. add ( rule) ;
FlowRuleManager. loadRules ( rules) ;
}
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
resource
:资源名,即限流规则的作用对象
count
: 限流阈值
grade
: 限流阈值类型(QPS 或并发线程数)
limitApp
: 流控针对的调用来源,若为 default
则不区分调用来源
strategy
: 调用关系限流策略
controlBehavior
: 流量控制效果(直接拒绝、Warm Up、匀速排队)
基本的参数
资源名 :唯一名称,默认请求路径
针对来源 :Sentinel可以针对调用者进行限流,填写微服务名,默认为default(不区分来源)
阈值类型/单机阈值:
1.QPS:每秒请求数,当前调用该api的QPS到达阈值的时候进行限流
2.线程数:当调用该api的线程数到达阈值的时候,进行限流
是否集群 :是否为集群
流控的几种strategy
:
1.直接:当api大达到限流条件时,直接限流
2.关联:当关联的资源到达阈值,就限流自己
3.链路:只记录指定路上的流量,指定资源从入口资源进来的流量,如果达到阈值,就进行限流,api级别的限流
4.1 直接失败模式
使用API进行资源定义
/**
* 限流实现方式一: 抛出异常的方式定义资源
*
* @param orderId
* @return
*/
@ApiOperation(value = "纯代码限流")
@GetMapping("/getOrder")
@ResponseBody
public String getOrder(@RequestParam(value = "orderId", required = false)String orderId)
{
Entry entry = null;
// 资源名
String resourceName = "getOrder";
try
{
// entry可以理解成入口登记
entry = SphU.entry(resourceName);
// 被保护的逻辑, 这里为订单查询接口
return "正常的业务逻辑 OrderInfo :" + orderId;
} catch (BlockException blockException)
{
// 接口被限流的时候, 会进入到这里
log.warn("---getOrder1接口被限流了---, exception: ", blockException);
return "接口限流, 返回空";
} finally
{
// SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
if (entry != null)
{
entry.exit();
}
}
}
代码限流规则
//限流规则 QPS mode,
List rules = new ArrayList();
FlowRule rule1 = new FlowRule();
rule1.setResource("getOrder");
// QPS控制在2以内
rule1.setCount(2);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
网页限流规则配置
选择QPS,直接,快速失败,单机阈值为2。
配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GE6VXcNe-1611672626765)( ]
参数
Field
说明
默认值
resource
资源名,资源名是限流规则的作用对象
count
限流阈值
grade
限流阈值类型,QPS 或线程数模式
QPS 模式
limitApp
流控针对的调用来源
default
,代表不区分调用来源
strategy
判断的根据是资源自身,还是根据其它关联资源 (refResource
),还是根据链路入口
根据资源本身
controlBehavior
流控效果(直接拒绝 / 排队等待 / 慢启动模式)
直接拒绝
测试
频繁刷新请求,1秒访问2次请求,正常,超过设置的阈值,将报默认的错误。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k3sDcsRC-1611672626766)(https://pics4.baidu.com/feed/d6ca7bcb0a46f21f736fcc3eb7e6e4660d33ae49.png?token=21c375e0121e5fac15a82a07284c66aa)]
再次的1秒访问2次请求,访问正常。超过2次,访问异常
4.2 关联模式
调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系。Sentinel 通过 NodeSelectorSlot
建立不同资源间的调用的关系,并且通过 ClusterBuilderSlot
记录每个资源的实时统计信息。
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。
比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢.
举例来说,read_db
和 write_db
这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写优先的目的。具体的方法:
设置 `strategy` 为 `RuleConstant.STRATEGY_RELATE`
设置 `refResource` 为 `write_db`。
这样当写库操作过于频繁时,读数据的请求会被限流。
还有一个例子,电商的 下订单 和 支付两个操作,需要优先保障 支付, 可以根据 支付接口的 流量阈值,来对订单接口进行限制,从而保护支付的目的。
使用注解进行资源定义
添加2个请求
@SentinelResource(value = "test1", blockHandler = "exceptionHandler")
@GetMapping("/test1")
public String test1()
{
log.info(Thread.currentThread().getName() + "\t" + "...test1");
return "-------hello baby,i am test1";
}
// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler(BlockException ex)
{
// Do some log here.
ex.printStackTrace();
log.info(Thread.currentThread().getName() + "\t" + "...exceptionHandler");
return String.format("error: test1 is not OK");
}
@SentinelResource(value = "test1_ref")
@GetMapping("/test1_ref")
public String test1_ref()
{
log.info(Thread.currentThread().getName() + "\t" + "...test1_related");
return "-------hello baby,i am test1_ref";
}
代码配置关联限流规则
// 关联模式流控 QPS控制在1以内
String refResource = "test1_ref";
FlowRule rRule = new FlowRule("test1")
.setCount(1) // QPS控制在1以内
.setStrategy(RuleConstant.STRATEGY_RELATE)
.setRefResource(refResource);
rules.add(rRule);
FlowRuleManager.loadRules(rules);
网页限流规则配置
测试
选择QPS,单机阈值为1,选择关联,关联资源为/test_ref,这里用Jmeter模拟高并发,请求/test_ref。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AI70aONS-1611672626768)(https://pics5.baidu.com/feed/a6efce1b9d16fdfa1299f057f44d035296ee7bde.png?token=ced899c66307cdd18da13c06403df487)]
在大批量线程高并发访问/test_ref,导致/test失效了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wcHfFhot-1611672626769)(https://pics4.baidu.com/feed/d6ca7bcb0a46f21f736fcc3eb7e6e4660d33ae49.png?token=21c375e0121e5fac15a82a07284c66aa)]
链路类型的关联也类似,就不再演示了。多个请求调用同一微服务。
4.3 Warm up(预热)模式
当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。
默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
使用注解定义资源
@SentinelResource(value = "testWarmUP", blockHandler = "exceptionHandlerOfWarmUp")
@GetMapping("/testWarmUP")
public String testWarmUP()
{
log.info(Thread.currentThread().getName() + "\t" + "...test1");
return "-------hello baby,i am testWarmUP";
}
代码限流规则
FlowRule warmUPRule = new FlowRule();
warmUPRule.setResource("testWarmUP");
warmUPRule.setCount(20);
warmUPRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
warmUPRule.setLimitApp("default");
warmUPRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
warmUPRule.setWarmUpPeriodSec(10);
网页限流规则配置
先在单机阈值10/3,3的时候,预热10秒后,慢慢将阈值升至20。刚开始刷/testWarmUP,会出现默认错误,预热时间到了后,阈值增加,没超过阈值刷新,请求正常。
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qk8n76Hw-1611672626770)(https://pics7.baidu.com/feed/9922720e0cf3d7ca95ce8b6db1dd310f6b63a93e.png?token=9a8b021a70b874e2d690ef0a1ab04ab0)]
如秒杀系统在开启瞬间,会有很多流量上来,很可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
通过jmeter进行测试
4.4 排队等待模式
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。阈值必须设置为QPS。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0XsRhvQF-1611672626771)(https://pics2.baidu.com/feed/42166d224f4a20a4de73da50d0901724730ed005.png?token=d2a53aac9415b83e46126f0781ae8e4c)]
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
某瞬时来了大流量的请求, 而如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力。Sentinel的Rate Limiter模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性.
Sentinel会以固定的间隔时间让请求通过, 访问资源。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过;反之,则马上抛出阻塞异常。
使用Sentinel的这种策略, 简单点说, 就是使用一个时间段(比如20s的时间)处理某一瞬时产生的大量请求, 起到一个削峰填谷的作用, 从而充分利用系统的处理能力, 下图能很形象的展示这种场景: X轴代表时间, Y轴代表系统处理的请求.
示例
模拟2个用户同时并发的访问资源,发出100个请求,
如果设置QPS阈值为1, 拒绝策略修改为Rate Limiter匀速RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER方式, 还需要设置setMaxQueueingTimeMs(20 * 1000)表示每一请求最长等待时间, 这里等待时间大一点, 以保证让所有请求都能正常通过;
假设这里设置的排队等待时间过小的话, 导致排队等待的请求超时而抛出异常BlockException, 最终结果可能是这100个并发请求中只有一个请求或几个才能正常通过, 所以使用这种模式得根据访问资源的耗时时间决定排队等待时间. 按照目前这种设置, QPS阈值为10的话, 每一个请求相当于是以匀速100ms左右通过.
使用注解定义资源
@SentinelResource(value = "testLineUp",
blockHandler = "exceptionHandlerOftestLineUp")
@GetMapping("/testLineUp")
public String testLineUp()
{
log.info(Thread.currentThread().getName() + "\t" + "...test1");
return "-------hello baby,i am testLineUp";
}
代码限流规则
FlowRule lineUpRule = new FlowRule();
lineUpRule.setResource("testLineUp");
lineUpRule.setCount(10);
lineUpRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
lineUpRule.setLimitApp("default");
lineUpRule.setMaxQueueingTimeMs(20 * 1000);
// CONTROL_BEHAVIOR_DEFAULT means requests more than threshold will be rejected immediately.
// CONTROL_BEHAVIOR_DEFAULT将超过阈值的流量立即拒绝掉.
lineUpRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
rules.add(lineUpRule);
网页限流规则配置
通过jmeter进行测试
4.5 热点规则 (ParamFlowRule)
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。 使用该规则需要引入依赖:
热点参数规则(ParamFlowRule
)类似于流量控制规则(FlowRule
):
属性
说明
默认值
resource
资源名,必填
count
限流阈值,必填
grade
限流模式
QPS 模式
durationInSec
统计窗口时间长度(单位为秒),1.6.0 版本开始支持
1s
controlBehavior
流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持
快速失败
maxQueueingTimeMs
最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持
0ms
paramIdx
热点参数的索引,必填,对应 SphU.entry(xxx, args)
中的参数索引位置
paramFlowItemList
参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count
阈值的限制。仅支持基本类型和字符串类型
clusterMode
是否是集群参数流控规则
false
clusterConfig
集群流控相关配置
自定义资源
@GetMapping("/byHotKey")
@SentinelResource(value = "byHotKey",
blockHandler = "userAccessError")
public String test4(@RequestParam(value = "userId", required = false) String userId,
@RequestParam(value = "goodId", required = false) int goodId)
{
log.info(Thread.currentThread().getName() + "\t" + "...byHotKey");
return "-----------by HotKey: UserId";
}
限流规则代码:
可以通过 ParamFlowRuleManager 的 loadRules 方法更新热点参数规则,下面是官方实例:
ParamFlowRule rule = new ParamFlowRule ( resourceName)
. setParamIdx ( 0 )
. setCount ( 5 ) ;
ParamFlowItem item = new ParamFlowItem ( ) . setObject ( String. valueOf ( PARAM_B ) )
. setClassType ( int. class . getName ( ) )
. setCount ( 10 ) ;
rule. setParamFlowItemList ( Collections. singletonList ( item) ) ;
ParamFlowRuleManager. loadRules ( Collections. singletonList ( rule) ) ;
具体的限流代码如下:
ParamFlowRule pRule = new ParamFlowRule("byHotKey")
.setParamIdx(1)
.setCount(1);
// 针对 参数值1000,单独设置限流 QPS 阈值为 5,而不是全局的阈值 1.
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(1000))
.setClassType(int.class.getName())
.setCount(5);
pRule.setParamFlowItemList(Collections.singletonList(item));
ParamFlowRuleManager.loadRules(Collections.singletonList(pRule));
网页限流规则配置
测试:
请参见视频
5. Sentinel 系统保护
系统保护的目的
在开始之前,我们先了解一下系统保护的目的:
保证系统不被拖垮
在系统稳定的前提下,保持系统的吞吐量
长期以来,系统保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:
load 是一个“结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。
恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。
系统保护的目标是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值 。如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。
Sentinel 在系统自适应保护的做法是,用 load1 作为启动自适应保护的因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。
系统保护规则的应用
系统规则支持以下的模式:
Load 自适应 (仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt
估算得出。设定参考值一般是 CPU cores * 2.5
。
CPU usage (1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
平均 RT :当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数 :当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS :当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效 。入口流量指的是进入应用的流量(EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则的参数说明:
highestSystemLoad 最大的 load1,参考值 -1 (不生效)
avgRt 所有入口流量的平均响应时间 -1 (不生效)
maxThread 入口流量的最大并发数 -1 (不生效)
qps 所有入口资源的 QPS -1 (不生效)
硬编码的方式定义流量控制规则如下:
List< SystemRule> srules = new ArrayList < > ( ) ;
SystemRule srule = new SystemRule ( ) ;
srule. setAvgRt ( 3000 ) ;
srules. add ( srule) ;
SystemRuleManager. loadRules ( srules) ;
网页限流规则配置
6 黑白名单规则
很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
调用方信息通过 ContextUtil.enter(resourceName, origin) 方法中的 origin 参数传入。
访问控制规则 (AuthorityRule)
授权规则,即黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:
resource:资源名,即限流规则的作用对象
limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式 比如我们希望控制对资源 test 的访问设置白名单,只有来源为 appA 和 appB 的请求才可通过,则可以配置如下白名单规则:
AuthorityRule rule = new AuthorityRule ( ) ;
rule. setResource ( "test" ) ;
rule. setStrategy ( RuleConstant. AUTHORITY_WHITE ) ;
rule. setLimitApp ( "appA,appB" ) ;
AuthorityRuleManager. loadRules ( Collections. singletonList ( rule) ) ;
7 核心组件
Resource
resource是sentinel中最重要的一个概念,sentinel通过资源来保护具体的业务代码或其他后方服务。sentinel把复杂的逻辑给屏蔽掉了,用户只需要为受保护的代码或服务定义一个资源,然后定义规则就可以了,剩下的通通交给sentinel来处理了。并且资源和规则是解耦的,规则甚至可以在运行时动态修改。定义完资源后,就可以通过在程序中埋点来保护你自己的服务了,埋点的方式有两种:
try-catch 方式(通过 SphU.entry(...)
),当 catch 到BlockException时执行异常处理(或fallback)
if-else 方式(通过 SphO.entry(...)
),当返回 false 时执行异常处理(或fallback)
以上这两种方式都是通过硬编码的形式定义资源然后进行资源埋点的,对业务代码的侵入太大,从0.1.1版本开始,sentinel加入了注解的支持,可以通过注解来定义资源,具体的注解为:SentinelResource 。通过注解除了可以定义资源外,还可以指定 blockHandler 和 fallback 方法。
在sentinel中具体表示资源的类是:ResourceWrapper ,他是一个抽象的包装类,包装了资源的 Name 和EntryType。他有两个实现类,分别是:StringResourceWrapper 和 MethodResourceWrapper。顾名思义,StringResourceWrapper 是通过对一串字符串进行包装,是一个通用的资源包装类,MethodResourceWrapper 是对方法调用的包装。
Context
Context是对资源操作时的上下文环境,每个资源操作(针对Resource进行的entry/exit
)必须属于一个Context,如果程序中未指定Context,会创建name为"sentinel_default_context"的默认Context。一个Context生命周期内可能有多个资源操作,Context生命周期内的最后一个资源exit时会清理该Context,这也预示这整个Context生命周期的结束。Context主要属性如下:
public class Context {
// context名字,默认名字 "sentinel_default_context"
private final String name;
// context入口节点,每个context必须有一个entranceNode
private DefaultNode entranceNode;
// context当前entry,Context生命周期中可能有多个Entry,所有curEntry会有变化
private Entry curEntry;
// The origin of this context (usually indicate different invokers, e.g. service consumer name or origin IP).
private String origin = "";
private final boolean async;
}
注意:一个Context生命期内Context只能初始化一次,因为是存到ThreadLocal中,并且只有在非null时才会进行初始化。
如果想在调用 SphU.entry() 或 SphO.entry() 前,自定义一个context,则通过ContextUtil.enter()方法来创建。context是保存在ThreadLocal中的,每次执行的时候会优先到ThreadLocal中获取,为null时会调用 MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType())
创建一个context。当Entry执行exit方法时,如果entry的parent节点为null,表示是当前Context中最外层的Entry了,此时将ThreadLocal中的context清空。
Context的创建与销毁
首先我们要清楚的一点就是,每次执行entry()方法,试图冲破一个资源时,都会生成一个上下文。这个上下文中会保存着调用链的根节点和当前的入口。
Context是通过ContextUtil创建的,具体的方法是trueEntry,代码如下:
protected static Context trueEnter ( String name, String origin) {
Context context = contextHolder. get ( ) ;
if ( context == null) {
Map< String, DefaultNode> localCacheNameMap = contextNameNodeMap;
DefaultNode node = localCacheNameMap. get ( name) ;
if ( node == null) {
try {
LOCK. lock ( ) ;
node = contextNameNodeMap. get ( name) ;
if ( node == null) {
node = new EntranceNode ( new StringResourceWrapper ( name, EntryType. IN) , null) ;
Constants. ROOT. addChild ( node) ;
}
} finally {
LOCK. unlock ( ) ;
}
}
context = new Context ( node, name) ;
context. setOrigin ( origin) ;
contextHolder. set ( context) ;
}
return context;
}
上面的代码中我省略了部分代码,只保留了核心的部分。从源码中还是可以比较清晰的看出生成Context的过程:
1.先从ThreadLocal中获取,如果能获取到直接返回,如果获取不到则继续第2步
2.从一个static的map中根据上下文的名称获取,如果能获取到则直接返回,否则继续第3步
3.加锁后进行一次double check,如果还是没能从map中获取到,则创建一个EntranceNode,并把该EntranceNode添加到一个全局的ROOT节点中去,然后将该节点添加到map中去(这部分代码在上述代码中省略了)
4.根据EntranceNode创建一个上下文,并将该上下文保存到ThreadLocal中去,下一个请求可以直接获取
那保存在ThreadLocal中的上下文什么时候会清除呢?从代码中可以看到具体的清除工作在ContextUtil的exit方法中,当执行该方法时,会将保存在ThreadLocal中的context对象清除,具体的代码非常简单,这里就不贴代码了。
那ContextUtil.exit方法什么时候会被调用呢?有两种情况:一是主动调用ContextUtil.exit的时候,二是当一个入口Entry要退出,执行该Entry的trueExit方法的时候,此时会触发ContextUtil.exit的方法。但是有一个前提,就是当前Entry的父Entry为null时,此时说明该Entry已经是最顶层的根节点了,可以清除context。
Entry
刚才在Context身影中也看到了Entry的出现,现在就谈谈Entry。每次执行 SphU.entry() 或 SphO.entry() 都会返回一个Entry,Entry表示一次资源操作,内部会保存当前invocation信息。在一个Context生命周期中多次资源操作,也就是对应多个Entry,这些Entry形成parent/child结构保存在Entry实例中,entry类CtEntry结构如下:
class CtEntry extends Entry {
protected Entry parent = null;
protected Entry child = null;
protected ProcessorSlot chain;
protected Context context;
}
public abstract class Entry implements AutoCloseable {
private long createTime;
private Node curNode;
/**
* {@link Node} of the specific origin, Usually the origin is the Service Consumer.
*/
private Node originNode;
private Throwable error; // 是否出现异常
protected ResourceWrapper resourceWrapper; // 资源信息
}
Entry实例代码中出现了Node,这个又是什么东东呢 ,咱们接着往下看:
DefaultNode
Node(关于StatisticNode的讨论放到下一小节 )默认实现类DefaultNode,该类还有一个子类EntranceNode;context有一个entranceNode属性,Entry中有一个curNode属性。
EntranceNode :该类的创建是在初始化Context时完成的(ContextUtil.trueEnter方法),注意该类是针对Context维度的,也就是一个context有且仅有一个EntranceNode。
DefaultNode :该类的创建是在NodeSelectorSlot.entry完成的,当不存在context.name对应的DefaultNode时会新建(new DefaultNode(resourceWrapper, null),对应resouce)并保存到本地缓存(NodeSelectorSlot中private volatile Map map);获取到context.name对应的DefaultNode后会将该DefaultNode设置到当前context的curEntry.curNode属性,也就是说,在NodeSelectorSlot中是一个context有且仅有一个DefaultNode。
看到这里,你是不是有疑问?为什么一个context有且仅有一个DefaultNode,我们的resouece跑哪去了呢,其实,这里的一个context有且仅有一个DefaultNode是在NodeSelectorSlot范围内,NodeSelectorSlot是ProcessorSlotChain中的一环,获取ProcessorSlotChain是根据Resource维度来的。总结为一句话就是:针对同一个Resource,多个context对应多个DefaultNode;针对不同Resource,(不管是否是同一个context)对应多个不同DefaultNode 。这还没看明白 : (,好吧,我不bb了,上图吧:
public class DefaultNode extends StatisticNode { private ResourceWrapper id; /** * The list of all child nodes. * 子节点集合 / private volatile Set childList = new HashSet<>(); / * * Associated cluster node. */ private ClusterNode clusterNode; }
一个Resouce只有一个clusterNode,多个defaultNode对应一个clusterNode,如果defaultNode.clusterNode为null,则在ClusterBuilderSlot.entry中会进行初始化。
同一个Resource,对应同一个ProcessorSlotChain,这块处理逻辑在lookProcessChain方法中,如下:
ProcessorSlot lookProcessChain(ResourceWrapper resourceWrapper) { ProcessorSlotChain chain = chainMap.get(resourceWrapper); if (chain == null) { synchronized (LOCK) { chain = chainMap.get(resourceWrapper); if (chain == null) { // Entry size limit. if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) { return null; }
chain = SlotChainProvider.newSlotChain();
Map newMap = newHashMap(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
} return chain; }
StatisticNode
StatisticNode中保存了资源的实时统计数据(基于滑动时间窗口机制),通过这些统计数据,sentinel才能进行限流、降级等一系列操作。StatisticNode属性如下:
public class StatisticNode implements Node {
/**
* 秒级的滑动时间窗口(时间窗口单位500ms)
*/
private transient volatile Metric rollingCounterInSecond = newArrayMetric(SampleCountProperty.SAMPLE_COUNT,
IntervalProperty.INTERVAL);
/**
* 分钟级的滑动时间窗口(时间窗口单位1s)
*/
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000,false);
/**
* The counter for thread count.
线程个数用户触发线程数流控
*/
private LongAdder curThreadNum = new LongAdder();
}
public class ArrayMetric implements Metric {
private final LeapArray data;
}
public class MetricBucket {
// 保存统计值
private final LongAdder[] counters;
// 最小rt
private volatile long minRt;
}
其中MetricBucket.counters数组大小为MetricEvent枚举值的个数,每个枚举对应一个统计项,比如PASS表示通过个数,限流可根据通过的个数和设置的限流规则配置count大小比较,得出是否触发限流操作,所有枚举值如下:
public enum MetricEvent {
PASS, // Normal pass.
BLOCK, // Normal block.
EXCEPTION,
SUCCESS,
RT,
OCCUPIED_PASS
}
8 插槽Slot
slot是另一个sentinel中非常重要的概念,sentinel的工作流程就是围绕着一个个插槽所组成的插槽链来展开的。需要注意的是每个插槽都有自己的职责,他们各司其职完好的配合,通过一定的编排顺序,来达到最终的限流降级的目的。默认的各个插槽之间的顺序是固定的,因为有的插槽需要依赖其他的插槽计算出来的结果才能进行工作。
但是这并不意味着我们只能按照框架的定义来,sentinel 通过 SlotChainBuilder 作为 SPI 接口,使得 Slot Chain 具备了扩展的能力。我们可以通过实现 SlotsChainBuilder 接口加入自定义的 slot 并自定义编排各个 slot 之间的顺序,从而可以给 sentinel 添加自定义的功能。
那SlotChain是在哪创建的呢?是在 CtSph.lookProcessChain() 方法中创建的,并且该方法会根据当前请求的资源先去一个静态的HashMap中获取,如果获取不到才会创建,创建后会保存到HashMap中。这就意味着,同一个资源会全局共享一个SlotChain。默认生成ProcessorSlotChain为:
// DefaultSlotChainBuilder
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new LogSlot());
chain.addLast(new StatisticSlot());
chain.addLast(new SystemSlot());
chain.addLast(new AuthoritySlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());
return chain;
这里大概的介绍下每种Slot的功能职责:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
StatisticsSlot
则用于记录,统计不同维度的 runtime 信息;
SystemSlot
则通过系统的状态,例如 load1 等,来控制总的入口流量;
AuthoritySlot
则根据黑白名单,来做黑白名单控制;
FlowSlot
则用于根据预设的限流规则,以及前面 slot 统计的状态,来进行限流;
DegradeSlot
则通过统计信息,以及预设的规则,来做熔断降级;
每个Slot执行完业务逻辑处理后,会调用fireEntry()方法,该方法将会触发下一个节点的entry方法,下一个节点又会调用他的fireEntry,以此类推直到最后一个Slot,由此就形成了sentinel的责任链。
下面我们就来详细研究下这些Slot的原理。
NodeSelectorSlot
NodeSelectorSlot
是用来构造调用链的,具体的是将资源的调用路径,封装成一个一个的节点,再组成一个树状的结构来形成一个完整的调用链,NodeSelectorSlot
是所有Slot中最关键也是最复杂的一个Slot,这里涉及到以下几个核心的概念:
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它服务,甚至可以是一段代码。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
简单来说,资源就是 Sentinel 用来保护系统的一个媒介。源码中用来包装资源的类是:com.alibaba.csp.sentinel.slotchain.ResourceWrapper
,他有两个子类:StringResourceWrapper
和 MethodResourceWrapper
,通过名字就知道可以将一段字符串或一个方法包装为一个资源。
打个比方,我有一个服务A,请求非常多,经常会被陡增的流量冲垮,为了防止这种情况,简单的做法,我们可以定义一个 Sentinel 的资源,通过该资源来对请求进行调整,使得允许通过的请求不会把服务A搞崩溃。
每个资源的状态也是不同的,这取决于资源后端的服务,有的资源可能比较稳定,有的资源可能不太稳定。那么在整个调用链中,Sentinel 需要对不稳定资源进行控制。当调用链路中某个资源出现不稳定,例如,表现为 timeout,或者异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终导致雪崩的后果。
上下文是一个用来保存调用链当前状态的元数据的类,每次进入一个资源时,就会创建一个上下文。**相同的资源名可能会创建多个上下文。**一个Context中包含了三个核心的对象:
1)当前调用链的根节点:EntranceNode
2)当前的入口:Entry
3)当前入口所关联的节点:Node
上下文中只会保存一个当前正在处理的入口Entry,另外还会保存调用链的根节点。需要注意的是,每次进入一个新的资源时,都会创建一个新的上下文。
每次调用 SphU#entry()
都会生成一个Entry入口,该入口中会保存了以下数据:入口的创建时间,当前入口所关联的节点,当前入口所关联的调用源对应的节点。Entry是一个抽象类,他只有一个实现类,在CtSph中的一个静态类:CtEntry
节点是用来保存某个资源的各种实时统计信息的,他是一个接口,通过访问节点,就可以获取到对应资源的实时状态,以此为依据进行限流和降级操作。
可能看到这里,大家还是比较懵,这么多类到底有什么用,接下来就让我们更进一步,挖掘一下这些类的作用,在这之前,我先给大家展示一下他们之间的关系,如下图所示:
这里把几种Node的作用先大概介绍下:
节点
作用
StatisticNode
执行具体的资源统计操作
DefaultNode
该节点持有指定上下文中指定资源的统计信息,当在同一个上下文中多次调用entry方法时,该节点可能下会创建有一系列的子节点。 另外每个DefaultNode中会关联一个ClusterNode
ClusterNode
该节点中保存了资源的总体的运行时统计信息,包括rt,线程数,qps等等,相同的资源会全局共享同一个ClusterNode,不管他属于哪个上下文
EntranceNode
该节点表示一棵调用链树的入口节点,通过他可以获取调用链树中所有的子节点
调用链树
当在一个上下文中多次调用了 SphU#entry() 方法时,就会创建一棵调用链树。具体的代码在entry方法中创建CtEntry对象时:
CtEntry ( ResourceWrapper resourceWrapper, ProcessorSlot< Object> chain, Context context) {
super ( resourceWrapper) ;
this . chain = chain;
this . context = context;
parent = context. getCurEntry ( ) ;
if ( parent != null) {
( ( CtEntry) parent) . child = this ;
}
context. setCurEntry ( this ) ;
}
这里可能看代码没有那么直观,可以用一些图形来描述一下这个过程。
构造树干
创建context
context的创建在上面已经分析过了,初始化的时候,context中的curEntry属性是没有值的,如下图所示:
创建Entry
每创建一个新的Entry对象时,都会重新设置context的curEntry,并将context原来的curEntry设置为该新Entry对象的父节点,如下图所示:
退出Entry
某个Entry退出时,将会重新设置context的curEntry,当该Entry是最顶层的一个入口时,将会把ThreadLocal中保存的context也清除掉,如下图所示:
构造叶子节点
上面的过程是构造了一棵调用链的树,但是这棵树只有树干,没有叶子,那叶子节点是在什么时候创建的呢?DefaultNode就是叶子节点,在叶子节点中保存着目标资源在当前状态下的统计信息。通过分析,我们知道了叶子节点是在NodeSelectorSlot的entry方法中创建的。具体的代码如下:
@Override
public void entry ( Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object. . . args) throws Throwable {
DefaultNode node = map. get ( context. getName ( ) ) ;
if ( node == null) {
synchronized ( this ) {
node = map. get ( context. getName ( ) ) ;
if ( node == null) {
node = Env. nodeBuilder. buildTreeNode ( resourceWrapper, null) ;
}
( ( DefaultNode) context. getLastNode ( ) ) . addChild ( node) ;
}
}
context. setCurNode ( node) ;
fireEntry ( context, resourceWrapper, node, count, args) ;
}
上面的代码可以分解成下面这些步骤: 1)获取当前上下文对应的DefaultNode,如果没有的话会为当前的调用新生成一个DefaultNode节点,它的作用是对资源进行各种统计度量以便进行流控; 2)将新创建的DefaultNode节点,添加到context中,作为「entranceNode」或者「curEntry.parent.curNode」的子节点; 3)将DefaultNode节点,添加到context中,作为「curEntry」的curNode。
上面的第2步,不是每次都会执行。我们先看第3步,把当前DefaultNode设置为context的curNode,实际上是把当前节点赋值给context中curEntry的curNode,用图形表示就是这样:
多次创建不同的Entry,并且执行NodeSelectorSlot的entry方法后,就会变成这样一棵调用链树:
PS:这里图中的node0,node1,node2可能是相同的node,因为在同一个context中从map中获取的node是同一个,这里只是为了表述的更清楚所以用了不同的节点名。
保存子节点
上面已经分析了叶子节点的构造过程,叶子节点是保存在各个Entry的curNode属性中的。
我们知道context中只保存了入口节点和当前Entry,那子节点是什么时候保存的呢,其实子节点就是上面代码中的第2步中保存的。
下面我们来分析上面的第2步的情况:
第一次调用NodeSelectorSlot的entry方法时,map中肯定是没有DefaultNode的,那就会进入第2步中,创建一个node,创建完成后会把该节点加入到context的lastNode的子节点中去。我们先看一下context的getLastNode方法:
public Node getLastNode ( ) {
if ( curEntry != null && curEntry. getLastNode ( ) != null) {
return curEntry. getLastNode ( ) ;
} else {
return entranceNode;
}
}
代码中我们可以知道,lastNode的值可能是context中的entranceNode也可能是curEntry.parent.curNode,但是他们都是「DefaultNode」类型的节点,DefaultNode的所有子节点是保存在一个HashSet中的。
第一次调用getLastNode方法时,context中curEntry是null,因为curEntry是在第3步中才赋值的。所以,lastNode最初的值就是context的entranceNode。那么将node添加到entranceNode的子节点中去之后就变成了下面这样:
紧接着再进入一次,资源名不同,会再次生成一个新的Entry,上面的图形就变成下图这样:
此时再次调用context的getLastNode方法,因为此时curEntry的parent不再是null了,所以获取到的lastNode是curEntry.parent.curNode,在上图中可以很方便的看出,这个节点就是node0 。那么把当前节点node1添加到lastNode的子节点中去,上面的图形就变成下图这样:
然后将当前node设置给context的curNode,上面的图形就变成下图这样:
假如再创建一个Entry,然后再进入一次不同的资源名,上面的图就变成下面这样:
至此NodeSelectorSlot的基本功能已经大致分析清楚了。
PS:以上的分析是基于每次执行SphU.entry(name)时,资源名都是不一样的前提下。如果资源名都一样的话,那么生成的node都相同,则只会再第一次把node加入到entranceNode的子节点中去,其他的时候,只会创建一个新的Entry,然后替换context中的curEntry的值。
ClusterBuilderSlot
NodeSelectorSlot的entry方法执行完之后,会调用fireEntry方法,此时会触发ClusterBuilderSlot的entry方法。
ClusterBuilderSlot的entry方法比较简单,具体代码如下:
@Override
public void entry ( Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object. . . args) throws Throwable {
if ( clusterNode == null) {
synchronized ( lock) {
if ( clusterNode == null) {
clusterNode = Env. nodeBuilder. buildClusterNode ( ) ;
HashMap< ResourceWrapper, ClusterNode> newMap = new HashMap < ResourceWrapper, ClusterNode> ( 16 ) ;
newMap. putAll ( clusterNodeMap) ;
newMap. put ( node. getId ( ) , clusterNode) ;
clusterNodeMap = newMap;
}
}
}
node. setClusterNode ( clusterNode) ;
fireEntry ( context, resourceWrapper, node, count, args) ;
}
NodeSelectorSlot的职责比较简单,主要做了两件事:
一、为每个资源创建一个clusterNode,然后把clusterNode塞到DefaultNode中去
二、将clusterNode保持到全局的map中去,用资源作为map的key
PS:一个资源只有一个ClusterNode,但是可以有多个DefaultNode
StatistcSlot
StatisticSlot负责来统计资源的实时状态,具体的代码如下:
@Override
public void entry ( Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object. . . args) throws Throwable {
try {
fireEntry ( context, resourceWrapper, node, count, args) ;
node. increaseThreadNum ( ) ;
node. addPassRequest ( ) ;
} catch ( BlockException e) {
context. getCurEntry ( ) . setError ( e) ;
node. increaseBlockedQps ( ) ;
throw e;
} catch ( Throwable e) {
context. getCurEntry ( ) . setError ( e) ;
node. increaseExceptionQps ( ) ;
throw e;
}
}
@Override
public void exit ( Context context, ResourceWrapper resourceWrapper, int count, Object. . . args) {
DefaultNode node = ( DefaultNode) context. getCurNode ( ) ;
if ( context. getCurEntry ( ) . getError ( ) == null) {
long rt = TimeUtil. currentTimeMillis ( ) - context. getCurEntry ( ) . getCreateTime ( ) ;
if ( rt > Constants. TIME_DROP_VALVE) {
rt = Constants. TIME_DROP_VALVE;
}
node. rt ( rt) ;
node. decreaseThreadNum ( ) ;
}
fireExit ( context, resourceWrapper, count) ;
}
代码分成了两部分,第一部分是entry方法,该方法首先会触发后续slot的entry方法,即SystemSlot、FlowSlot、DegradeSlot等的规则,如果规则不通过,就会抛出BlockException,则会在node中统计被block的数量。反之会在node中统计通过的请求数和线程数等信息。第二部分是在exit方法中,当退出该Entry入口时,会统计rt的时间,并减少线程数。
这些统计的实时数据会被后续的校验规则所使用,具体的统计方式是通过 滑动窗口
来实现的。
SystemSlot
SystemSlot就是根据总的请求统计信息,来做流控,主要是防止系统被搞垮,具体的代码如下:
@Override
public void entry ( Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object. . . args) throws Throwable {
SystemRuleManager. checkSystem ( resourceWrapper) ;
fireEntry ( context, resourceWrapper, node, count, prioritized, args) ;
}
public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
if (resourceWrapper == null) {
return;
}
// Ensure the checking switch is on.
if (!checkSystemStatus.get()) {
return;
}
// for inbound traffic only
if (resourceWrapper.getEntryType() != EntryType.IN) {
return;
}
// total qps
double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();
if (currentQps > qps) {
throw new SystemBlockException(resourceWrapper.getName(), "qps");
}
// total thread
int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
if (currentThread > maxThread) {
throw new SystemBlockException(resourceWrapper.getName(), "thread");
}
double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
if (rt > maxRt) {
throw new SystemBlockException(resourceWrapper.getName(), "rt");
}
// BBR算法
// load. BBR algorithm.
if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
if (!checkBbr(currentThread)) {
throw new SystemBlockException(resourceWrapper.getName(), "load");
}
}
// cpu usage
if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
throw new SystemBlockException(resourceWrapper.getName(), "cpu");
}
}
其中的Constants.ENTRY_NODE是一个全局的ClusterNode,该节点的值是在StatisticsSlot中进行统计的。
当前的统计值和系统配置的进行比较,各个维度超过范围抛BlockException
AuthoritySlot
AuthoritySlot做的事也比较简单,主要是根据黑白名单进行过滤,只要有一条规则校验不通过,就抛出异常。
@Override
public void entry ( Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object. . . args)
throws Throwable {
checkBlackWhiteAuthority ( resourceWrapper, context) ;
fireEntry ( context, resourceWrapper, node, count, prioritized, args) ;
}
void checkBlackWhiteAuthority ( ResourceWrapper resource, Context context) throws AuthorityException {
Map< String, Set< AuthorityRule> > authorityRules = AuthorityRuleManager. getAuthorityRules ( ) ;
if ( authorityRules == null) {
return ;
}
Set< AuthorityRule> rules = authorityRules. get ( resource. getName ( ) ) ;
if ( rules == null) {
return ;
}
for ( AuthorityRule rule : rules) {
if ( ! AuthorityRuleChecker. passCheck ( rule, context) ) {
throw new AuthorityException ( context. getOrigin ( ) , rule) ;
}
}
}
FlowSlot
FlowSlot主要是根据前面统计好的信息,与设置的限流规则进行匹配校验,如果规则校验不通过则进行限流,具体的代码如下:
@Override
public void entry ( Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object. . . args) throws Throwable {
checkFlow ( resourceWrapper, context, node, count, prioritized) ;
fireEntry ( context, resourceWrapper, node, count, prioritized, args) ;
}
void checkFlow ( ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
throws BlockException {
checker. checkFlow ( ruleProvider, resource, context, node, count, prioritized) ;
}
DegradeSlot
DegradeSlot主要是根据前面统计好的信息,与设置的降级规则进行匹配校验,如果规则校验不通过则进行降级,具体的代码如下:
@Override
public void entry ( Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object. . . args) throws Throwable {
performChecking ( context, resourceWrapper) ;
fireEntry ( context, resourceWrapper, node, count, prioritized, args) ;
}
void performChecking ( Context context, ResourceWrapper r) throws BlockException {
List< CircuitBreaker> circuitBreakers =
DegradeRuleManager. getCircuitBreakers ( r. getName ( ) ) ;
if ( circuitBreakers == null || circuitBreakers. isEmpty ( ) ) {
return ;
}
for ( CircuitBreaker cb : circuitBreakers) {
if ( ! cb. tryPass ( context) ) {
throw new DegradeException ( cb. getRule ( ) . getLimitApp ( ) , cb. getRule ( ) ) ;
}
}
}
DefaultProcessorSlotChain
Chain 是链条的意思,从build的方法可看出,ProcessorSlotChain 是一个链表,里面添加了很多个 Slot。都是 ProcessorSlot 的子类。具体的实现需要到 DefaultProcessorSlotChain 中去看。
public class DefaultProcessorSlotChain extends ProcessorSlotChain {
AbstractLinkedProcessorSlot first = new AbstractLinkedProcessorSlot() {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
throws Throwable {
super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
super.fireExit(context, resourceWrapper, count, args);
}
};
AbstractLinkedProcessorSlot end = first;
@Override
public void addFirst(AbstractLinkedProcessorSlot protocolProcessor) {
protocolProcessor.setNext(first.getNext());
first.setNext(protocolProcessor);
if (end == first) {
end = protocolProcessor;
}
}
@Override
public void addLast(AbstractLinkedProcessorSlot protocolProcessor) {
end.setNext(protocolProcessor);
end = protocolProcessor;
}
/**
* Same as {@link #addLast(AbstractLinkedProcessorSlot)}.
*
* @param next processor to be added.
*/
@Override
public void setNext(AbstractLinkedProcessorSlot next) {
addLast(next);
}
@Override
public AbstractLinkedProcessorSlot getNext() {
return first.getNext();
}
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
throws Throwable {
first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
first.exit(context, resourceWrapper, count, args);
}
}
DefaultProcessorSlotChain中有两个AbstractLinkedProcessorSlot类型的变量:first和end,这就是链表的头结点和尾节点。
创建DefaultProcessorSlotChain对象时,首先创建了首节点,然后把首节点赋值给了尾节点,可以用下图表示: 将第一个节点添加到链表中后,整个链表的结构变成了如下图这样:
将所有的节点都加入到链表中后,整个链表的结构变成了如下图所示:
这样就将所有的Slot对象添加到了链表中去了,每一个Slot都是继承自AbstractLinkedProcessorSlot。而AbstractLinkedProcessorSlot是一种责任链的设计,每个对象中都有一个next属性,指向的是另一个AbstractLinkedProcessorSlot对象。其实责任链模式在很多框架中都有,比如Netty中是通过pipeline来实现的。
知道了SlotChain是如何创建的了,那接下来就要看下是如何执行Slot的entry方法的了
从这里可以看到,从fireEntry方法中就开始传递执行entry了,这里会执行当前节点的下一个节点transformEntry方法,上面已经分析过了,transformEntry方法会触发当前节点的entry,也就是说fireEntry方法实际是触发了下一个节点的entry方法。
从最初的调用Chain的entry()方法,转变成了调用SlotChain中Slot的entry()方法。从 @SpiOrder(-10000)
知道,SlotChain中的第一个Slot节点是NodeSelectorSlot。
slot总结
sentinel的限流降级等功能,主要是通过一个SlotChain实现的。在链式插槽中,有7个核心的Slot,这些Slot各司其职,可以分为以下几种类型:
一、进行资源调用路径构造的NodeSelectorSlot和ClusterBuilderSlot
二、进行资源的实时状态统计的StatisticsSlot
三、进行系统保护,限流,降级等规则校验的SystemSlot、AuthoritySlot、FlowSlot、DegradeSlot
后面几个Slot依赖于前面几个Slot统计的结果。至此,每种Slot的功能已经基本分析清楚了。
9 .sentinel滑动窗口实现原理
基本原理
滑动窗口可以先拆为滑动跟窗口两个词,先介绍下窗口
,你可以这么理解,一段是时间就是窗口,比如说我们可以把这个1s认为是1个窗口
这个样子我们就能将1分钟就可以划分成60个窗口
了,这个没毛病吧。如下图我们就分成了60个窗口
(这个多了我们就画5个表示一下) 比如现在处于第1秒上,那1s那个窗口就是当前窗口
,就如下图中红框表示。 好了,窗口就介绍完了,现在在来看下滑动
,滑动很简单,比如说现在时间由第1秒变成了第2秒,就是从当前这个窗口---->下一个窗口就可以了,这个时候下一个窗口就变成了当前窗口,之前那个当前窗口就变成了上一个窗口,这个过程其实就是滑动。
好了,介绍完了滑动窗口,我们再来介绍下这个sentinel的滑动窗口的实现原理。 其实你要是理解了上面这个滑动窗口的意思,sentinel实现原理就简单了。 先是介绍下窗口中里面都存储些啥。也就是上面这个小框框都有啥。
它得有个开始时间吧,不然你怎么知道这个窗口是什么时候开始的
还得有个窗口的长度吧,不然你咋知道窗口啥时候结束,通过这个开始时间+窗口长度=窗口结束时间,就比如说上面的1s,间隔1s
最后就是要在这个窗口里面统计的东西,你总不能白搞些窗口,搞些滑动吧。所以这里就存储了一堆要统计的指标(qps,rt等等)
说完了这一个小窗口里面的东西,就得来说说是怎么划分这个小窗口,怎么管理这些小窗口的了,也就是我们的视野得往上提高一下了,不能总聚在这个小窗口上。
要知道有多少个小窗口,在sentinel中也就是sampleCount,比如说我们有60个窗口。
还有就是intervalInMs,这个intervalInMs是用来计算这个窗口长度的,intervalInMs/窗口数量= 窗口长度。也就是我给你1分钟,你给我分成60个窗口,这个时候窗口长度就是1s了,那如果我给你1s,你给我分2个窗口,这个时候窗口长度就是500毫秒了,这个1分钟,就是intervalInMs。
再就是存储这个窗口的容器(这里是数组),毕竟那么多窗口,还得提供计算当前时间窗口的方法等等
最后我们就来看看这个当前时间窗口是怎么计算的。 咱们就拿 60个窗口,这个60个窗口放在数组中,窗口长度是1s 来计算,看看当前时间戳的一个时间窗口是是在数组中哪个位置。
比如说当前时间戳是1609085401454 ms,算出秒 = 1609085401454 /1000(窗口长度)
在数组的位置 = 算出秒 %数组长度
我们再来计算下 某个时间戳对应窗口的起始时间,还是以1609085401454 来计算
窗口startTime = 1609085401454 - 1609085401454%1000(窗口长度)=454
这里1609085401454%1000(窗口长度) 能算出来它的毫秒值,也就是454 , 减去这个后就变成了1609085401000
好了,sentinel 滑动窗口原理就介绍完成了。
2.sentinel使用滑动窗口都统计啥
我们来介绍下使用这个滑动窗口都来统计啥
public enum MetricEvent {
PASS,
BLOCK,
EXCEPTION,
SUCCESS,
RT,
OCCUPIED_PASS
}
1234567891011121314151617
这是最基本的指标,然后通过这些指标,又可以计算出来比如说最大,最小,平均等等的一些指标。
3.滑动窗口源码实现
我们先来看下这个窗口里面的统计指标的实现 MetricBucket
3.1 MetricBucket
这个MetricBucket 是由LongAdder数组组成的,一个LongAdder就是一个MetricEvent ,也就是第二小节里面的PASS ,BLOCK等等。 我们稍微看下就可以了 可以看到它在实例化的时候创建一个LongAdder 数据,个数就是那堆event的数量。这个LongAdder 是jdk8里面的原子操作类,你可以把它简单认为AtomicLong。然后下面就是一堆get 跟add 的方法了,这里我们就不看了。 接下来再来看看那这个窗口的实现WindowWrap类
3.2 WindowWrap
先来看下这个成员 窗口长度,窗口startTime ,指标统计的 都有了,下面的就没啥好看的了,我们再来看下的一个方法吧 就是判断某个时间戳是不是在这个窗口中 时间戳要大于等于窗口开始时间 && 小于这个结束时间。
接下来再来看下这个管理窗口的类LeapArray
3.3 LeapArray
看下它的成员, 窗口长度, 样本数sampleCount 也就是窗口个数, intervalInMs ,再就是窗口数组 看看它的构造方法 这个构造方法其实就是计算出来这个窗口长度,创建了窗口数组。 再来看下一个很重要的方法
你可能感兴趣的:(java)
深入探索 Dubbo:高效的 Java RPC 框架
Kale又菜又爱玩
dubbo java rpc
深入探索Dubbo:高效的JavaRPC框架随着微服务架构的流行,分布式系统中的服务间通信变得愈加复杂。Dubbo作为阿里巴巴开源的高性能JavaRPC框架,已成为开发高可用、高性能微服务架构的核心工具之一。本文将深入探讨Dubbo的核心特性、配置方法,以及如何利用Dubbo提供的高级功能来构建一个高效、可靠的分布式系统。什么是Dubbo?Dubbo是一个轻量级、高性能的JavaRPC框架,主要用
【前端进阶】Web Worker性能优化实战:解码10万条数据不卡顿
爱上大树的小猪
前端 性能优化
为什么需要WebWorker?JavaScript是单线程语言,当处理大量数据(如解析10万条JSON数据)时,主线程会被阻塞,导致页面卡顿、无法响应点击事件。WebWorker是浏览器提供的多线程解决方案,可以将耗时任务放到后台执行,解放主线程!实战目标主线程流畅渲染,10万条数据解码不卡顿!代码案例与分步解析1.模拟10万条数据//生成10万条模拟数据functiongenerateMockD
利用Java爬虫根据关键词获取17网(17zwd)商品列表:实战指南
小爬虫程序猿
java 爬虫 开发语言
在电商领域,通过关键词搜索商品并获取商品列表是常见的需求。17网(17zwd)作为知名的电商平台,提供了丰富的商品资源。本文将详细介绍如何使用Java爬虫技术根据关键词获取17网商品列表,并确保爬虫行为符合平台规范。一、环境准备(一)Java开发环境确保你的系统中已安装Java开发环境(推荐使用JDK1.8及以上版本)。(二)安装所需依赖使用Maven管理项目依赖,主要包括以下库:Jsoup:用于
LeetCode——1910. 删除一个字符串中所有出现的给定子字符串(Remove All Occurrences of a Substring)[中等]——分析及代码(Java)
江南土豆
数据结构与算法 LeetCode Java 题解
LeetCode——1910.删除一个字符串中所有出现的给定子字符串[RemoveAllOccurrencesofaSubstring][中等]——分析及代码[Java]一、题目二、分析及代码1.KMP算法(1)思路(2)代码(3)结果三、其他一、题目给你两个字符串s和part,请你对s反复执行以下操作直到所有子字符串part都被删除:找到s中最左边的子字符串part,并将它从s中删除。请你返回从
【智能算法】Dijkstra算法
大雨淅淅
智能算法 算法 python 机器学习 大数据 图论
目录一、Dijkstra算法概述1.1基本概念1.2算法思想1.3算法步骤1.4算法特点二、Dijkstra算法优缺点和改进2.1Dijkstra算法优点2.2Dijkstra算法缺点2.3Dijkstra算法改进三、Dijkstra算法编程实现3.1Dijkstra算法C语言实现3.2Dijkstra算法JAVA实现3.3Dijkstra算法python实现3.4Dijkstra算法matlab
JAVA学习-练习试用Java实现“使用神经网络算法对大数据集进行模式识别和筛选”
守护者170
java学习 java 学习
问题:实现一个Java程序,使用神经网络算法对大数据集进行模式识别和筛选。解答思路:要实现一个使用神经网络算法对大数据集进行模式识别和筛选的Java程序,我们可以使用一个简单的多层感知器(MLP)模型。以下是一个使用Java实现的简单示例,其中使用了'java.util'包中的数据结构和算法。一、在这个例子中,我们将使用以下步骤:1.准备数据集(这里我们将随机生成一些数据)。2.定义一个简单的多层
【Node.js入门笔记1---初始Node.js)】
阿陈陈陈
node.js 笔记
Node.js入门笔记1初始Node.js1.Node.js简介2.Node.js中js的运行环境3.Node.js可以做什么4.Node.js怎么学初始Node.js1.Node.js简介Node.js是一个基于ChromeV8引擎的JavaScript运行时环境,用于在服务器端运行JavaScript代码。它让开发者可以用JavaScript编写后端(服务器端)程序,打破了传统上JavaScr
HTML5 Canvas
智慧浩海
HTML html5 前端 html
标签定义图形,比如图表和其他图像,您必须使用脚本来绘制图形。在画布上(Canvas)画一个红色矩形,渐变矩形,彩色矩形,和一些彩色的文字。什么是canvas?HTML5元素用于图形的绘制,通过脚本(通常是JavaScript)来完成.标签只是图形容器,您必须使用脚本来绘制图形。你可以通过多种方法使用canvas绘制路径,盒、圆、字符以及添加图像。浏览器支持表格中的数字表示支持元素的第一个浏览器版本
pycharm2018
qq_35581867
安装指南
因公司的需求,需要做一个爬取最近上映的电影、列车号、航班号、机场、车站等信息,所以需要我做一个爬虫项目,当然java也可以做爬虫,但是还是没有python这样方便,所以也开始学习Python啦!!!欲善其事,必先利其器。这里我为大家提供了三种激活方式:授权服务器激活:适合小白,一步到位,但服务器容易被封激活码激活:适合小白,Windows、Mac、Linux都适用且无其他副作用,推荐~破解补丁激活
SHEIN面试——Java高级开发
有鹿如溪
面试 Java 面试 职场和发展
2022年7月20希音二面高级开发工程师(供应链)面试流程1自我介绍2问问题开始多线程:1线程间的通信方式2什么是pua定义3什么是内卷的定义4spring和mybatis怎么操作数据库的5平时工作量怎么定义的6工作技巧7如何提升工作效率8对加班有啥看法9为啥离职耗时:30分钟
智慧树刷课神器:自动化学习新体验
詹坦直Lucinda
智慧树刷课神器:自动化学习新体验zhihuishu智慧树刷课插件,自动播放下一集、1.5倍速度、无声项目地址:https://gitcode.com/gh_mirrors/zh/zhihuishu在开源社区的一隅,我们发现了一款专为学生打造的高效学习辅助工具——“智慧树刷课插件”。这款由JavaScript为主要编程语言构建的开源项目,旨在简化在线课程的学习过程,特别是针对广大学生头疼的智慧树网课
jQuery 入门到精通
abments
前端 jquery 前端 javascript
jQuery入门到精通:详尽指南目录jQuery简介jQuery基础安装jQueryjQuery选择器DOM操作事件处理jQuery进阶动画效果AJAX插件项目实践简单的待办事项列表获取和显示API数据高级技巧性能优化调试技巧资源和总结1.jQuery简介jQuery是一个快速、小巧且功能丰富的JavaScript库,它使HTML文档的遍历和操作、事件处理、动画以及AJAX交互更简单。jQuery
蚂蚁集团可转正实习算法岗内推-自然语言
飞300
业界资讯 自然语言处理
具备极佳的工程实现能力,精通C/C++、Java、Pvthon、Perl等至少一门语言:对目前主流的深度学习平台:tensorflow、pytorch、mxnet等,至少对其中一个有上手经验;熟悉深度学习以及常见机器学习算法的原理与算法,能熟练运用聚类、分类、回归、排序等模型解决有挑战性的问题,有大数据处理的实战经验;有强烈求知欲,对人工智能领域相关技术有热情,内推链接:https://u.ali
使用Electron构建桌面应用程序:一个全面指南
AxCybersecurity
electron javascript 前端
Electron是一个强大的框架,它使开发人员能够使用Web技术(如HTML、CSS和JavaScript)构建跨平台的桌面应用程序。本文将介绍如何使用Electron来构建一个简单的桌面应用程序,并提供相应的源代码示例。什么是Electron?Electron是一个开源的框架,由GitHub开发,用于构建跨平台的桌面应用程序。它基于Chromium和Node.js,允许开发人员使用Web技术构建
Java-servlet(四)详细讲解Servlet类层次结构与生命周期
珹洺
Java servlet java servlet 开发语言 运维 服务器 后端
Java-servlet(四)详细讲解Servlet和生命周期前言一、Servlet类层次结构1.Servlet包2.Servlet与ServletConfig与Serializable的关系1.三者分别是什么2.三者之间的关系3.如何让类具有Servlet功能二、Servlet生命周期1.初始化阶段2.服务阶段3.销毁阶段总结前言Servlet是JavaWeb开发的核心组件,负责处理客户端请求并
”天下第一神数“——紫微斗数的JAVA实现!紫微玄机速run~
钮钴禄·爱因斯晨
赛博算命JAVA实现 java python 开发语言
各位佬儿们好呀~~互三必回哦~更多精彩:个人主页赛博算命精彩文章:梅花易数的java实现赛博算命系列文章不作溢美之词,不作浮夸文章,此文与功名进取毫不相关也!与各位共勉!!文章目录#前言:一、紫微斗数简介二、紫微斗数的数学原理1.**命盘构建规则**2.**星曜分布算法**3.**运势推导逻辑**三、Java实现步骤1.代码分布实现1.1**数据结构设计**1.2**命盘构建算法实现**1.3**
华为OD-不限经验,急招,机考资料,面试攻略,不过改推,捞人
2301_79125642
java
超星(学习通)-Java后端一面网易互娱40min(感觉是G了)一篇不太像面经的面经2023总结,前端大二上进小红书秋招面经第一波海康红外图像算法实习(微影)面经测试工程师社招-测试面试题大厂在职傻屌。TPlink图像算法工程师一二三面经深圳海康红外图像算法实习(微影)面经TPLink提前批面经(已OC)传统车辆转规控算法岗秋招记录腾讯TEG测试与质量管理全记录瑞幸Java开发校招一面腾讯金融科技
希音(Shein)前端开发面试题集锦和参考答案
大模型大数据攻城狮
arcgis webpack 前端攻击 xss csrf react 前端面试
用Node写过什么工具或npm包在实际开发中,使用Node编写过多种实用工具和npm包。自动化构建工具开发了一个简单的自动化构建工具,用于处理前端项目的资源压缩和合并。在前端项目中,为了优化性能,需要对CSS和JavaScript文件进行压缩,减少文件体积,同时将多个小文件合并成一个大文件,减少HTTP请求。这个工具使用Node的fs模块进行文件的读写操作,通过terser库对JavaScript
【华为OD机试真题E卷】54、统一限载货物数最小值 | 机试真题+思路参考+代码解析(C++、Java、Py)
KFickle
Java Py) 华为od c++ java 华为OD机试真题 统一限载货物数最小值
文章目录一、题目题目描述输入输出样例1样例2二、代码与思路参考C++代码Java代码Python代码订阅本专栏后即可解锁在线OJ刷题权限个人博客首页:KFickle专栏介绍:最新的华为OD机试真题D、E卷,每题都使用C++,Java,Python语言进行解答,每个题目的思路分析都非常详细,持续更新,支持在线OJ刷题,订阅后评论获取权限,有代码问题随时解答,代码仅供学习参考一、题目题目描述火车站附近
迎接AI挑战:Java程序员的技能进化与发展趋势!!!
小南AI学院
人工智能 java 开发语言
1.AI时代,JAVA程序员编码方式的发展趋势在AI时代,JAVA程序员编码方式的发展趋势正在经历显著变化。以下是几个主要发展方向:AI辅助编码工具的普及:像GitHubCopilot、AmazonCodeWhisperer和JetBrainsAIAssistant等工具正在帮助Java开发者更快地编写代码,自动完成常见模式和解决方案。这些工具不仅提供代码补全,还能生成函数、类甚至完整的实现。声明
Java中获取日期区间中所有日期
知行02
Java相关 日期处理 LocalDate 时间区间 Java 日期遍历
1.获取传入日期区间中所有日期1.代码演示/***根据传入的日期,获取时间区间中所有的日期**@paramstartDate开始日期*@paramendDate结束日期*@returnjava.util.List*@since2022/3/2*/@TestpublicstaticListgetAllDatesInTheDateRange(LocalDatestartDate,LocalDateen
java.util中的Scanner类
鼬猿
java 开发语言 intellij-idea
Scanner类可以用于从各种来源(如标准输入、文件、字符串等)读取不同类型的数据。它提供了各种方法来解析和提取输入的数据,并将其转换为相应的数据类型。在使用Scanner之前,需要先通过import语句导入java.util.Scanner类。创建了一个Scanner对象并传入System.in作为输入源,表示从标准输入中读取数据。例:Scanner变量名=newScanner(System.i
【华为OD技术面试手撕真题】113、组合总和 | 手撕真题+思路参考+代码解析(C & C++ & Java & Python & JS)
KJ.JK
华为OD技术面试手撕真题 华为od 面试 c语言 华为od机试E卷 华为od机试真题 组合总和
文章目录一、题目题目描述样例1二、代码参考C语言思路C语言代码C++语言思路C++代码Java语言思路Java代码Python语言思路Python代码JS语言思路JS代码作者:KJ.JK个人博客首页:KJ.JK专栏介绍:本专栏更新每年华为OD机试的高频手撕代码题,每个题目都会使用五种语言进行解答(C&C++&Java&Python&JS),思路分析都非常详细,争取实现最低的时间复杂度和高通过率,每
Java接口(3)与图书管理系统
风吹落叶3257
java 开发语言
抽象类与接口的区别1.抽象类包含普通类和抽象方法,子类可以直接调用普通类方法不用重写。接口包含抽象方法和全局变量。2.抽象类有各种权限,接口只有pubilc。3.子类使用抽象类用extend,使用接口用implement。4.一个抽象类可以实现若干个接口,接口不能继承抽象类,但是接口可以继承多个接口5.一个类只能继承一个抽象类,一个子类可以实现多个接口。Object类Object类没有父类,可以引
数据库事务,回滚到指定点 oracle java
xiaoyustudiowww
jvm java oracle
======oracle表sqlCREATETABLE"SMALL19RAIN"."R_TABLE_STU"("NAME"VARCHAR2(200BYTE),"AGE"NUMBER,"STU_ID"NUMBERNOTNULLENABLE,"DATARAIN"VARCHAR2(200BYTE))SEGMENTCREATIONIMMEDIATEPCTFREE10PCTUSED40INITRANS1MA
琴韵博主 —— 工具集
琴 韵
知识库 在线文档
CSDN猿如意_开发者工具箱CSDN开发助手ChromeChrome最新版离线下载internetdownloadmanager付费在线转换在线JSON字符串转Java实体类(JavaBean、Entity)在线MD5加密解密YAML、YML在线编辑器(格式化校验)在线图片转换成文字图片base64互转颜色转换颜色转换人民币大小写转换蛙蛙:英文字母大小写转换、文本工具汉字拼音在线转换Google翻
【Qt】Qt Widgets和QML(Qt Quick)开发界面的区别
£އއ昔年
qt 开发语言
Qt提供了两种主要的UI技术:QtWidgets和QML(QtQuick)。它们的核心区别主要体现在使用方式、架构、性能、开发难度和适用场景等方面。1.QtWidgetsvs.QML总体对比对比项QtWidgetsQML(QtQuick)语言C++(带QtUI库)QML+JavaScript(底层C++)渲染方式传统窗口系统控件(原生或模拟)基于OpenGL,使用GPU加速UI风格经典桌面UI(W
JAVA排序
荔枝吃吃
java 排序算法 算法
1.冒泡排序/***使用冒泡排序算法对整数数组进行排序*冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,*一次比较两个元素,如果它们的顺序错误就把它们交换过来*遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成*这个算法的名字由来是因为越小(或越大)的元素会经过交换慢慢“浮”到数列的顶端**@paramarr待排序的整数数组*/publicstaticvoidbubb
React 基础教程
阿贾克斯的黎明
前端 react.js 前端 前端框架
目录React基础教程一、React简介二、安装和设置三、创建第一个React组件(一)函数式组件(二)类组件四、渲染组件五、组件的属性和状态(一)属性(Props)(二)状态(State)六、组件的生命周期方法七、事件处理八、总结React是一个用于构建用户界面的JavaScript库。它以高效、灵活和可维护性而受到广泛的欢迎。本教程将介绍React的基础知识,帮助你快速上手React开发。一、
贪心算法-字符串数组能拼接出的最小字典序(java)
SP_1024
算法 贪心算法 算法 java
最小字典序的贪心算法题目描述贪心算法的解题思路贪心算法自定义比较器贪心算法暴力递归解法题目描述给定一个由字符串组成的数组strs,必须把所有的字符串拼接起来,返回所有可能的拼接结果中字典序最小的结果贪心算法的解题思路首先我们很自然的能想到,遍历数组,比较数组中每一个元素,字典序越小的,就放前面.但这里右一个陷阱,比如ba和b两个字符串,b的字典序小于ba,如果拼成bba就错了,显然bab字典序更小
HttpClient 4.3与4.3版本以下版本比较
spjich
java httpclient
网上利用java发送http请求的代码很多,一搜一大把,有的利用的是java.net.*下的HttpURLConnection,有的用httpclient,而且发送的代码也分门别类。今天我们主要来说的是利用httpclient发送请求。
httpclient又可分为
httpclient3.x
httpclient4.x到httpclient4.3以下
httpclient4.3
Essential Studio Enterprise Edition 2015 v1新功能体验
Axiba
.net
概述:Essential Studio已全线升级至2015 v1版本了!新版本为JavaScript和ASP.NET MVC添加了新的文件资源管理器控件,还有其他一些控件功能升级,精彩不容错过,让我们一起来看看吧!
syncfusion公司是世界领先的Windows开发组件提供商,该公司正式对外发布Essential Studio Enterprise Edition 2015 v1版本。新版本
[宇宙与天文]微波背景辐射值与地球温度
comsci
背景
宇宙这个庞大,无边无际的空间是否存在某种确定的,变化的温度呢?
如果宇宙微波背景辐射值是表示宇宙空间温度的参数之一,那么测量这些数值,并观测周围的恒星能量输出值,我们是否获得地球的长期气候变化的情况呢?
&nbs
lvs-server
男人50
server
#!/bin/bash
#
# LVS script for VS/DR
#
#./etc/rc.d/init.d/functions
#
VIP=10.10.6.252
RIP1=10.10.6.101
RIP2=10.10.6.13
PORT=80
case $1 in
start)
/sbin/ifconfig eth2:0 $VIP broadca
java的WebCollector爬虫框架
oloz
爬虫
WebCollector主页:
https://github.com/CrawlScript/WebCollector
下载:webcollector-版本号-bin.zip将解压后文件夹中的所有jar包添加到工程既可。
接下来看demo
package org.spider.myspider;
import cn.edu.hfut.dmic.webcollector.cra
jQuery append 与 after 的区别
小猪猪08
1、after函数
定义和用法:
after() 方法在被选元素后插入指定的内容。
语法:
$(selector).after(content)
实例:
<html>
<head>
<script type="text/javascript" src="/jquery/jquery.js"></scr
mysql知识充电
香水浓
mysql
索引
索引是在存储引擎中实现的,因此每种存储引擎的索引都不一定完全相同,并且每种存储引擎也不一定支持所有索引类型。
根据存储引擎定义每个表的最大索引数和最大索引长度。所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节。
大多数存储引擎有更高的限制。MYSQL中索引的存储类型有两种:BTREE和HASH,具体和表的存储引擎相关;
MYISAM和InnoDB存储引擎
我的架构经验系列文章索引
agevs
架构
下面是一些个人架构上的总结,本来想只在公司内部进行共享的,因此内容写的口语化一点,也没什么图示,所有内容没有查任何资料是脑子里面的东西吐出来的因此可能会不准确不全,希望抛砖引玉,大家互相讨论。
要注意,我这些文章是一个总体的架构经验不针对具体的语言和平台,因此也不一定是适用所有的语言和平台的。
(内容是前几天写的,现附上索引)
前端架构 http://www.
Android so lib库远程http下载和动态注册
aijuans
andorid
一、背景
在开发Android应用程序的实现,有时候需要引入第三方so lib库,但第三方so库比较大,例如开源第三方播放组件ffmpeg库, 如果直接打包的apk包里面, 整个应用程序会大很多.经过查阅资料和实验,发现通过远程下载so文件,然后再动态注册so文件时可行的。主要需要解决下载so文件存放位置以及文件读写权限问题。
二、主要
linux中svn配置出错 conf/svnserve.conf:12: Option expected 解决方法
baalwolf
option
在客户端访问subversion版本库时出现这个错误:
svnserve.conf:12: Option expected
为什么会出现这个错误呢,就是因为subversion读取配置文件svnserve.conf时,无法识别有前置空格的配置文件,如### This file controls the configuration of the svnserve daemon, if you##
MongoDB的连接池和连接管理
BigCat2013
mongodb
在关系型数据库中,我们总是需要关闭使用的数据库连接,不然大量的创建连接会导致资源的浪费甚至于数据库宕机。这篇文章主要想解释一下mongoDB的连接池以及连接管理机制,如果正对此有疑惑的朋友可以看一下。
通常我们习惯于new 一个connection并且通常在finally语句中调用connection的close()方法将其关闭。正巧,mongoDB中当我们new一个Mongo的时候,会发现它也
AngularJS使用Socket.IO
bijian1013
JavaScript AngularJS Socket.IO
目前,web应用普遍被要求是实时web应用,即服务端的数据更新之后,应用能立即更新。以前使用的技术(例如polling)存在一些局限性,而且有时我们需要在客户端打开一个socket,然后进行通信。
Socket.IO(http://socket.io/)是一个非常优秀的库,它可以帮你实
[Maven学习笔记四]Maven依赖特性
bit1129
maven
三个模块
为了说明问题,以用户登陆小web应用为例。通常一个web应用分为三个模块,模型和数据持久化层user-core, 业务逻辑层user-service以及web展现层user-web,
user-service依赖于user-core
user-web依赖于user-core和user-service
依赖作用范围
Maven的dependency定义
【Akka一】Akka入门
bit1129
akka
什么是Akka
Message-Driven Runtime is the Foundation to Reactive Applications
In Akka, your business logic is driven through message-based communication patterns that are independent of physical locatio
zabbix_api之perl语言写法
ronin47
zabbix_api之perl
zabbix_api网上比较多的写法是python或curl。上次我用java--http://bossr.iteye.com/blog/2195679,这次用perl。for example: #!/usr/bin/perl
use 5.010 ;
use strict ;
use warnings ;
use JSON :: RPC :: Client ;
use
比优衣库跟牛掰的视频流出了,兄弟连Linux运维工程师课堂实录,更加刺激,更加实在!
brotherlamp
linux运维工程师 linux运维工程师教程 linux运维工程师视频 linux运维工程师资料 linux运维工程师自学
比优衣库跟牛掰的视频流出了,兄弟连Linux运维工程师课堂实录,更加刺激,更加实在!
-----------------------------------------------------
兄弟连Linux运维工程师课堂实录-计算机基础-1-课程体系介绍1
链接:http://pan.baidu.com/s/1i3GQtGL 密码:bl65
兄弟连Lin
bitmap求哈密顿距离-给定N(1<=N<=100000)个五维的点A(x1,x2,x3,x4,x5),求两个点X(x1,x2,x3,x4,x5)和Y(
bylijinnan
java
import java.util.Random;
/**
* 题目:
* 给定N(1<=N<=100000)个五维的点A(x1,x2,x3,x4,x5),求两个点X(x1,x2,x3,x4,x5)和Y(y1,y2,y3,y4,y5),
* 使得他们的哈密顿距离(d=|x1-y1| + |x2-y2| + |x3-y3| + |x4-y4| + |x5-y5|)最大
map的三种遍历方法
chicony
map
package com.test;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class TestMap {
public static v
Linux安装mysql的一些坑
chenchao051
linux
1、mysql不建议在root用户下运行
2、出现服务启动不了,111错误,注意要用chown来赋予权限, 我在root用户下装的mysql,我就把usr/share/mysql/mysql.server复制到/etc/init.d/mysqld, (同时把my-huge.cnf复制/etc/my.cnf)
chown -R cc /etc/init.d/mysql
Sublime Text 3 配置
daizj
配置 Sublime Text
Sublime Text 3 配置解释(默认){// 设置主题文件“color_scheme”: “Packages/Color Scheme – Default/Monokai.tmTheme”,// 设置字体和大小“font_face”: “Consolas”,“font_size”: 12,// 字体选项:no_bold不显示粗体字,no_italic不显示斜体字,no_antialias和
MySQL server has gone away 问题的解决方法
dcj3sjt126com
SQL Server
MySQL server has gone away 问题解决方法,需要的朋友可以参考下。
应用程序(比如PHP)长时间的执行批量的MYSQL语句。执行一个SQL,但SQL语句过大或者语句中含有BLOB或者longblob字段。比如,图片数据的处理。都容易引起MySQL server has gone away。 今天遇到类似的情景,MySQL只是冷冷的说:MySQL server h
javascript/dom:固定居中效果
dcj3sjt126com
JavaScript
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml&
使用 Spring 2.5 注释驱动的 IoC 功能
e200702084
spring bean 配置管理 IOC Office
使用 Spring 2.5 注释驱动的 IoC 功能
developerWorks
文档选项
将打印机的版面设置成横向打印模式
打印本页
将此页作为电子邮件发送
将此页作为电子邮件发送
级别: 初级
陈 雄华 (quickselect@163.com), 技术总监, 宝宝淘网络科技有限公司
2008 年 2 月 28 日
&nb
MongoDB常用操作命令
geeksun
mongodb
1. 基本操作
db.AddUser(username,password) 添加用户
db.auth(usrename,password) 设置数据库连接验证
db.cloneDataBase(fromhost)
php写守护进程(Daemon)
hongtoushizi
PHP
转载自: http://blog.csdn.net/tengzhaorong/article/details/9764655
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。php也可以实现守护进程的功能。
1、基本概念
&nbs
spring整合mybatis,关于注入Dao对象出错问题
jonsvien
DAO spring bean mybatis prototype
今天在公司测试功能时发现一问题:
先进行代码说明:
1,controller配置了Scope="prototype"(表明每一次请求都是原子型)
@resource/@autowired service对象都可以(两种注解都可以)。
2,service 配置了Scope="prototype"(表明每一次请求都是原子型)
对象关系行为模式之标识映射
home198979
PHP 架构 企业应用 对象关系 标识映射
HELLO!架构
一、概念
identity Map:通过在映射中保存每个已经加载的对象,确保每个对象只加载一次,当要访问对象的时候,通过映射来查找它们。其实在数据源架构模式之数据映射器代码中有提及到标识映射,Mapper类的getFromMap方法就是实现标识映射的实现。
二、为什么要使用标识映射?
在数据源架构模式之数据映射器中
//c
Linux下hosts文件详解
pda158
linux
1、主机名: 无论在局域网还是INTERNET上,每台主机都有一个IP地址,是为了区分此台主机和彼台主机,也就是说IP地址就是主机的门牌号。 公网:IP地址不方便记忆,所以又有了域名。域名只是在公网(INtERNET)中存在,每个域名都对应一个IP地址,但一个IP地址可有对应多个域名。 局域网:每台机器都有一个主机名,用于主机与主机之间的便于区分,就可以为每台机器设置主机
nginx配置文件粗解
spjich
java nginx
#运行用户#user nobody;#启动进程,通常设置成和cpu的数量相等worker_processes 2;#全局错误日志及PID文件#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log inf
数学函数
w54653520
java
public
class
S {
// 传入两个整数,进行比较,返回两个数中的最大值的方法。
public
int
get(
int
num1,
int
nu