SpringCloud Alibaba Nacos详解
1. 现有项目注册中心替换alibaba组件Nacos
1.springcloud 、springboot、springcloud alibaba版本对应
spring cloud alibaba文档:https://github.com/alibaba/spring-cloud-alibaba/wiki
版本对应:https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明
注意:
- 0.9版本之后,毕业版本的groudId 为 com.alibaba.com
- 0.9版本之前,化器版本groudId 为org.springframework.cloud(不推荐使用)
2. 依赖修改
目前mango系统各组件版本
- springboot版本为1.5
- springcloud alibaba使用毕业版版本1.5.0RELEASE ,不要使用孵化器版本
- nacos service 阿里文档中推荐1.1.1版本,实际上使用最新的稳定版本也可以使用,测试验证使用1.2版本可以正常使用
<!--pom文件依赖新增一下内容,删除其他注册中心的相关的依赖(例如:Eureka) 和其他配置中心的依赖(例如:Springcloud config)-->
<dependencyManagement>
<!--只是对版本进行管理,不会实际引入jar -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>1.5.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--mango系统替换后需要指定springCloud-common包版本号-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<!--因为nacos config是通过web获取配置,需要导入springboot-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos 服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-cloud-commons</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
<exclusion>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<!--nacos 配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-cloud-commons</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
</exclusions>
</dependency>
<!--sentinel依赖,注意网关根据不同的实现方式对应着不同的sentinel依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-boot-starter-ahas-sentinel-client</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
</dependencies>
网关不同实现方式sentinle对应的依赖:
<!--zuul 1.x网关接入, Mango即是zuul 1.X-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-cloud-zuul-starter-ahas-sentinel</artifactId>
<version>1.1.8</version>
</dependency>
<!--springcloudGateway网关接入-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-cloud-gateway-starter-ahas-sentinel</artifactId>
<version>1.1.8</version>
</dependency>
3. 配置文件修改
其他详细的配置可参考nacos-配置管理中的介绍
#bootstrap
spring:
application:
name: gateway
cloud:
sentinel:
transport:
#sentinel 控制面板
dashboard: 127.0.0.1:8083
nacos:
config:
# nacos service地址
server-addr: 127.0.0.1:8848
namespace: public
group: DEFAULT_GROUP
data-id: gateway.yml
file-extension: yaml
refresh:
enables: true # 默认为fasle ,设为true,开启动态刷新
#application
server:
port: 56020 #启动端口 命令行注入
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# nacos service地址
server‐addr: 127.0.0.1:8848
4. 代码修改
- 启动类新增服务发现注解
@EnableDiscoveryClient
@EnableFeignClients
- 生产者声明
@FeignClient(name = "uaa")
,mango项目中,生产者远程代理在common中,注意修改common的依赖
2. nacos介绍
Nacos是阿里的一个开源产品,它是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。
nacos文档: https://nacos.io/en-us/docs/what-is-nacos.html
2.1 nacos-配置管理
nacos-config文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config
2.1.1 配置中心的使用场景
微服务架构,系统拆分为一个个服务节点,配置文件也被分割,分散中就包含了很多相同的配置,造成冗余。
配置中心就是将配置从应用里剥离出来, 进行统一管理。
配置中心的服务流程:
1. 用户在配置中心更新配置信息。
2. 服务A和服务B及时得到配置更新通知,从配置中心获取配置
2.1.2 常见的配置中心
nacos 读写性能最高,且和springcloud condig相比,nacos带图形化界面配置。
2.1.3 nacos 快速部署
-
下载地址:https://github.com/alibaba/nacos/releases,下载源码自己编译/下载编译好的安装包
-
安装包启动方式:bin目录下执行 sh startup.sh -m standalone
standalone 代表单机模式运行,非集群模式
sh startup ‐m cluster 标识集群启动
-
启动成功, http://127.0.0.1:8848/nacos可打开nacos控制台,默认账号密码都为 nacos
-
OPEN API 配置管理测试 :https://nacos.io/en-us/docs/open-api.html
- 发布配置:
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs dataId=nacos.cfg.dataId&group=test&content=HelloWorld"
- 获取配置:
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"
- 发布配置:
2.1.4 nacos 外部mysql数据库支持
单机模式nacos使用的是嵌入式数据库实现数据储存,可以配置mysql作为外部存储(目前只支持使用mysql)。
-
新建数据库nacos_config
-
执行nacos下conf/nacos-mysql.sql 文件
-
修改nacos下conf/application.properties,增加支持mysql数据源配置,增加MySQL数据源账号密码
-
增加后记得重启一下nacos
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user= db.password=
2.1.5 nacos 配置管理应用
对于nacos配置管理,通过namespace、group、Data ID 可以定位到一个配置集(也可以理解为一个配置文件)。
- namespace:设计是 nacos 基于此做多环境以及多租户(多个用户共同使用nacos)数据(配置和服务)隔离的。
一个租户情况下:有多套不同环境,可以使用同一套nacos集群,通过建多个不同的nacespace来做配置/服务的隔离
多个租户情况下:不同租户分配不同的账号密码,创建自己的namespace,不同租户下namespace互不可见(nacos还没有实现多租户功能)
nacos 服务隔离,应该是同一namespace下才可以调用其他服务,注册中心虽然是一个,但是注册表不同
服务获取配置集需要配置的内容:
- nacos server 地址 ,必须配置
- namespace:可以不指定,默认是public,若指定填写的是namespace的id而不是名称,id在nacos管理页面查看
- group:可以不指定,默认是DEFAUT_GROUP
- dataId:必须指定
nacos配置管理提供了配置clone,历史版本、回滚、订阅者查询等核心管理能力,通过图形化界面很好操作。
监听查询:
Nacos提供配置订阅者即监听者查询能力,同时提供客户端当前配置的MD5校验值,以便帮助用户更好的检查配 置变更是否推送到 Client 端。
2.1.6. nacos登录管理:
除了使用默认的nacos账号密码,可以通过直接在数据库中增加账号密码,密码为BCrypt加密方法
也可以在nacos service中管理登录功能,
spring.security.enabled=false management.security=false security.basic.enabled=false nacos.security.ignore.urls=/**
2.1.7 nacos集成分布式系统
通过 Spring cloud原生方式快捷的获取配置内容:
1. 在bootstrap.yml基础配置
Spring:
cloud:
nacos:
config:
enabled: true # 可不指定,默认true,false时关闭了nacos-config配置
server-addr: 127.0.0.1:8848 #必须指定
namespace: c67e4a97‐a698‐4d6d‐9bb1‐cfac5f5b51c4 # 可不指定,默认为public
ext-configs[0]: #配置单个配置集的时候,可省略
group: DEFAULT_GROUP # 可不指定,默认为DEFAULT_GROUP
file-extension: yaml # 默认是properties
data-id: service.yaml # 可不指定,默认是${spring.application.name}和文件拓展名组合
# 指定data-id时,需要带上文件拓展名,yaml和yml都可以,和file-extension两者不干扰,也可以配置多个data-id,优先级根据ex-config[n]中n的数字大小决定,数字越大,优先级越高
refresh:
enables: true # 默认为false,设为fasle 关闭动态刷新
ext‐config[1]:
# Data Id 不在默认的组,不支持动态刷新
data‐id: ext‐config‐common02.properties # 可以配置公用的配置
group: GLOBALE_GROUP
在加载配置文件时,不仅会加载${spring.application.name}.${file-extension:properties}为前缀的基础配置
还会加载${spring.application}0 - ${profile}.${file-extension:properties}的基础信息
${spring.profiles.active} 当通过配置文件来指定时必须放在 bootstrap.properties 文件中。但是实际项目中,时通过启动参数-Dspring.profiles.active=<profile>
来切换不同的配置
通过修改namespace、group、dataId可以获取指定的配置集,还可以修改profile.active来指定配置集
2. 自定义共享DataId配置
spring:
cloud:
nacos:
config:
# 配置分享dataId必须带上文件后缀名,refreable-dataids也是
# shared-dataids 中group为默认的DEFAULT_GROUP ,若所配的data-id不在默认组中获取不到配置集
# 支持多个DataId配置,逗号隔开,优先级按照配置出现的先后顺序,即后面的优先级要高于前面。
shared‐dataids: ext‐config‐common01.properties,ext‐config‐common02.properties
# 指定动态刷新的 配置集
refreshable‐dataids: ext‐config‐common01.properties
3. 配置的优先级
Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置。
- A: 通过
spring.cloud.nacos.config.shared-configs[n].data-id
支持多个共享 Data Id 的配置 - B: 通过
spring.cloud.nacos.config.extension-configs[n].data-id
的方式支持多个扩展 Data Id 的配置 - C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置
当三种方式共同使用时,他们的一个优先级关系是:A < B < C
4. 实时获取最新配置
// 注入配置文件上下文
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping(value = "/configs")
public String getConfigs(){
// 实时获取配置
return applicationContext.getEnvironment().getProperty("common.name");
}
2.1.8 nacos集群部署
-
安装三个以上nacos
-
在所有nacos conf目录下将cluster.conf.example文件改为cluster.conf,将每行配置成 ip:port。(请配置3个或3个以上节点)
# ip:port 127.0.0.1:8848 127.0.0.1:8849 127.0.0.1:8850
-
单机的话还需修改conf下application.properties中server.port ,防止端口冲突,如果服务器有多个ip也要指定具体的ip地址,如:nacos.inetutils.ip-address=127.0.0.1
-
客户端配置,spring.cloud.nacos.config.server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
关闭其中的注册中心,其他注册注册中心会被成功选举。
2.2服务发现
2.2.1 常见服务发现中心:
从长远来看Nacos在以后的版本会 支持SpringCLoud+Kubernetes的组合,填补 2 者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解 决方案,这将大大的简化使用和维护的成本。另外,Nacos 计划实现 Service Mesh,也是未来微服务发展的趋 势。
2.2.2 代码修改
增加依赖,增加服务发现注解, 如1-4,省略
2.2.3 服务管理
- 可以通过nacos service 可视化界面对服务进行管理, 可以看到已经注册的服务,且可以进行服务上线下线管理,通过配置实例的权重,修改实例接收的流量,权重为0时,不接受流量,和下线效果一样
- 也可以在服务详情页面,进行元数据的修改,key-value的结构
2.2.4 nacos disvocery 配置项信息详情
配置项 | Key | 默认值 | 说明 |
---|---|---|---|
服务端地址 |
spring.cloud.nacos.discovery.server-addr |
无 |
Nacos Server 启动监听的ip地址和端口 |
服务名 |
spring.cloud.nacos.discovery.service |
${spring.application.name} |
给当前的服务命名 |
服务分组 |
spring.cloud.nacos.discovery.group |
DEFAULT_GROUP |
设置服务所处的分组 |
权重 |
spring.cloud.nacos.discovery.weight |
1 |
取值范围 1 到 100,数值越大,权重越大 |
网卡名 |
spring.cloud.nacos.discovery.network-interface |
无 |
当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址 |
注册的IP地址 |
spring.cloud.nacos.discovery.ip |
无 |
优先级最高 |
注册的端口 |
spring.cloud.nacos.discovery.port |
-1 |
默认情况下不用配置,会自动探测 |
命名空间 |
spring.cloud.nacos.discovery.namespace |
无 |
常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。 |
AccessKey |
spring.cloud.nacos.discovery.access-key |
无 |
当要上阿里云时,阿里云上面的一个云账号名 |
SecretKey |
spring.cloud.nacos.discovery.secret-key |
无 |
当要上阿里云时,阿里云上面的一个云账号密码 |
Metadata |
spring.cloud.nacos.discovery.metadata |
无 |
使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息 |
日志文件名 |
spring.cloud.nacos.discovery.log-name |
无 |
|
集群 |
spring.cloud.nacos.discovery.cluster-name |
DEFAULT |
配置成Nacos集群名称 |
接入点 |
spring.cloud.nacos.discovery.enpoint |
UTF-8 |
地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 |
是否集成Ribbon |
ribbon.nacos.enabled |
true |
一般都设置成true即可 |
是否开启Nacos Watch |
spring.cloud.nacos.discovery.watch.enabled |
true |
可以设置成false来关闭 watch |
3. sentinel
流控制组件,包括流控制,并发限制,电路中断和自适应系统保护,以确保微服务的可靠性。
sentinel包括两个部分:
- 核心库:不依赖任何框架
- 控制台:springboot应用,可直接运行,负责管理推送规则、监控、集群限流分配,机器发现等.
sentinel功能:
- 流控
- 断路和并发
- 最大并发限制
过限制并发线程的数量(即信号隔离)来减少不稳定资源的影响,而不是使用线程池。(hystrix使用线程池来实现隔离,可做对比//TODO)
当资源的响应时间变长时,线程将开始被占用。当线程数累积到一定数量时,新的传入请求将被拒绝。反之亦然,当资源恢复并变得稳定时,占用的线程也将被释放,新请求将被接受。 - 断路
根据不稳定资源的响应时间降级不稳定资源保证可靠性。当资源的响应时间太大时,将在指定的时间窗口中拒绝所有对该资源的访问。
- 最大并发限制
3.1 定义资源
能通过sentinel Api定义的diamond,就是资源
可以是:服务,方法,某一段代码
3.1.1 Api方式定义资源
文档:https://github.com/alibaba/Sentinel/wiki/如何使用
// 举例
@GetMapping(value="/echo")
public String echo(){
// 定义资源
try(Entry entry = Sphu.entry("echo")){
//定义被保护的逻辑
return "i am from port"+port;
}catch(BlockException e){
retrun "当前访问被控制了";
}
}
3.1.2 使用注解定义资源
sentinle提供了@SentinelResource
注解的方式定义资源,更推荐使用。
主要属性 | 作用 |
---|---|
value | 资源名称,必需项(不能为空) |
blockHandler | 在原方法被限流/降级/系统保护的时候调用,可选项。必须与原方法必须处于同一个类中、访问类型必须为public、返回类型需要与原方法相匹配、参数类型需要和原方法相匹配。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
-
定义配置类
@Configuration public class SentinelAspectConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } }
-
使用注解定义资源
@GetMapping(value = "/echo") @SentinelResource(value = "echo",blockHandler = "echoBlockHandler") public String echo() { //调用规则 this.initFlowRules(); //定义被保护的逻辑 return "i am from port " + port; } // 1. 必须处于同一个类,2. 访问类型必须为public,3. 返回值类型必须与原方法一致,4. 参数与类型必须与原方法相同,BlockException是特例 public String echoBlockHandler(BlockException e){ //处理被控制的逻辑 return "当前访问被控制了"; } private void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //关联域名 rule.setResource("echo"); //设定限流阈值类型 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //设置限流阈值 rule.setCount(2); //设置流控针对的调用来源,default为不区分来源 rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); }
3.2 定义规则
规则就是保护服务稳定的准则,可以是流控规则、服务熔断降级规则、系统保护规则、来源访问控制规则、热点参数规则。
sentinle的所有规则都在内存
中动态查询修改,规则可以实时进行调整。
定义规则有两种途径:1.sentinel Api 2. sentinel控制台
3.2.1 Api规则定义实现
![1584606721445](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584606721445.png?lastModify=1604835718)
// 举例
private void initFolwRules(){
// flowRule 流控规则,可以定义多个流控规则
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
//关联域名、资源名称
rule.setResource("echo");
//设定限流阀值类型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置限流阈值
rule.setCount(2);
//设置流控针对的调用来源,default为不区分来源
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
3.2.2 资源和规则结合
return "当前访问被控制了";
@GetMapping(value = "/echo")
public String echo() {
//调用规则
this.initFlowRules();
//定义资源
try (Entry entry = SphU.entry("echo")){
//定义被保护的逻辑
return "i am from port " + port;
} catch (BlockException e) {
//处理被控制的逻辑
return "当前访问被控制了";
}
}
private void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
//关联域名
rule.setResource("echo");
//设定限流阈值类型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置限流阈值
rule.setCount(2);
//设置流控针对的调用来源,default为不区分来源
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
3.3 sentinle控制台使用
访问:https://github.com/alibaba/Sentinel/releases。进行对应版本下载,可下载jar包,也可下载exe文件
3.3.1 客户端接入控制台
客户端添加控制台配置后重启服务,若未开启自动心跳, 则服务资源后调用后,才能在控制台看到具体服务。sentinel是延迟加载的。
project:
name: 服务名
// application.yml中添加配置
sentinel:
dashboard:
server: sentinel控制台地址
3.4 sentinel各规则详解
3.4.1流控- QPS(每秒查询率)流量控制
sentinel实现流控的原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
3.4.1.1 不针对来源进行流控
针对来源选项值当为default,代表不针对来源。即对所有微服务都生效。
3.4.1.2 针对特定来源流控
对于一个系统而言,不同的请求来源,很有可能会设置不同的规则。
例如对于商品搜索结果页面,针对PC端指定一个规则,针对小程序端指定一个规则。
-
添加依赖
-
声明配置bean
@Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { // 从请求路径或者请求头中获取请求来源信息 // 此处以请求头为例 String origin = httpServletRequest.getParameter("origin"); if (StringUtils.isBlank(origin)) { throw new IllegalArgumentException("origin must be exist"); } return origin; } }
-
添加配置类
@Configuration @ConditionalOnWebApplication( type = ConditionalOnWebApplication.Type.SERVLET ) @ConditionalOnClass({CommonFilter.class}) @ConditionalOnProperty( name = {"spring.cloud.sentinel.enabled"}, matchIfMissing = true ) //@EnableConfigurationProperties({SentinelProperties.class}) // 从sentinel源码中copy出来 public class SentinelWebAutoConfiguration { //private static final Logger log = LoggerFactory.getLogger(com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration.class); // @Autowired // private SentinelProperties properties; @Autowired private Optional<UrlCleaner> urlCleanerOptional; @Autowired private Optional<UrlBlockHandler> urlBlockHandlerOptional; @Autowired private Optional<RequestOriginParser> requestOriginParserOptional; public SentinelWebAutoConfiguration() { } @PostConstruct public void init() { this.urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler); this.urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner); this.requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser); } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true ) public FilterRegistrationBean sentinelFilter() { FilterRegistrationBean<Filter> registration = new FilterRegistrationBean(); // com.alibaba.cloud.sentinel.SentinelProperties.Filter filterConfig = this.properties.getFilter(); // if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) { // List<String> defaultPatterns = new ArrayList(); // defaultPatterns.add("/*"); // filterConfig.setUrlPatterns(defaultPatterns); // } // registration.addUrlPatterns((String[])filterConfig.getUrlPatterns().toArray(new String[0])); Filter filter = new CommonFilter(); registration.setFilter(filter); registration.setOrder(-2147483648); //registration.setOrder(filterConfig.getOrder()); registration.addInitParameter("HTTP_METHOD_SPECIFY", String.valueOf(false)); //log.info("[Sentinel Starter] register Sentinel CommonFilter with urlPatterns: {}.", filterConfig.getUrlPatterns()); return registration; } }
-
在控制台增加流控规则,指定来源
![1585061229263](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1585061229263.png?lastModify=1604838986)
-
访问资源增加origin=pc,才会被限流,不是pc时可以任意访问。例如:
http://localhost:8083/info?origin=pc
-
3.4.2 流控-并发线程控制
并发线程数限流用于保护业务线程数不被耗尽。
例如,当下游服务出现响应时间延长,则会导致上游服务吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。
为了避免这种情况,业内比较常见的解决方案是线程隔离(hystrix)。给不同的业务逻辑分配不同的线程池。但是这种方案会造成大量的线程上下文切换,严重影响效率。
sentinle实现原理:统计请求上下文线程数、超出阈值,新的请求被拒绝,效果当前与信号隔离。
// 测试,在控制台增加并发线程控制规则,可以发现有一部分请求会被隔离掉,这是因为超过了线程的阈值。
public class MyThread implements Runnable {
@Override
public void run() {
RestTemplate restTemplate = new RestTemplate();
String forObject = restTemplate.getForObject("http://localhost:8083/info", String.class);
System.out.println(forObject);
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
//线程池
ExecutorService pool = Executors.newCachedThreadPool();
for(int i=0;i<100;i++){
MyThread myThread = new MyThread();
pool.execute(myThread);
}
}
}
3.4.3 流控-流控模式、流控效果
- QPS关联模式-快速失败:场景为两个方法会产生性能上相互影响,如读方法和写方法。若写方法优先级高,在定义读方法流控规则时,将关联资源定为写方法,当写方法操作超过阈值,会限制读方法。
- QPS链路模式快速失败:链路即从指定入口访问的资源达到阈值则限流
流控效果:
-
快速失败:达到限流规则,不允许进行访问
-
warmUp:冷启动/预热,对流量的增加是逐步增加的,适用于处理突发性流量。
threshold (阈值)/coldFactor(冷加载因子,默认为3)=最初的阈值
,接着最初的阈值*预热时长
来最终实现限流。 -
排队等待/匀速器:让请求匀速通过,适用处理间隔性突发的流量。
原理:如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;
否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);
若预期的通过时间超出最大排队时长,则直接拒接这个请求。
![1585067276895](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1585067276895.png?lastModify=1604840075)
3.4.4 熔断降级
避免某个接口异常,造成请求堆积,导致系统雪崩。
1. 熔断降级策略:
3.4.4.1 熔断策略-平均响应时间:rt
当 1s 内持续进入 5 个请求,对应时刻的平均响应时间超过阈值(count
,以 ms 为单位),那么会在下一个时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地熔断
3.4.4.2 熔断策略-异常比例
当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(每秒异常总数/通过量 > 阈值
)之后,资源进入降级状态,即在下一个时间窗口,对这个方法的调用自动熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%
3.4.4.3 熔断策略-异常数
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计是分钟级别的,若 timeWindow
小于 60s,则结束熔断状态后仍可能再进入熔断状态。
3.4.5 热点参数限流
热点就是经常被访问的数据,也叫热数据。
热点参数限流会统计访问时所带的参数,根据定义的规则进行参数级别的限流。也是一种流量控制。
=sentinel热点参数限流实现原理:Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
举例:对id为1的参数进行限流,那么在访问时,生效的级别只会在id为1时,当id为其他值时不会进行限流控制。
![1584713089706](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584713089706.png?lastModify=1604846149)
3.4.5.1 热点参数位限流
![1584714823721](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584714823721.png?lastModify=1604846213)
上述配置声明:对于hot资源通过QPS限流模式对访问路径上第一个参数,每秒只能访问一次。
3.4.5.1 热点参数值限流
![1584715354860](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584715354860.png?lastModify=1604846338)
上述内容声明:对于hot资源通过QPS限流模式对访问路径上第一个参数,每秒只能访问一次。但 当参数值2,每秒能访问两次。当参数值3,每秒能访问10000次。
3.4.6 系统自适应限流
文档:https://github.com/alibaba/Sentinel/wiki/系统自适应限流
3.4.6.1 系统保护简介
sentinel从 应用的load、cpu利用率、总体平均rt、入口QPS和并发线程几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡。
系统保护目的:1. 保证系统不会拖垮 2.系统稳定的前提下,保证系统的吞吐量
以往的系统保护思路:根据硬指标,即load来做系统过载的保护,当系统负载高于阈值,就会禁止或减少流量的进入,load好转即恢复流量的进入。
以往设计思路的弊端:
- load是一个结果,根据load 的情况来调节流量的通过率会有延迟性
- 恢复慢:当下游应用异常导致RT过高,从而load达到了一个很高的点,当下游应用恢复,这是load应然很高,通过率依然还在受限制
sentinel系统保护的思路:
- 根据系统能处理的请求和允许进来的请求做平衡,而不是通过一个间接的指标(load)做限流。
- 在sentinel系统保护的做法中,load是作用启动自适应保护因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。
3.4.6.2 系统保护-规则
系统保护规则是从应用级别的入口流量做控制的,并且仅对入口流量生效(进入应用的流量EntryType.IN
,如web服务或Dubbo服务端接收的请求)。
从单台机器的load、CPU利用率、平均RT、入口QPS、并发线程数等几个维度监控应用指标。
系统规则支持以下的模式:
- 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 达到阈值即触发系统保护
3.4.6.3 系统保护原理
3.5 动态规则、规则持久化
Sentinel默认会把规则信息保存到内存中,当服务重启之后,规则就会丢失。可以将规存储在文件、数据库或者配置中心当中。
3.5.1 规则推送模式
- pull(拉)模式:客户端定期轮询拉取规则,规则持久化,但实时性不能保证,拉取过于频繁还有性能问题。
- push(推)模式:规则中心统计推送,客户端通过注册监听器的方式监听变化,可以使用Nacos、zookeeper等配置中心。这种方式有更好的实时性和以执行的保证。推荐使用
4. 负载
文档:https://juejin.im/post/6844903608371118094
在衡量服务器的性能时,经常会涉及到几个指标,load(机器负载)、cpu、mem、qps、rt等。每个指标都有其独特的意义,很多时候在线上出现问题时,往往会伴随着某些指标的异常。
负载(load)是linux机器的一个重要指标,直观了反应了机器当前的状态。
在UNIX系统中,系统负载是对当前CPU工作量的度量,被定义为特定时间间隔内运行队列中的平均线程数。load average 表示机器一段时间内的平均load。这个值越低越好。负载过高会导致机器无法处理其他请求及操作,甚至导致死机。Linux的负载高,主要是由于CPU使用、内存使用、IO消耗三部分构成。任意一项使用过多,都将导致服务器负载的急剧攀升。
load值代表的是对应时间内的jobs的平均数量,比如load1就表示过去1分钟内的jobs数量的平均值。
4.1 查看机器负载
在Linux机器上,有多个命令都可以查看机器的负载信息。其中包括uptime
、top
、w
等。
- uptime:能够打印系统总共运行了多长时间和系统的平均负载。
➜ ~ uptime
13:29 up 23:41, 3 users, load averages: 1.74 1.87 1.97
现在时间、系统已经运行了多长时间、目前有多少登陆用户、系统在过去的1分钟、5分钟和15分钟内的平均负载。
-
w:能够打印当前时间,系统启动到现在的时间,登录用户的数目,系统在最近1分钟、5分钟和15分钟的平均负载。然后是每个用户的各项数据,项目显示顺序如下:登录帐号、终端名称、远 程主机名、登录时间、空闲时间、JCPU、PCPU、当前正在运行进程的命令行]。
-
top:是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。
4.2 机器正常负载范围
没有一个明确的指标。
可以根据自己机器的实际情况,建立一个指标的基线(如近一个月的平均值),只要日常的load在基线上下范围内不太大都可以接收,如果差距太多可能就要人为介入检查了。
阮一峰对此的建议为:
当系统负荷持续大于0.7,你必须开始调查了,问题出在哪里,防止情况恶化。
当系统负荷持续大于1.0,你必须动手寻找解决办法,把这个值降下来。
当系统负荷达到5.0,就表明你的系统有很严重的问题,长时间没有响应,或者接近死机了。你不应该让系统达到这个值。注:这里的指标是但cpu的,如果是多喝系统,需乘上cpu的数量
4.3 如何降低负荷
CPU使用、内存使用、IO消耗都可能导致负载高。如果是软件问题,有可能由于Java中的某些线程被长时间占用、大量内存持续占用等导致。建议从以下几个方面排查代码问题:
- 是否有内存泄露导致频繁GC
- 是否有死锁发生
- 是否有大字段的读写
- 会不会是数据库操作导致的,排查SQL语句问题
- 死循环
这里还有个建议,如果发现线上机器Load飙高,可以考虑先把堆栈内存dump下来后,进行重启,暂时解决问题,然后再考虑回滚和排查问题。
4.4 排查思路
1、使用uptime查看当前load,发现load飙高。
➜ ~ uptime
13:29 up 23:41, 3 users, load averages: 10 10 10
复制代码
2、使用top命令,查看占用CPU较高的进程ID。
➜ ~ top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1893 admin 20 0 7127m 2.6g 38m S 181.7 32.6 10:20.26 java
复制代码
发现PID为1893的进程占用CPU 181%。而且是一个Java进程,基本断定是软件问题。
3、使用 top
命令,查看具体是哪个线程占用率较高
➜ ~ top -Hp 1893
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4519 admin 20 0 7127m 2.6g 38m R 18.6 32.6 0:40.11 java
复制代码
4、使用printf
命令查看这个线程的16进制
➜ ~ printf %x 4519
11a7
复制代码
5、使用jstack
命令查看当前线程正在执行的方法。(Java命令学习系列(二)——Jstack)
➜ ~ jstack 1893 |grep -A 200 11a7
"thread-5" #500 daemon prio=10 os_prio=0 tid=0x00007f632314a800 nid=0x11a2 runnable [0x000000005442a000]
java.lang.Thread.State: RUNNABLE
at sun.misc.URLClassPath$Loader.findResource(URLClassPath.java:684)
at sun.misc.URLClassPath.findResource(URLClassPath.java:188)
at java.net.URLClassLoader$2.run(URLClassLoader.java:569)
at java.net.URLClassLoader$2.run(URLClassLoader.java:567)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findResource(URLClassLoader.java:566)
at org.hibernate.validator.internal.xml.ValidationXmlParser.getInputStreamForPath(ValidationXmlParser.java:248)
at com.hollis.test.util.BeanValidator.validate(BeanValidator.java:30)
复制代码
从上面的线程的栈日志中,可以发现,当前占用CPU较高的线程正在执行我代码的com.hollis.test.util.BeanValidator.validate(BeanValidator.java:30)类。那么就可以去排查这个类是否用法有问题了。
6、还可以使用jstat(Java命令学习系列(四)——jstat)来查看GC情况,看看是否有频繁FGC,然后再使用jmap(Java命令学习系列(三)——Jmap)来dump内存,查看是否存在内存泄露。